Documentation
¶
Overview ¶
Package tui wraps charmbracelet/huh to ensure every interactive prompt fails cleanly under --no-input or non-TTY instead of hanging.
Field skipping (when values are already set via flags/env) is the caller's responsibility — they must check the value before calling Field().
This is the linchpin of dual-use (human + LLM): one command surface, two modes of driving it.
Index ¶
- Variables
- func BrandTheme() *huh.Theme
- func Confirm(noInput bool, msg string) (bool, error)
- func ConfirmDefault(noInput bool, msg string, defaultValue bool) (bool, error)
- func IsInteractive() bool
- func NewChartApp(data *api.ChartData, ...) tea.Model
- func OpenURL(url string) error
- func Required(label string) func(string) error
- func RunBrowser(title string, items []BrowserItem) error
- func RunBrowserTable(title string, cols []string, items []BrowserItem) error
- func RunChartView(data *api.ChartData, ...) error
- type BrowserField
- type BrowserItem
- type BrowserLink
- type BrowserSection
- type BrowserSectionRow
- type Chat
- type ChatEmitter
- type ChatEntry
- type ChatRole
- type Flow
- func (f *Flow) Confirm(question string, def bool) (bool, error)
- func (f *Flow) Hint(line string)
- func (f *Flow) Input(title, placeholder string, validate func(string) error, desc ...string) (string, error)
- func (f *Flow) Intro(title string)
- func (f *Flow) Item(line string)
- func (f *Flow) Ledger(labels ...string) *Ledger
- func (f *Flow) Outro(title string, extras ...string)
- func (f *Flow) Password(title string, validate func(string) error, desc ...string) (string, error)
- func (f *Flow) Receipt(key, value string)
- func (f *Flow) Say(line string)
- func (f *Flow) Select(title string, opts []Option, desc ...string) (string, error)
- func (f *Flow) Step(title string)
- func (f *Flow) URL(url string)
- func (f *Flow) Warn(line string)
- type FormBuilder
- type Ledger
- type Option
Constants ¶
This section is empty.
Variables ¶
var ErrPromptCancelled = errors.New("cancelled")
ErrPromptCancelled is returned when the user aborts a rail prompt (esc/ctrl-c).
Functions ¶
func BrandTheme ¶
BrandTheme follows the CLI's color standard: ANSI semantics carry meaning (red is danger), RC violet is the single interaction accent (focus, selection, cursor), and field titles stay neutral bold so labels never look like errors. Brand red appears only at section landmarks, not here.
func ConfirmDefault ¶
func IsInteractive ¶
func IsInteractive() bool
IsInteractive reports whether both stdin and stdout are connected to a TTY.
func NewChartApp ¶
func NewChartApp( data *api.ChartData, fetchFn func(resID string, startUnix int64) (*api.ChartData, error), noColor bool, ) tea.Model
NewChartApp builds a chart model from fetched data for embedding in the browser. Terminal size is set by the browser via tea.WindowSizeMsg.
func RunBrowser ¶
func RunBrowser(title string, items []BrowserItem) error
RunBrowser launches a simple-list browser frame.
func RunBrowserTable ¶
func RunBrowserTable(title string, cols []string, items []BrowserItem) error
RunBrowserTable launches a table browser frame.
Types ¶
type BrowserField ¶
BrowserField is a key/value row in a detail view.
type BrowserItem ¶
type BrowserItem struct {
ID string
Label string // primary text in simple-list and breadcrumb
Meta string // dim secondary text in simple-list
Row []string // cells used when the parent frame is in table mode
WebURL string
Fields []BrowserField
Links []BrowserLink
// OpenChart: if set, Enter fetches and embeds the chart directly in the
// browser (no separate program). Takes priority over DirectLoad.
OpenChart func() (tea.Model, error)
// DirectLoad bypasses the detail view: pressing Enter fires the func and
// pushes a new frame. Return non-nil cols to get a table frame.
DirectLoad func() (title string, cols []string, items []BrowserItem, err error)
// AutoLoad fires when the detail view opens; results are inline tables.
AutoLoad func() (sections []BrowserSection, err error)
}
BrowserItem is one row in a list/table view or the subject of a detail view.
type BrowserLink ¶
type BrowserLink struct {
Label string
Load func() (title string, items []BrowserItem, err error)
}
BrowserLink is a nav entry in the detail view that pushes a child list.
type BrowserSection ¶
type BrowserSection struct {
Title string
Cols []string
Rows []BrowserSectionRow
Empty string
}
BrowserSection is an inline table in a detail view, populated by AutoLoad.
type BrowserSectionRow ¶
type BrowserSectionRow struct {
Cells []string
Item *BrowserItem
}
BrowserSectionRow is one row in a BrowserSection. If Item is non-nil the row is selectable.
type Chat ¶
type Chat struct {
Title string
Subtitle string
// Placeholder shown in the empty input.
Placeholder string
// Send runs one conversation turn. It is called on its own goroutine and
// must stop when ctx is cancelled. It must call emit.Done exactly once.
Send func(ctx context.Context, message string, emit *ChatEmitter)
// Transcript preloads history (e.g. when resuming a conversation).
Transcript []ChatEntry
// Initial, when set, is sent as the first turn as soon as the window opens.
Initial string
// RelativeLinkBase, when set, prefixes relative markdown link targets
// ("[x](/path)") in assistant messages so they are clickable in a
// terminal (e.g. "https://app.revenuecat.com").
RelativeLinkBase string
}
Chat is a full-screen streaming chat window (transcript + input) for agent conversations. The caller supplies Send, which runs one turn and reports progress through the ChatEmitter; the window handles rendering, scrolling, and tool-approval prompts.
type ChatEmitter ¶
type ChatEmitter struct {
// contains filtered or unexported fields
}
ChatEmitter delivers turn progress into the running window. Methods are safe to call from the Send goroutine.
func (*ChatEmitter) Approve ¶
func (e *ChatEmitter) Approve(prompt string, destructive bool) bool
Approve blocks until the user decides (or the window closes, which counts as a rejection).
func (*ChatEmitter) Delta ¶
func (e *ChatEmitter) Delta(text string)
func (*ChatEmitter) Done ¶
func (e *ChatEmitter) Done(err error)
Done ends the turn; err is shown as a notice rather than exiting the window.
func (*ChatEmitter) ToolCall ¶
func (e *ChatEmitter) ToolCall(name string)
type Flow ¶
type Flow struct {
// contains filtered or unexported fields
}
Flow renders a guided, single-experience command as one continuous vertical rail (clack-style): a ┌ intro cap, ◇ step headers and │ narration on a shared spine, and a └ outro cap. It's the shell for human-first `requires_human` flows so the whole command reads as one designed experience rather than a scatter of lines.
Interactive prompts (Confirm/Select/Input) happen between rail calls — they're the active moments the rail pauses for, then resumes. Shares its styles and rail helpers with the prompt models (prompt_rail.go).
On a non-TTY (plain), the glyphs are dropped for clean append-only lines.
func (*Flow) Hint ¶
Hint prints a dim guidance line on the rail (rail-native equivalent of Renderer.Hint, so hints stay on the gutter mid-flow).
func (*Flow) Input ¶
func (f *Flow) Input(title, placeholder string, validate func(string) error, desc ...string) (string, error)
Input's validate may be nil.
func (*Flow) Password ¶
Password is like Input but masks the entry and never echoes the value back in the resolved receipt.
type FormBuilder ¶
type FormBuilder struct {
// contains filtered or unexported fields
}
func Form ¶
func Form(noInput bool) *FormBuilder
func (*FormBuilder) Field ¶
func (b *FormBuilder) Field(f huh.Field) *FormBuilder
Field adds an interactive field to the form. It unconditionally appends the field — callers must decide whether to call this based on whether the value is already set (e.g. via flag). Under --no-input, fields are validated via their attached Validate() functions; required fields should use Validate(Required(...)) to enforce presence in non-interactive mode.
func (*FormBuilder) Run ¶
func (b *FormBuilder) Run() error
func (*FormBuilder) WithScriptedIO ¶
func (b *FormBuilder) WithScriptedIO(r io.Reader, w io.Writer) *FormBuilder
WithScriptedIO drives the form from scripted keystrokes (r) and captures rendering (w), bypassing the TTY check. For tests only: it lets us drive the real huh widgets end to end instead of leaving the interactive layer untested.
type Ledger ¶
type Ledger struct {
// contains filtered or unexported fields
}
A Ledger renders a fixed list of steps that fill in as they run: a spinner on the active step, a green check when it finishes, a red cross on failure, and a dim marker for steps not yet reached. On a TTY it repaints in place (bubbletea inline — no alt-screen, scrollback preserved). When not on a TTY (or plain is set), it prints append-only lines instead, so CI logs stay clean.
Usage:
l := tui.NewLedger(os.Stderr, plain, "Enable APIs", "Create service account") l.Start() l.Running(0); /* work */; l.Done(0, "") l.Running(1); /* work */; l.Fail(1, "quota exceeded") l.Stop()