tui

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 25 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var ErrPromptCancelled = errors.New("cancelled")

ErrPromptCancelled is returned when the user aborts a rail prompt (esc/ctrl-c).

Functions

func BrandTheme

func BrandTheme() *huh.Theme

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 Confirm

func Confirm(noInput bool, msg string) (bool, error)

func ConfirmDefault

func ConfirmDefault(noInput bool, msg string, defaultValue bool) (bool, error)

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 OpenURL

func OpenURL(url string) error

OpenURL opens url in the default system browser.

func Required

func Required(label string) func(string) error

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.

func RunChartView

func RunChartView(
	data *api.ChartData,
	fetchFn func(resID string, startUnix int64) (*api.ChartData, error),
	noColor bool,
) error

RunChartView runs an interactive scrollable chart viewer. Returns an error if stdout or stdin is not a TTY — callers should fall back to --json.

Types

type BrowserField

type BrowserField struct {
	Key   string
	Value string
}

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 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.

func (Chat) RunChat

func (c Chat) RunChat() error

RunChat opens the window and blocks until the user exits.

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 ChatEntry

type ChatEntry struct {
	Role ChatRole
	Text string
}

type ChatRole

type ChatRole int
const (
	ChatUser ChatRole = iota
	ChatAssistant
	ChatTool
	ChatNotice
)

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 NewFlow

func NewFlow(w io.Writer, plain, noColor, quiet bool) *Flow

func (*Flow) Confirm

func (f *Flow) Confirm(question string, def bool) (bool, error)

Confirm returns def without prompting in plain mode.

func (*Flow) Hint

func (f *Flow) Hint(line string)

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) Intro

func (f *Flow) Intro(title string)

func (*Flow) Item

func (f *Flow) Item(line string)

func (*Flow) Ledger

func (f *Flow) Ledger(labels ...string) *Ledger

func (*Flow) Outro

func (f *Flow) Outro(title string, extras ...string)

func (*Flow) Password

func (f *Flow) Password(title string, validate func(string) error, desc ...string) (string, error)

Password is like Input but masks the entry and never echoes the value back in the resolved receipt.

func (*Flow) Receipt

func (f *Flow) Receipt(key, value string)

func (*Flow) Say

func (f *Flow) Say(line string)

func (*Flow) Select

func (f *Flow) Select(title string, opts []Option, desc ...string) (string, error)

func (*Flow) Step

func (f *Flow) Step(title string)

func (*Flow) URL

func (f *Flow) URL(url string)

URL prints the raw URL, not a hyperlink label, so it stays selectable to copy on terminals that don't support OSC 8.

func (*Flow) Warn

func (f *Flow) Warn(line string)

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()

func NewLedger

func NewLedger(w io.Writer, plain bool, labels ...string) *Ledger

NewLedger builds a ledger for the given ordered step labels.

func (*Ledger) Done

func (l *Ledger) Done(i int, note string)

Done marks step i complete, with an optional trailing note.

func (*Ledger) Fail

func (l *Ledger) Fail(i int, note string)

Fail marks step i failed, with a reason.

func (*Ledger) Running

func (l *Ledger) Running(i int)

Running marks step i as the active one (spinner).

func (*Ledger) Start

func (l *Ledger) Start()

Start begins rendering. On a TTY it launches the live region; in plain mode it is a no-op (lines are printed as steps resolve).

func (*Ledger) Stop

func (l *Ledger) Stop()

Stop finishes the live region, leaving the final frame in scrollback.

type Option

type Option struct {
	Label string
	Value string
}

Jump to

Keyboard shortcuts

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