Documentation
¶
Overview ¶
Package ui provides consistent terminal output primitives for the obol CLI.
Pass a *UI through your call chain instead of using fmt directly. Output adapts to the environment: colors and spinners in interactive terminals, plain text when piped or in CI.
When OutputJSON mode is active, human-readable output (Info, Success, Print, etc.) is redirected to stderr so that stdout contains only clean JSON. Use the JSON() method to emit structured data.
Index ¶
- Constants
- func Banner() string
- func ColorDisabled() bool
- func Heading(title string) string
- func Paint(kind, s string) string
- func StdinPromptable() bool
- type BrowserEnv
- type BrowserPlan
- type ExecConfig
- type OutputMode
- type UI
- func (u *UI) Blank()
- func (u *UI) Bold(msg string)
- func (u *UI) CanPrompt() bool
- func (u *UI) Check(kind, title string, width int, msg, fix string)
- func (u *UI) Confirm(msg string, defaultYes bool) bool
- func (u *UI) ConfirmOrFlag(msg, flag string) (bool, error)
- func (u *UI) DefaultBrowserEnv() BrowserEnv
- func (u *UI) Detail(key, value string)
- func (u *UI) Dim(msg string)
- func (u *UI) Error(msg string)
- func (u *UI) Errorf(format string, args ...any)
- func (u *UI) Exec(cfg ExecConfig) error
- func (u *UI) ExecOutput(cfg ExecConfig) ([]byte, error)
- func (u *UI) FormatActionableError(err error, action string)
- func (u *UI) FormatError(err error, hint string)
- func (u *UI) Info(msg string)
- func (u *UI) Infof(format string, args ...any)
- func (u *UI) Input(msg string, defaultVal string) (string, error)
- func (u *UI) InputWithSuffix(msg, defaultVal, suffix string) (string, error)
- func (u *UI) IsJSON() bool
- func (u *UI) IsQuiet() bool
- func (u *UI) IsTTY() bool
- func (u *UI) IsVerbose() bool
- func (u *UI) JSON(v any) error
- func (u *UI) OpenBrowser(url string) bool
- func (u *UI) OpenURL(label, url string, autoOpen bool) bool
- func (u *UI) Print(msg string)
- func (u *UI) Printf(format string, args ...any)
- func (u *UI) RunWithSpinner(msg string, fn func() error) error
- func (u *UI) SecretInput(msg string) (string, error)
- func (u *UI) Select(msg string, options []string, defaultIdx int) (int, error)
- func (u *UI) SetNoBrowser(v bool)
- func (u *UI) Success(msg string)
- func (u *UI) Successf(format string, args ...any)
- func (u *UI) SuggestCommand(parent *cli.Command, command string)
- func (u *UI) Warn(msg string)
- func (u *UI) WarnCount() int64
- func (u *UI) Warnf(format string, args ...any)
Constants ¶
const ( ColorObolGreen = "#2FE4AB" // Primary brand green ColorObolDarkGreen = "#0F7C76" // Dark green (success) ColorObolCyan = "#3CD2DD" // Light blue / info ColorObolPurple = "#9167E4" // Accent purple ColorObolAmber = "#FABA5A" // Warning amber ColorObolRed = "#DD603C" // Error red-orange ColorObolAcid = "#B6EA5C" // Highlight acid green ColorObolMuted = "#667A80" // Muted gray ColorObolLight = "#97B2B8" // Light muted )
Obol brand colors — from blog.obol.org/branding.
const ( GlyphOK = "✓" GlyphCurrent = "▸" GlyphFail = "✗" GlyphBusy = "◐" GlyphStopped = "◼" GlyphBullet = "●" GlyphPending = "·" )
Status glyphs for multi-line status views (network status). The glyph is always printed; NO_COLOR (or a non-terminal stdout) only drops the color.
Variables ¶
This section is empty.
Functions ¶
func Banner ¶
func Banner() string
Banner returns the Obol Stack ASCII art rendered in brand colors.
func ColorDisabled ¶ added in v0.15.0
func ColorDisabled() bool
ColorDisabled reports whether the user opted out of colored output via NO_COLOR (any non-empty value).
func Heading ¶ added in v0.17.0
Heading renders a section heading in the same style as Info ("==> title"), for callers that build a frame as a string instead of printing directly.
func Paint ¶ added in v0.17.0
Paint styles s for a status kind: "ok", "warn", "fail", "info", "dim", "bold" or "accent". Unknown kinds return s unchanged. Use it to compose lines that are written in one go (e.g. a redrawn --watch frame).
func StdinPromptable ¶ added in v0.15.0
func StdinPromptable() bool
StdinPromptable reports whether a raw prompt on stdin (sudo -v, legacy readers that don't go through UI) can be answered: stdin is a terminal and OBOL_NONINTERACTIVE is unset.
Types ¶
type BrowserEnv ¶ added in v0.15.0
type BrowserEnv struct {
// GOOS is runtime.GOOS (or a test override).
GOOS string
// Getenv reads an environment variable.
Getenv func(string) string
// LookPath reports whether a binary is on PATH (exec.LookPath shape).
LookPath func(string) (string, error)
// ProcVersion is the contents of /proc/version (Linux only; "" elsewhere).
ProcVersion string
// Interactive is true when stdout is a TTY, output is human (not JSON)
// and OBOL_NONINTERACTIVE is not "true".
Interactive bool
// Disabled is true when the operator passed the global --no-open flag.
Disabled bool
}
BrowserEnv is everything the open-a-browser decision depends on. It is a plain struct so the decision (PlanBrowserOpen) is a pure, table-testable function; DefaultBrowserEnv fills it from the real process.
func (BrowserEnv) IsWSL ¶ added in v0.15.0
func (e BrowserEnv) IsWSL() bool
IsWSL reports whether the environment looks like Windows Subsystem for Linux.
type BrowserPlan ¶ added in v0.15.0
BrowserPlan is the outcome of PlanBrowserOpen: either a command to start (Open == true) or the reason the browser is left alone.
func PlanBrowserOpen ¶ added in v0.15.0
func PlanBrowserOpen(env BrowserEnv, url string) BrowserPlan
PlanBrowserOpen decides whether (and how) to open url in a browser. It never opens anything itself. Rules, in order:
- --no-open / OBOL_NO_BROWSER → skip
- non-interactive (no TTY, JSON output, OBOL_NONINTERACTIVE=true) → skip
- CI → skip
- over SSH (SSH_CONNECTION / SSH_TTY) → skip (the browser would open on the remote host, not in front of the operator)
- WSL → wslview when on PATH, else explorer.exe
- macOS → open
- Windows → rundll32 url.dll,FileProtocolHandler
- other Unix → xdg-open, only when DISPLAY or WAYLAND_DISPLAY is set
type ExecConfig ¶
type ExecConfig struct {
// Name is the display name shown in the spinner (e.g., "Deploying with helmfile").
Name string
// Cmd is the command to run.
Cmd *exec.Cmd
// Interactive runs the command with stdin/stdout/stderr connected directly
// to the terminal. Use this for commands that may prompt for input (e.g. sudo).
// Disables spinner and output capture.
Interactive bool
}
ExecConfig configures how a subprocess is run.
type OutputMode ¶ added in v0.8.0
type OutputMode int
OutputMode controls the format of command output.
const ( // OutputHuman is the default human-readable output mode. OutputHuman OutputMode = iota // OutputJSON produces machine-readable JSON on stdout. OutputJSON )
func ParseOutputMode ¶ added in v0.8.0
func ParseOutputMode(s string) (OutputMode, error)
ParseOutputMode converts a string to an OutputMode.
type UI ¶
type UI struct {
// contains filtered or unexported fields
}
UI provides consistent terminal output primitives.
func New ¶
New creates a UI instance. When verbose is true, subprocess output is streamed live instead of captured behind a spinner.
func NewForTest ¶ added in v0.10.0
NewForTest creates a UI instance that writes to the supplied writers. Use bytes.Buffer for stdout/stderr to capture output in unit tests.
func NewForTestTTY ¶ added in v0.17.0
NewForTestTTY is NewForTest with prompts enabled, for exercising interactive pickers in unit tests (answers are read from os.Stdin).
func NewWithAllOptions ¶ added in v0.8.0
func NewWithAllOptions(verbose, quiet bool, output OutputMode) *UI
NewWithAllOptions creates a UI instance with full control over all modes.
func NewWithOptions ¶
NewWithOptions creates a UI instance with full control over verbose and quiet modes. Quiet mode suppresses all output except errors.
func (*UI) CanPrompt ¶ added in v0.15.0
CanPrompt reports whether prompts reach a person: a TTY, not JSON mode, and OBOL_NONINTERACTIVE unset.
func (*UI) Check ¶ added in v0.16.0
Check prints one diagnostic line, " ✓ title message", with the symbol coloured by kind (ok, info, skip, warn, fail) and an optional dimmed "→ fix" line under it. title is padded to width. Warn and fail lines are shown even in quiet mode.
func (*UI) Confirm ¶
Confirm asks a yes/no question, returns true for "y"/"yes". In non-interactive mode, returns the default without prompting.
func (*UI) ConfirmOrFlag ¶ added in v0.15.0
ConfirmOrFlag asks a destructive yes/no question (default No). When no one can answer it, it returns an error naming the flag that skips it, rather than silently declining and exiting 0 as if the action had run.
func (*UI) DefaultBrowserEnv ¶ added in v0.15.0
func (u *UI) DefaultBrowserEnv() BrowserEnv
DefaultBrowserEnv builds a BrowserEnv from the real process and this UI's output mode.
func (*UI) Error ¶
Error prints: ✗ message (red x, matching obolup.sh log_error). Not suppressed by quiet mode.
func (*UI) Exec ¶
func (u *UI) Exec(cfg ExecConfig) error
Exec runs a subprocess with output capture and spinner.
Default mode (TTY, not verbose):
- Shows spinner with config.Name
- Captures stdout+stderr to buffer
- On success: "✓ Name (Xs)"
- On failure: "✗ Name" + dumps captured output to stderr
Verbose mode:
- Shows "==> Name"
- Streams stdout+stderr live, each line indented with dim "│" prefix
Non-TTY (pipe/CI):
- Shows "Name..."
- Streams live (no spinner)
func (*UI) ExecOutput ¶
func (u *UI) ExecOutput(cfg ExecConfig) ([]byte, error)
ExecOutput runs a subprocess, captures stdout, and returns it. Stderr is captured and shown on error.
func (*UI) FormatActionableError ¶
FormatActionableError renders an error with a concrete next-step command.
✗ Stack not running Run: obol stack up
func (*UI) FormatError ¶
FormatError renders a structured error with an optional hint.
✗ something went wrong Hint: check your configuration
func (*UI) Input ¶
Input reads a single line of text input with an optional default. In non-interactive mode, returns the default or an error if no default is set.
func (*UI) InputWithSuffix ¶ added in v0.10.0
InputWithSuffix reads input like Input but renders an extra dimmed suffix between the default brace and the colon. Use this for hints that aren't part of the question itself — e.g. "[5] (5 OBOL ceiling):". The suffix is shown only when defaultVal is non-empty (so it stays adjacent to the "[default]" chip).
func (*UI) JSON ¶ added in v0.8.0
JSON writes v as indented JSON to the real stdout (os.Stdout). This bypasses the stderr redirect so agents always get JSON on stdout.
func (*UI) OpenBrowser ¶ added in v0.15.0
OpenBrowser opens url in the operator's browser when PlanBrowserOpen allows it and reports whether it did. It prints nothing: callers always print the URL themselves (see OpenURL for the print-and-maybe-open helper).
func (*UI) OpenURL ¶ added in v0.15.0
OpenURL always prints "label: url" and, when autoOpen is set and the environment allows it, opens the URL in a browser. Reports whether a browser was opened. Human output only — in JSON mode it lands on stderr like every other UI line, so stdout stays clean.
func (*UI) RunWithSpinner ¶
RunWithSpinner executes fn while displaying a spinner with msg.
On success: replaces spinner with "✓ msg (duration)". On failure: replaces spinner with "✗ msg", caller handles error display. In verbose mode or non-TTY: prints the message and runs fn without animation.
func (*UI) SecretInput ¶
SecretInput reads input without echoing (for API keys, passwords). In non-interactive mode, returns an error directing the user to use a flag.
func (*UI) Select ¶
Select presents a numbered list and returns the selected index. In non-interactive mode, returns the default index without prompting.
func (*UI) SetNoBrowser ¶ added in v0.15.0
SetNoBrowser disables every automatic browser open for this UI (the global --no-open flag).
func (*UI) SuggestCommand ¶
SuggestCommand prints an error for an unknown subcommand of parent and suggests similarly named sibling commands (Levenshtein distance <= 2).
func (*UI) Warn ¶
Warn prints: ! message (yellow bang, matching obolup.sh log_warn). Not suppressed by quiet mode.