detached f5a88db

style(cli): restyle help output

Makes zot --help use zot's TUI palette and section layout, pads columns consistently across terminals, expands section rules to terminal width, and adds extension install guidance in the help output.
commit
f5a88db223dbf5554b6a9e6cb1b307c6856c92bd
author
patriceckhart · · 2026-04-22 19:03 UTC
parent
a5fad05
signature
unsigned
refs
tag: v0.0.96
1 file · +112 -66
M internal/agent/args.go +112 -66
@@ -4,6 +4,9 @@ import (
44 "fmt"
55 "os"
66 "strings"
7+
8+ "github.com/patriceckhart/zot/internal/tui"
9+ "golang.org/x/term"
710 )
811
912 // Mode is the CLI run mode.
@@ -238,73 +241,116 @@ func ParseArgs(in []string) (Args, error) {
238241 return a, nil
239242 }
240243
241-// PrintHelp writes the help text to w (stderr by default).
244+// PrintHelp writes the help text to stderr. When stderr is a TTY it
245+// uses the same palette as zot's TUI; when redirected it falls back to
246+// plain text with no ANSI escapes.
242247 func PrintHelp(version string) {
243- fmt.Fprintf(os.Stderr, `zot %s — Yet another coding agent harness.
244-
245-usage:
246- zot interactive tui
247- zot "prompt" interactive, pre-filled prompt
248- zot -p "prompt" print final text, exit
249- zot --json "prompt" newline-delimited json events, exit
250- zot rpc json-rpc loop on stdin/stdout (see docs/rpc.md)
251- zot ext help extension manager help
252- zot ext list list installed extensions
253- zot ext install <path|url> install an extension into $ZOT_HOME/extensions/
254- zot telegram-bot setup configure a telegram bot (from BotFather)
255- zot telegram-bot run foreground bridge (ctrl+c to stop)
256- zot telegram-bot start background bridge (detached)
257- zot telegram-bot stop stop the background bridge
258- zot telegram-bot logs [-f] tail the background bridge's log
259- zot telegram-bot status config + running state
260- zot telegram-bot reset forget saved token
261- (short alias: zot tg ...)
262-
263-flags:
264- --provider anthropic|openai
265- --model model id (see --list-models)
266- --api-key api key for this run (falls back to env and auth.json)
267- --base-url override provider api base url (for testing/self-hosted)
268-
269- --system-prompt TEXT replace the default system prompt
270- --append-system-prompt TEXT append to the system prompt (repeatable)
271- --reasoning low|medium|high enable reasoning on supported models
272-
273- -c, --continue continue the most recent session for this cwd
274- -r, --resume pick a session to resume
275- --session PATH resume a specific session file
276- --no-session do not read or write a session file
277-
278- --cwd PATH treat PATH as the working directory
279- --no-tools disable all tools
280- --tools csv only enable the listed tools
281- -e, --ext PATH load an extension from PATH for this run
282- (repeatable; takes precedence over
283- installed extensions of the same name)
284- --no-ext skip extension discovery for this run
285- (--ext PATH still works on top, so
286- --no-ext --ext ./x runs only x)
287- --no-skill skip skill discovery for this run,
288- including built-in skills (no skill
289- tool, no Available skills manifest)
290- --with-skills load user-installed skills from
291- $ZOT_HOME/skills/ + .zot/skills/ +
292- .claude/skills/ + .agents/skills/.
293- default: only built-in skills load
294-
295- --no-yolo ask before running every tool call
296- (interactive tui only; ignored with
297- a stderr warning in -p / --json / rpc)
248+ th := tui.Dark
249+ fd := int(os.Stderr.Fd())
250+ useColor := term.IsTerminal(fd)
251+ style := func(c int, s string) string {
252+ if !useColor {
253+ return s
254+ }
255+ return th.FG256(c, s)
256+ }
257+ assistant := func(s string) string { return style(th.Assistant, s) }
258+ muted := func(s string) string { return style(th.Muted, s) }
259+ fg := func(s string) string { return style(th.FG, s) }
260+ width := 96
261+ if useColor {
262+ if w, _, err := term.GetSize(fd); err == nil && w > 20 {
263+ width = w
264+ }
265+ }
266+ ruleWidth := width
267+ if ruleWidth < 40 {
268+ ruleWidth = 40
269+ }
270+ rule := strings.Repeat("─", ruleWidth)
271+ if useColor {
272+ rule = muted(rule)
273+ }
274+ leftW := 34
275+ if width >= 120 {
276+ leftW = 40
277+ }
278+ if width >= 140 {
279+ leftW = 46
280+ }
281+ type row struct{ left, right string }
282+ section := func(title string, rows ...row) {
283+ fmt.Fprintln(os.Stderr)
284+ fmt.Fprintln(os.Stderr, assistant(title))
285+ fmt.Fprintln(os.Stderr, rule)
286+ for _, r := range rows {
287+ left := r.left
288+ if len([]rune(left)) < leftW {
289+ left += strings.Repeat(" ", leftW-len([]rune(left)))
290+ }
291+ fmt.Fprintf(os.Stderr, " %s %s\n", fg(left), muted(r.right))
292+ }
293+ }
298294
299- --max-steps N agent loop iteration cap (default 50)
300- --list-models print known models and exit
301- -h, --help this message
302- -v, --version version info
295+ fmt.Fprintln(os.Stderr)
296+ fmt.Fprintln(os.Stderr, assistant(tui.Bold("▌ i'm zot. yet another coding agent harness.")))
297+ fmt.Fprintln(os.Stderr, muted("ask anything, or type /help inside the tui to see commands."))
298+ fmt.Fprintf(os.Stderr, "%s %s\n", muted("version:"), fg(version))
303299
304-extensions:
305- zot ext install ./path/to/ext install a local extension
306- zot ext install https://...git clone + install from git
307- zot --ext ./path/to/ext load an extension for this run only
308- zot ext help show all extension subcommands
309-`, version)
300+ section("modes",
301+ row{"zot", "interactive tui"},
302+ row{"zot \"prompt\"", "interactive, pre-filled prompt"},
303+ row{"zot -p \"prompt\"", "print final text, exit"},
304+ row{"zot --json \"prompt\"", "newline-delimited json events, exit"},
305+ row{"zot rpc", "json-rpc loop on stdin/stdout (see docs/rpc.md)"},
306+ )
307+ section("extensions",
308+ row{"zot ext list", "list installed extensions"},
309+ row{"zot ext install <path|url>", "install into $ZOT_HOME/extensions/"},
310+ row{"zot --ext ./path/to/ext", "load an extension for this run only"},
311+ row{"zot ext help", "show all extension subcommands"},
312+ )
313+ section("telegram",
314+ row{"zot telegram-bot setup", "configure a telegram bot (from BotFather)"},
315+ row{"zot telegram-bot run", "foreground bridge (ctrl+c to stop)"},
316+ row{"zot telegram-bot start", "background bridge (detached)"},
317+ row{"zot telegram-bot stop", "stop the background bridge"},
318+ row{"zot telegram-bot logs [-f]", "tail the background bridge log"},
319+ row{"zot telegram-bot status", "config + running state"},
320+ row{"zot telegram-bot reset", "forget saved token"},
321+ row{"zot tg ...", "short alias for telegram-bot"},
322+ )
323+ section("provider and model flags",
324+ row{"--provider", "provider to use (anthropic|openai)"},
325+ row{"--model ID", "model id (see --list-models)"},
326+ row{"--api-key KEY", "api key for this run (env / auth.json fallback)"},
327+ row{"--base-url URL", "override provider api base url"},
328+ row{"--reasoning low|medium|high", "enable reasoning on supported models"},
329+ )
330+ section("prompt and session flags",
331+ row{"--system-prompt TEXT", "replace the default system prompt"},
332+ row{"--append-system-prompt TEXT", "append to the system prompt (repeatable)"},
333+ row{"-c, --continue", "continue the most recent session for this cwd"},
334+ row{"-r, --resume", "pick a session to resume"},
335+ row{"--session PATH", "resume a specific session file"},
336+ row{"--no-session", "do not read or write a session file"},
337+ )
338+ section("workspace, tools, skills",
339+ row{"--cwd PATH", "treat PATH as the working directory"},
340+ row{"--no-tools", "disable all tools"},
341+ row{"--tools csv", "only enable the listed tools"},
342+ row{"--no-yolo", "ask before running every tool call"},
343+ row{"--no-ext", "skip extension discovery for this run"},
344+ row{"--no-skill", "skip all skill discovery for this run"},
345+ row{"--with-skills", "load user-installed skills in addition to built-ins"},
346+ )
347+ section("misc",
348+ row{"--max-steps N", "agent loop iteration cap (default 50)"},
349+ row{"--list-models", "print known models and exit"},
350+ row{"-h, --help", "show this help"},
351+ row{"-v, --version", "show version info"},
352+ )
353+ fmt.Fprintln(os.Stderr)
354+ fmt.Fprintln(os.Stderr, assistant("see also: docs/extensions.md, docs/rpc.md, docs/skills.md"))
355+ fmt.Fprintln(os.Stderr)
310356 }
@@ -4,6 +4,9 @@ import (
4 "fmt"4 "fmt"
5 "os"5 "os"
6 "strings"6 "strings"
7+
8+ "github.com/patriceckhart/zot/internal/tui"
9+ "golang.org/x/term"
7 )10 )
8 11
9 // Mode is the CLI run mode.12 // Mode is the CLI run mode.
@@ -238,73 +241,116 @@ func ParseArgs(in []string) (Args, error) {
238 return a, nil241 return a, nil
239 }242 }
240 243
241-// PrintHelp writes the help text to w (stderr by default).244+// PrintHelp writes the help text to stderr. When stderr is a TTY it
245+// uses the same palette as zot's TUI; when redirected it falls back to
246+// plain text with no ANSI escapes.
242 func PrintHelp(version string) {247 func PrintHelp(version string) {
243- fmt.Fprintf(os.Stderr, `zot %s — Yet another coding agent harness.248+ th := tui.Dark
244-249+ fd := int(os.Stderr.Fd())
245-usage:250+ useColor := term.IsTerminal(fd)
246- zot interactive tui251+ style := func(c int, s string) string {
247- zot "prompt" interactive, pre-filled prompt252+ if !useColor {
248- zot -p "prompt" print final text, exit253+ return s
249- zot --json "prompt" newline-delimited json events, exit254+ }
250- zot rpc json-rpc loop on stdin/stdout (see docs/rpc.md)255+ return th.FG256(c, s)
251- zot ext help extension manager help256+ }
252- zot ext list list installed extensions257+ assistant := func(s string) string { return style(th.Assistant, s) }
253- zot ext install <path|url> install an extension into $ZOT_HOME/extensions/258+ muted := func(s string) string { return style(th.Muted, s) }
254- zot telegram-bot setup configure a telegram bot (from BotFather)259+ fg := func(s string) string { return style(th.FG, s) }
255- zot telegram-bot run foreground bridge (ctrl+c to stop)260+ width := 96
256- zot telegram-bot start background bridge (detached)261+ if useColor {
257- zot telegram-bot stop stop the background bridge262+ if w, _, err := term.GetSize(fd); err == nil && w > 20 {
258- zot telegram-bot logs [-f] tail the background bridge's log263+ width = w
259- zot telegram-bot status config + running state264+ }
260- zot telegram-bot reset forget saved token265+ }
261- (short alias: zot tg ...)266+ ruleWidth := width
262-267+ if ruleWidth < 40 {
263-flags:268+ ruleWidth = 40
264- --provider anthropic|openai269+ }
265- --model model id (see --list-models)270+ rule := strings.Repeat("─", ruleWidth)
266- --api-key api key for this run (falls back to env and auth.json)271+ if useColor {
267- --base-url override provider api base url (for testing/self-hosted)272+ rule = muted(rule)
268-273+ }
269- --system-prompt TEXT replace the default system prompt274+ leftW := 34
270- --append-system-prompt TEXT append to the system prompt (repeatable)275+ if width >= 120 {
271- --reasoning low|medium|high enable reasoning on supported models276+ leftW = 40
272-277+ }
273- -c, --continue continue the most recent session for this cwd278+ if width >= 140 {
274- -r, --resume pick a session to resume279+ leftW = 46
275- --session PATH resume a specific session file280+ }
276- --no-session do not read or write a session file281+ type row struct{ left, right string }
277-282+ section := func(title string, rows ...row) {
278- --cwd PATH treat PATH as the working directory283+ fmt.Fprintln(os.Stderr)
279- --no-tools disable all tools284+ fmt.Fprintln(os.Stderr, assistant(title))
280- --tools csv only enable the listed tools285+ fmt.Fprintln(os.Stderr, rule)
281- -e, --ext PATH load an extension from PATH for this run286+ for _, r := range rows {
282- (repeatable; takes precedence over287+ left := r.left
283- installed extensions of the same name)288+ if len([]rune(left)) < leftW {
284- --no-ext skip extension discovery for this run289+ left += strings.Repeat(" ", leftW-len([]rune(left)))
285- (--ext PATH still works on top, so290+ }
286- --no-ext --ext ./x runs only x)291+ fmt.Fprintf(os.Stderr, " %s %s\n", fg(left), muted(r.right))
287- --no-skill skip skill discovery for this run,292+ }
288- including built-in skills (no skill293+ }
289- tool, no Available skills manifest)
290- --with-skills load user-installed skills from
291- $ZOT_HOME/skills/ + .zot/skills/ +
292- .claude/skills/ + .agents/skills/.
293- default: only built-in skills load
294-
295- --no-yolo ask before running every tool call
296- (interactive tui only; ignored with
297- a stderr warning in -p / --json / rpc)
298 294
299- --max-steps N agent loop iteration cap (default 50)295+ fmt.Fprintln(os.Stderr)
300- --list-models print known models and exit296+ fmt.Fprintln(os.Stderr, assistant(tui.Bold("▌ i'm zot. yet another coding agent harness.")))
301- -h, --help this message297+ fmt.Fprintln(os.Stderr, muted("ask anything, or type /help inside the tui to see commands."))
302- -v, --version version info298+ fmt.Fprintf(os.Stderr, "%s %s\n", muted("version:"), fg(version))
303 299
304-extensions:300+ section("modes",
305- zot ext install ./path/to/ext install a local extension301+ row{"zot", "interactive tui"},
306- zot ext install https://...git clone + install from git302+ row{"zot \"prompt\"", "interactive, pre-filled prompt"},
307- zot --ext ./path/to/ext load an extension for this run only303+ row{"zot -p \"prompt\"", "print final text, exit"},
308- zot ext help show all extension subcommands304+ row{"zot --json \"prompt\"", "newline-delimited json events, exit"},
309-`, version)305+ row{"zot rpc", "json-rpc loop on stdin/stdout (see docs/rpc.md)"},
306+ )
307+ section("extensions",
308+ row{"zot ext list", "list installed extensions"},
309+ row{"zot ext install <path|url>", "install into $ZOT_HOME/extensions/"},
310+ row{"zot --ext ./path/to/ext", "load an extension for this run only"},
311+ row{"zot ext help", "show all extension subcommands"},
312+ )
313+ section("telegram",
314+ row{"zot telegram-bot setup", "configure a telegram bot (from BotFather)"},
315+ row{"zot telegram-bot run", "foreground bridge (ctrl+c to stop)"},
316+ row{"zot telegram-bot start", "background bridge (detached)"},
317+ row{"zot telegram-bot stop", "stop the background bridge"},
318+ row{"zot telegram-bot logs [-f]", "tail the background bridge log"},
319+ row{"zot telegram-bot status", "config + running state"},
320+ row{"zot telegram-bot reset", "forget saved token"},
321+ row{"zot tg ...", "short alias for telegram-bot"},
322+ )
323+ section("provider and model flags",
324+ row{"--provider", "provider to use (anthropic|openai)"},
325+ row{"--model ID", "model id (see --list-models)"},
326+ row{"--api-key KEY", "api key for this run (env / auth.json fallback)"},
327+ row{"--base-url URL", "override provider api base url"},
328+ row{"--reasoning low|medium|high", "enable reasoning on supported models"},
329+ )
330+ section("prompt and session flags",
331+ row{"--system-prompt TEXT", "replace the default system prompt"},
332+ row{"--append-system-prompt TEXT", "append to the system prompt (repeatable)"},
333+ row{"-c, --continue", "continue the most recent session for this cwd"},
334+ row{"-r, --resume", "pick a session to resume"},
335+ row{"--session PATH", "resume a specific session file"},
336+ row{"--no-session", "do not read or write a session file"},
337+ )
338+ section("workspace, tools, skills",
339+ row{"--cwd PATH", "treat PATH as the working directory"},
340+ row{"--no-tools", "disable all tools"},
341+ row{"--tools csv", "only enable the listed tools"},
342+ row{"--no-yolo", "ask before running every tool call"},
343+ row{"--no-ext", "skip extension discovery for this run"},
344+ row{"--no-skill", "skip all skill discovery for this run"},
345+ row{"--with-skills", "load user-installed skills in addition to built-ins"},
346+ )
347+ section("misc",
348+ row{"--max-steps N", "agent loop iteration cap (default 50)"},
349+ row{"--list-models", "print known models and exit"},
350+ row{"-h, --help", "show this help"},
351+ row{"-v, --version", "show version info"},
352+ )
353+ fmt.Fprintln(os.Stderr)
354+ fmt.Fprintln(os.Stderr, assistant("see also: docs/extensions.md, docs/rpc.md, docs/skills.md"))
355+ fmt.Fprintln(os.Stderr)
310 }356 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close