inquire

package module
v2.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 5 Imported by: 0

README

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

Documentation

Overview

Package inquire provides line-oriented interactive CLI prompts that render inline at the current cursor, leaving answered questions as static scrollback.

TTY requirements

Run requires both stdin and stdout to be terminals. Piping either stream returns ErrNotTerminal. Stderr may be redirected.

Interrupts

Ctrl+C aborts the entire session and returns ErrInterrupted. Answers bound before the interrupt are kept; later prompts are not run.

See https://pkg.go.dev/github.com/burl/inquire/v2 for API reference.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotTerminal is returned when stdin or stdout is not a terminal.
	ErrNotTerminal = errors.New("inquire: stdin/stdout is not a terminal")
	// ErrInterrupted is returned when the user presses Ctrl+C.
	ErrInterrupted = errors.New("inquire: interrupted")
)

Functions

This section is empty.

Types

type Option

type Option func(*Session)

Option configures a query session.

func WithColor

func WithColor(enabled bool) Option

WithColor forces ANSI color on (true) or off (false). When unset, color follows NO_COLOR, TERM, and COLORTERM.

func WithInput

func WithInput(in *os.File) Option

WithInput sets the terminal input stream (must be a TTY for Run).

func WithOutput

func WithOutput(out *os.File) Option

WithOutput sets the terminal output stream (must be a TTY for Run).

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is a fluent builder for interactive prompts.

func Query

func Query(opts ...Option) *Session

Query builds an interactive prompt session.

Example (Chain)
package main

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

func main() {
	var name string
	var ok bool

	// Widgets chain on Query; Run needs a real TTY in production.
	_ = inquire.Query().
		Input(&name, "what is your name", nil).
		YesNo(&ok, "continue", nil)

}
Example (NotTerminal)
package main

import (
	"context"
	"errors"
	"fmt"

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

func main() {
	err := inquire.Query().Run(context.TODO())
	fmt.Println(errors.Is(err, inquire.ErrNotTerminal))
}
Output:
true

func (*Session) AnyKey

func (s *Session) AnyKey(message string, more func(*widget.AnyKey)) *Session

AnyKey adds a prompt that continues on any key (except Ctrl+C).

func (*Session) Input

func (s *Session) Input(value *string, prompt string, more func(*widget.Input)) *Session

Input adds a text prompt bound to value.

func (*Session) Menu

func (s *Session) Menu(value *string, prompt string, more func(*widget.Menu)) *Session

Menu adds a single-select menu bound to value.

func (*Session) Note

func (s *Session) Note(text string, more func(*widget.Note)) *Session

Note adds a non-interactive message printed before the next prompt.

func (*Session) Run

func (s *Session) Run(ctx context.Context) error

Run executes the prompt session.

func (*Session) Select

func (s *Session) Select(prompt string, more func(*widget.Select)) *Session

Select adds a multi-select checkbox group.

func (*Session) YesNo

func (s *Session) YesNo(value *bool, prompt string, more func(*widget.YesNo)) *Session

YesNo adds a yes/no prompt bound to value.

Directories

Path Synopsis
Grail demo: Monty Python quest through every inquire widget.
Grail demo: Monty Python quest through every inquire widget.
termui command
Command termui exercises the inquire band layer: an inline menu that does not take over the full screen.
Command termui exercises the inquire band layer: an inline menu that does not take over the full screen.
internal
termui
Package termui implements the inline terminal band layer for inquire.
Package termui implements the inline terminal band layer for inquire.
Package widget provides configurable prompt types used with [inquire.Query].
Package widget provides configurable prompt types used with [inquire.Query].

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL