Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CLI output UX & agent-friendliness

FieldValue
Date finalised2026-06-05
AuthorRômulo Febasi romulo@febasi.com.br (Claude-assisted)
ScopeIs Synapse’s CLI output well-structured and visually modern (vs Vercel/gcloud/gh/cargo)? Where are the AI-running-the-CLI affordances? Grounded in the v0.5 code.
Statusdraft
InputsAudit of src/output.rs, src/cli.rs, src/exit.rs, i18n strings; clig.dev (Command Line Interface Guidelines); 2026 “CLI for AI agents” literature.
Out of scopeThe MCP door (covered in the agent-ergonomics brief); a full TUI (ratatui is backlog SYN-203).

Executive summary

Synapse’s CLI is functionally correct and predictable but visually flat. Output goes through one clean chokepoint (src/output.rs), --format json exists, and exit codes are already sysexits-structured (better than most CLIs) (but the only colour in the whole tool is the red error: label. There are no success/warn glyphs, no bold/dim, no tables, no NO_COLOR/--no-color, no progress on TTY, and no non-interactive flag) so an agent driving the CLI in a pseudo-TTY can block on the syn init model-download prompt. The fix is cheap because the output module is centralised: add a TTY-gated styling layer (clig.dev rules), a --no-input/--quiet pair, finish --format json coverage, and emit machine-readable errors. None of this should change the data an existing script sees on a pipe.

1. Context and premisses

Audited reality (v0.5):

  • output::println/println_stderr are bare println!/eprintln!. The only styled output is print_error → red error: label, gated on stderr.is_terminal().
  • Globals: --verbose, --format text|json, --lang, --no-freshness. No --no-color, --quiet, --yes/--no-input.
  • --format json is implemented in ~13 of 27 command files (reads); writes/actions print text only.
  • ExitCode (BR-8.3): Success=0, UserError=1, InternalBug=2, Usage=64, DataError=65, IoError=74, Transient=75: sysexits-aligned, but category-level (not-found and already-exists both map to UserError=1).
  • NO_COLOR / TERM=dumb are not honoured anywhere.
  • Prompts exist in syn setup and the syn init onboarding; they gate on is_terminal() but have no flag to force non-interactive.

2. Market scan

2.1 clig.dev: colour & TTY discipline

Disable colour when: not a TTY (check stdout/stderr individually), NO_COLOR is set (non-empty), TERM=dumb, --no-color is passed, or an app-specific MYAPP_NO_COLOR is set. No animations when stdout is not a TTY (progress bars become “christmas trees” in CI logs).

What this means for Synapse: Synapse does the TTY check for one label and ignores the other four signals. A real styling layer must honour all of them in one place (the output module).

2.2 Modern CLIs: the visual bar

CapabilityVercelgcloudghcargoSynapse today
Colour + glyphs (✓/✗/▲)✓✓✓✓only red error:
Bold headings / dim secondary✓✓✓✓✗
Tables for lists-✓✓-manual spaces
Spinner/progress on TTY✓✓✓✓plain text lines
NO_COLOR/--no-color✓✓✓✓✗
--json machine output✓✓✓ (+--jq)-~half the commands
Non-interactive flag✓✓ (-q)✓✓✗
Structured exit codespartialpartialpartial✓✓ (sysexits)

What this means for Synapse: the contract basics (json, exit codes, clean output module) are ahead of the pack; the presentation (colour, glyphs, tables, progress, NO_COLOR) is well behind. The asymmetry is the story.

2.3 CLI-for-AI-agents (2026)

Agents want: deterministic, machine-readable output (--json), no ANSI / no prompts / no spinners in machine mode, distinct/branchable exit codes, machine-readable errors, and a frozen, additive-only output contract once they depend on it. The CLI is increasingly the lowest-friction door for an agent (less context than MCP tool schemas).

What this means for Synapse: the CLI is a first-class agent surface (complementary to MCP). Two real gaps: (a) no --no-input means a prompt can block an autonomous run; (b) --format json doesn’t cover writes, and errors are never JSON, so an agent parsing output gets structured reads but unstructured actions/errors.

2.4 Current output, captured (the honest picture)

Real output from the binary (v0.5 formatters):

$ syn project list
project   cliente-alfa              Cliente Alfa          ← manual space columns, no header/color/border

$ syn search alfa
person    maria-silva               Maria Silva
project   cliente-alfa              Cliente Alfa
           ## Context                                     ← snippet dumps RAW markdown + blank lines (looks broken)

