Skip to content

burl/inquire

Repository files navigation

inquire (v2)

Lightweight, line-oriented interactive CLI prompts for Go — an inquirer.js-style experience without taking over the full terminal.

Each question renders as a small inline band at the current cursor. Answered prompts settle to static ✔ … scrollback; the next question opens below. No alternate screen, no full-screen repaint.

This repo is v2. Import github.com/burl/inquire/v2 — not github.com/burl/inquire. The original v1 module (termbox-based) is frozen; see Migration from v1.

go get github.com/burl/inquire/v2

Requires Go 1.26+. Unix terminals (Linux, macOS, BSD) are supported; Windows is not in v2.0.

API docs: pkg.go.dev/github.com/burl/inquire/v2 (indexed after a v2.* release tag is pushed).

Quick start

package main

import (
    "context"
    "errors"
    "fmt"
    "os"

    "github.com/burl/inquire/v2"
)

func main() {
    var name string
    var ok bool

    err := inquire.Query().
        Input(&name, "what is your name", nil).
        YesNo(&ok, "continue", nil).
        Run(context.Background())
    if errors.Is(err, inquire.ErrInterrupted) {
        fmt.Fprintln(os.Stderr, "interrupted")
        os.Exit(130)
    }
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Println(name, ok)
}

TTY requirements

Run requires both stdin and stdout to be terminals. Redirecting either stream (e.g. piping, CI without a pseudo-TTY) returns inquire.ErrNotTerminal. Stderr may be redirected freely.

Use inquire.WithInput / inquire.WithOutput when embedding in tests or alternate file descriptors.

Interrupts

Ctrl+C aborts the entire session and returns inquire.ErrInterrupted. Answers from prompts already completed remain in bound variables; later prompts are not run. Your application should treat this as a full cancel of the remaining flow.

Widgets

Widget Binds Notes
Input *string Defaults, validation, password mask
YesNo *bool Arrows, y/n, space
Menu *string (tag) Vertical single-select
Select *bool per item Multi-select checkboxes
Note Non-interactive message; Enter to continue
AnyKey Continues on any key

Configure every widget in the more callback:

inquire.Query().
    Menu(&quest, "your quest", func(w *widget.Menu) {
        w.Item("grail", "find the grail")
        w.When(widget.WhenEqual(&mode, "advanced"))
    })

Conditional prompts use When with widget.WhenEqual predicates.

Keybindings

Context Keys
All Ctrl+C — abort session
Input Arrows, Home/End, Backspace/Delete; Ctrl+A/E/K/D/W; Ctrl+B/F (left/right); Tab completes when Complete is set
YesNo ←/→, ↑/↓, y/n, Space (toggle), Enter (confirm)
Menu ↑/↓, ←/→ (move); Space/Enter (confirm); Ctrl+P/N (up/down)
Select ↑/↓ (move); Space (toggle item); a (select all); i (invert); Enter (confirm)
Note Enter (continue)
AnyKey Any key (continue)

Footer hints are shown on Menu and Select while active.

Recipes

Conditional follow-up

inquire.Query().
    Menu(&quest, "your quest", func(w *widget.Menu) {
        w.Item("grail", "find the grail")
        w.Item("nuts", "find coconuts")
    }).
    Input(&weight, "swallow weight", func(w *widget.Input) {
        w.When(widget.WhenEqual(&quest, "nuts"))
        w.Valid(func(s string) string {
            if s == "" { return "required" }
            return ""
        })
    })

Section divider

inquire.Query().
    Note("Database setup", nil).
    Input(&host, "host", nil).
    Input(&port, "port", nil)

Force monochrome output

inquire.Query(inquire.WithColor(false)).
    Input(&name, "name", nil)

Embed with signal handling

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()

err := inquire.Query().Input(&name, "name", nil).Run(ctx)
if errors.Is(err, inquire.ErrInterrupted) {
    // user pressed Ctrl+C
}

Demo

task demo

Runs demo/grail.go — the Monty Python Bridge of Death quest through every widget (Note, AnyKey, Input, Menu, Select, YesNo).

Lower-level band/terminal demo:

task demo:termui

Development

Requires Task. Common targets:

task check       # vet, test, lint
task build       # inquire-grail for current host → bin.out/
task build:all   # cross-compile demos for linux+darwin (amd64+arm64)

CI and releases

Workflow Trigger What it does
ci.yml PRs to master / develop task check (ubuntu + macos) + task build:all
release.yml Push to master Same gate, then semver-tags from conventional commits and publishes a short API guide

To ship a release: merge a green PR into master. The release workflow runs automatically: scripts/next-version.sh inspects commits since the last v* tag and bumps the version — breaking change → major (v3.0.0), feat → minor (v2.1.0), anything else → patch (v2.0.1). The first release on a fresh tree is v2.0.0. That tag is what pkg.go.dev and go get use — distinct from the /v2 in the import path (see Migration from v1).

Enable branch protection on master and require the ci jobs (check, build) before merge.

Migration from v1

v1 used module path github.com/burl/inquire (termbox, global state, different API). v2 is a separate module with a /v2 suffix per Go module versioning:

v1 (frozen) v2 (this branch)
Module github.com/burl/inquire github.com/burl/inquire/v2
Import import "github.com/burl/inquire" import "github.com/burl/inquire/v2"
Tags v0.x on the old tree v2.x.x on this tree

See docs/MIGRATION.md for API changes.

How this differs from full-screen TUIs

Libraries like Bubble Tea, huh, and tview assume an application model: alternate buffer, cleared screen, global event loop. That is the right fit for full dashboards and rich TUIs.

inquire targets a narrower case: ask a few questions in the middle of an existing CLI, leave prior output in scrollback, and return control to the caller via Run(ctx) error — no os.Exit, no init panics.

License

MIT

About

a collection of common interactive command line user interfaces

Topics

Resources

License

Stars

21 stars

Watchers

3 watching

Forks

Packages

 
 
 

Contributors