Documentation
¶
Overview ¶
Package ui is hmm's Charm layer — every terminal write in the binary goes through it (the rule crush gives its TUI, scaled to an enriched CLI):
theme.go one palette → lipgloss styles + the glamour style tree markdown.go glamour renderers, memoized per style+width stream.go incremental markdown: emit blocks once provably complete spinner.go the loader: ticker-driven, pausable, atomic Line() console.go Console — the facade all output goes through turn.go Turn — weft events in, rendered terminal out
All of it is TTY-gated: piped output stays byte-clean (principle 2), and the full-screen TUI remains a later phase.
Index ¶
- func Compact(s string, n int) string
- func Markdown(text string, styled bool) string
- func MarkdownStyle(dark bool) ansi.StyleConfig
- type Console
- func (c *Console) AnswerText(text string)
- func (c *Console) Banner(provider, model, what string)
- func (c *Console) Dimf(format string, a ...any)
- func (c *Console) Err() io.Writer
- func (c *Console) Errorf(format string, a ...any)
- func (c *Console) Feed(line string)
- func (c *Console) Hint(s string)
- func (c *Console) JSON() bool
- func (c *Console) JSONLine(v any)
- func (c *Console) Mark(s string) string
- func (c *Console) Markf(format string, a ...any)
- func (c *Console) Newline()
- func (c *Console) Outf(format string, a ...any)
- func (c *Console) Outln(s string)
- func (c *Console) PauseSpin()
- func (c *Console) ResumeSpin()
- func (c *Console) SetMode(jsonMode, quiet bool)
- func (c *Console) StartSpin()
- func (c *Console) Status(s string)
- func (c *Console) Stdout() io.Writer
- func (c *Console) StopSpin()
- func (c *Console) Turn() *Turn
- func (c *Console) TurnPrompt()
- func (c *Console) Usage(s string)
- type Spinner
- type Theme
- type Turn
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Compact ¶
Compact compacts s to one line of at most n runes, for the activity feed and spinner suffixes. Control bytes are dropped first: tool and plugin output is model-authored and can carry ANSI escapes that would garble the terminal (or embed control bytes in piped logs).
func Markdown ¶
Markdown renders an answer with glamour. styled=false (a pipe, or NO_COLOR) returns the text untouched — redirection must stay raw.
func MarkdownStyle ¶
func MarkdownStyle(dark bool) ansi.StyleConfig
MarkdownStyle builds the glamour side of the palette: the standard dark (or light) tree with hmm's colors on the elements that carry identity — headings, links, code, quotes — so answers match the feed.
Types ¶
type Console ¶
type Console struct {
// contains filtered or unexported fields
}
Console is the facade all hmm terminal output goes through (the layer crush gives its whole TUI; hmm is an enriched CLI, so the facade is smaller but the rule is the same: no package outside ui writes to the streams). It owns the TTY gates, the themes (one per stream, so color follows the right pipe), the spinner, and the json/quiet modes; every method degrades to byte-clean plain output on a non-TTY writer automatically (principle 2).
func NewConsole ¶
NewConsole binds the console to hmm's streams. stderr is serialized internally — callers hand their writers over once, here.
func (*Console) AnswerText ¶
AnswerText streams raw text to stdout (the pipe path).
func (*Console) Banner ¶
Banner prints the conversation-mode banner (TTY only — the caller gates on stdin being interactive).
func (*Console) Err ¶
Err returns the serialized stderr writer — for the few external sinks (the flag package's output) that must interleave cleanly.
func (*Console) Errorf ¶
Errorf prints a "hmm: …" failure line to stderr. On a pipe the bytes are exactly the plain form logs have always had.
func (*Console) Feed ¶
Feed prints one permanent activity-feed line above the spinner, atomically. Suppressed by --quiet and --json.
func (*Console) JSONLine ¶
JSONLine writes one NDJSON event to stdout; encode failures land on stderr like every other warning.
func (*Console) Mark ¶
Mark styles an inline stdout fragment (the current-model "►" row) — violet on a TTY, untouched bytes elsewhere.
func (*Console) Markf ¶
Markf renders a highlighted stdout line (the config header) — violet on a TTY, plain bytes elsewhere.
func (*Console) Newline ¶
func (c *Console) Newline()
Newline adds the trailing newline a streamed answer may be missing.
func (*Console) Outf ¶
Outf writes plain stdout text (subcommand tables); on a TTY the companion themed helpers below add color without changing layout.
func (*Console) PauseSpin ¶
func (c *Console) PauseSpin()
PauseSpin holds the spinner's line free (the y/N gate); nil-safe.
func (*Console) ResumeSpin ¶
func (c *Console) ResumeSpin()
ResumeSpin repaints after PauseSpin; nil-safe.
func (*Console) SetMode ¶
SetMode installs the parsed --json/--quiet; both change what the feed and spinner do, so they live here rather than branching at every call site.
func (*Console) StartSpin ¶
func (c *Console) StartSpin()
StartSpin begins the loader: stderr TTY, feed not quieted, not json.
func (*Console) Status ¶
Status swaps the spinner's suffix ("thinking · …", "step 2 · …"). Suppressed by --quiet and --json; store-only, so per-token updates cost a mutex and a pointer store.
func (*Console) Stdout ¶
Stdout returns the stdout writer — the answer's stream, used by the Turn renderer.
func (*Console) StopSpin ¶
func (c *Console) StopSpin()
StopSpin erases the loader and ends its goroutine.
func (*Console) TurnPrompt ¶
func (c *Console) TurnPrompt()
TurnPrompt prints the conversation's "› " turn glyph (no newline).
type Spinner ¶
type Spinner struct {
// contains filtered or unexported fields
}
Spinner drives bubbles' spinner on one line without bubbletea: a goroutine ticks frames at the spinner's FPS and redraws "frame suffix". The drawing invariants, learned the hard way:
- a paused spinner never redraws — Pause means the line is free for permanent output, and the old loop redrew over it anyway within one frame (the garbled-feed bug);
- Line() prints a permanent line atomically (erase → write → redraw under one lock) — callers must not compose Pause, print, Resume themselves, that composition is inherently racy;
- every draw erases the whole line first ("\r\x1b[K") — padding math over ANSI-styled bytes leaves stale tails;
- the loop selects on done and a ticker, so Stop returns promptly instead of blocking out the last frame (~143ms per turn).
A nil *Spinner (or a non-TTY writer) is a no-op — callers never branch.
func StartSpinner ¶
StartSpinner begins the loader; on a non-TTY it returns nil (a safe no-op), so pipes see nothing. The Meter's stock 7 FPS reads as a stutter, so it runs at 12.
func (*Spinner) Line ¶
Line prints one permanent line above the spinner, atomically: the erase, the write, and the redraw happen under a single lock, so no frame can interleave. A paused spinner stays paused; a stopped one has no live loop left to refresh or erase the frame, so the line prints without the repaint.
func (*Spinner) Pause ¶
func (s *Spinner) Pause()
Pause erases the loader and holds the line free for output of unknown length (the y/N gate reads stdin while paused); Resume repaints immediately.
type Theme ¶
type Theme struct {
// contains filtered or unexported fields
}
Theme holds the styles, bound to the feed writer so color detection follows the right stream: a renderer over a pipe yields no color and plain glyphs, which keeps logs (and tests) clean.
func (*Theme) ErrorHeader ¶
ErrorHeader styles "hmm: …" failure lines.
func (*Theme) ThinkingPrefix ¶
ThinkingPrefix marks the reasoning stream.
type Turn ¶
type Turn struct {
// contains filtered or unexported fields
}
Turn is the renderer for one agent run: it maps weft's event stream to the terminal — the stderr activity feed, the spinner status, the streamed or glamour-rendered answer, the --json event lines — so the CLI layer only pumps events; every presentation decision lives here (turn() in internal/cli used to be this, half the file).
On a TTY stdout the answer streams as completed markdown blocks (mdStream); on a pipe it streams raw per-delta, byte-clean.
func (*Turn) Event ¶
Event renders one weft event; unknown events are ignored (the wire set is additive).