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 ( | |||
| 4 | 4 | "fmt" | |
| 5 | 5 | "os" | |
| 6 | 6 | "strings" | |
| 7 | + | ||
| 8 | + | "github.com/patriceckhart/zot/internal/tui" | |
| 9 | + | "golang.org/x/term" | |
| 7 | 10 | ) | |
| 8 | 11 | ||
| 9 | 12 | // Mode is the CLI run mode. | |
| @@ -238,73 +241,116 @@ func ParseArgs(in []string) (Args, error) { | |||
| 238 | 241 | 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 | 247 | 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 | + | } | |
| 298 | 294 | ||
| 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)) | |
| 303 | 299 | ||
| 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) | |
| 310 | 356 | } | |
| @@ -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, nil | 241 | 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 tui | 251 | + | style := func(c int, s string) string { |
| 247 | - | zot "prompt" interactive, pre-filled prompt | 252 | + | if !useColor { |
| 248 | - | zot -p "prompt" print final text, exit | 253 | + | return s |
| 249 | - | zot --json "prompt" newline-delimited json events, exit | 254 | + | } |
| 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 help | 256 | + | } |
| 252 | - | zot ext list list installed extensions | 257 | + | 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 bridge | 262 | + | if w, _, err := term.GetSize(fd); err == nil && w > 20 { |
| 258 | - | zot telegram-bot logs [-f] tail the background bridge's log | 263 | + | width = w |
| 259 | - | zot telegram-bot status config + running state | 264 | + | } |
| 260 | - | zot telegram-bot reset forget saved token | 265 | + | } |
| 261 | - | (short alias: zot tg ...) | 266 | + | ruleWidth := width |
| 262 | - | 267 | + | if ruleWidth < 40 { | |
| 263 | - | flags: | 268 | + | ruleWidth = 40 |
| 264 | - | --provider anthropic|openai | 269 | + | } |
| 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 prompt | 274 | + | 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 models | 276 | + | leftW = 40 |
| 272 | - | 277 | + | } | |
| 273 | - | -c, --continue continue the most recent session for this cwd | 278 | + | if width >= 140 { |
| 274 | - | -r, --resume pick a session to resume | 279 | + | leftW = 46 |
| 275 | - | --session PATH resume a specific session file | 280 | + | } |
| 276 | - | --no-session do not read or write a session file | 281 | + | type row struct{ left, right string } |
| 277 | - | 282 | + | section := func(title string, rows ...row) { | |
| 278 | - | --cwd PATH treat PATH as the working directory | 283 | + | fmt.Fprintln(os.Stderr) |
| 279 | - | --no-tools disable all tools | 284 | + | fmt.Fprintln(os.Stderr, assistant(title)) |
| 280 | - | --tools csv only enable the listed tools | 285 | + | fmt.Fprintln(os.Stderr, rule) |
| 281 | - | -e, --ext PATH load an extension from PATH for this run | 286 | + | for _, r := range rows { |
| 282 | - | (repeatable; takes precedence over | 287 | + | left := r.left |
| 283 | - | installed extensions of the same name) | 288 | + | if len([]rune(left)) < leftW { |
| 284 | - | --no-ext skip extension discovery for this run | 289 | + | left += strings.Repeat(" ", leftW-len([]rune(left))) |
| 285 | - | (--ext PATH still works on top, so | 290 | + | } |
| 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 skill | 293 | + | } |
| 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 exit | 296 | + | fmt.Fprintln(os.Stderr, assistant(tui.Bold("▌ i'm zot. yet another coding agent harness."))) |
| 301 | - | -h, --help this message | 297 | + | fmt.Fprintln(os.Stderr, muted("ask anything, or type /help inside the tui to see commands.")) |
| 302 | - | -v, --version version info | 298 | + | 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 extension | 301 | + | row{"zot", "interactive tui"}, |
| 306 | - | zot ext install https://...git clone + install from git | 302 | + | row{"zot \"prompt\"", "interactive, pre-filled prompt"}, |
| 307 | - | zot --ext ./path/to/ext load an extension for this run only | 303 | + | row{"zot -p \"prompt\"", "print final text, exit"}, |
| 308 | - | zot ext help show all extension subcommands | 304 | + | 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 | } | ||