$ syn person show maria-silva
---
type: person
id: maria-silva
...                                                       ← raw YAML frontmatter dump, not a styled card

$ syn graph project cliente-alfa
project/cliente-alfa
  inbound:
    <--[focal]-- person/maria-silva                       ← ASCII arrows (decent) but no unicode tree, no color

$ syn info
workspace: /private/tmp/syn-ux
  project  1                                              ← plain key:value, no total, no bar/panel

What this means for Synapse: there is no unicode/box-drawing, no tables, no colour anywhere; show dumps raw YAML; search snippets render raw markdown (## Context) and look broken; graph is the only “structured” output and it’s plain ASCII. This is functional but well below a premium (“AAA”) modern-CLI bar. Three concrete polish targets beyond the styling layer: rich rendering (unicode graph tree, entity panels/cards, clean snippets) and a premium loading/animation experience.

3. Recommendation

#BlockJustificationCostNotes
1TTY-gated styling layer (colour, ✓/✗/▲, bold/dim) honouring NO_COLOR/--no-color/TERM=dumb/SYNAPSE_NO_COLORCloses the whole visual gap in one module; clig.dev-compliantMNeeds a colour crate → ADR (anstyle/anstream, the clap/cargo stack, ~0 transitive weight)
2Non-interactive mode: --no-input (alias --yes/-y) + --quiet/-q; every prompt respects itAgents/CI must never block on a promptSThe syn init onboarding + syn setup are the prompts to gate
3--format json to 100% + machine-readable JSON errors (stderr) in json modeAgents get structured actions + errors, not just readsMToday print_error is always plain text
4Finer exit-code branching (not-found vs already-exists vs conflict)Agents branch on outcome without parsing textSKeep sysexits categories; add/Document sub-codes
5Tables + visual structure for list/search/info/doctor on TTYThe human-readability win; plain columns on pipeMLight table crate or hand-rolled; gate on TTY
6TTY progress (spinner/bar) for embed --all/reindex/watch; plain lines off-TTYLong ops feel alive; clig.dev: no animation off-TTYSindicatif or minimal; keep the current plain stream for pipes

Non-goals: a full TUI (SYN-203 backlog); colour in JSON mode (never); changing the data a piped consumer already parses.

4. Disruptive bets

  1. --no-input as a first-class “agent mode” that also flips output to compact + no-colour + no-progress in one switch (a SYNAPSE_AGENT=1 env too), the single flag an autonomous runner sets.
  2. A documented, frozen CLI output contract (alongside the v1.0 frozen MCP surface): agents can depend on field names + exit codes; changes are additive-only. This is a trust feature, not just hygiene.

5. Ticket sketch (provisional IDs)

Sketch IDBlockSize
SYN-186TTY-gated styling layer + NO_COLOR/–no-color/TERM compliance (ADR for colour crate)M
SYN-187--no-input/--yes + --quiet; prompts respect it (agent/CI never blocks)S
SYN-188--format json 100% coverage + JSON errors + frozen output contractM
SYN-189Finer exit-code branching for agents (not-found/already-exists/conflict)S
SYN-190Tables + structured layout for list/search/info/doctor on TTYM
SYN-191TTY spinner/progress for embed –all / reindex / watchS
SYN-192Premium loading & animation experience (branded spinners, multi-phase bars, polished completion): AAA tierM
SYN-193Rich rendering: unicode graph tree (├─└─), entity panels/cards for show, clean search snippets (strip raw markdown)M
SYN-195Rendering opt-out contract: default AAA/rich; --plain/--raw + SYNAPSE_PLAIN for raw/token-economy; single is_rich() gateS

6. Risk register

RiskSeverityMitigation
Colour crate = new depLowanstyle/anstream are the clap stack (already transitively present); still write an ADR
Styling leaks into pipes/JSON, breaking scriptsHighSingle chokepoint; gate on TTY + NO_COLOR; never style JSON; snapshot-test plain output
Output-field changes break agentsMedFreeze the contract (block 3); additive-only after v1.0

7. Open decisions

  1. Colour crate: anstyle (style only, BYO writer) vs anstream (auto-stripping stream)? Recommend anstream (handles the strip-on-pipe rule for free).
  2. --no-input default in non-TTY: should the absence of a TTY imply --no-input (recommended) or still require the flag? (Recommend: non-TTY implies it; flag forces it even on a TTY.)
  3. Tables: add a crate (comfy-table/tabled) or hand-roll? (Hand-roll is dep-light but more code.)

8. References