CLI output UX & agent-friendliness
| Field | Value |
|---|---|
| Date finalised | 2026-06-05 |
| Author | Rômulo Febasi romulo@febasi.com.br (Claude-assisted) |
| Scope | Is 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. |
| Status | draft |
| Inputs | Audit 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 scope | The 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_stderrare bareprintln!/eprintln!. The only styled output isprint_error→ rederror:label, gated onstderr.is_terminal().- Globals:
--verbose,--format text|json,--lang,--no-freshness. No--no-color,--quiet,--yes/--no-input. --format jsonis 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 toUserError=1).NO_COLOR/TERM=dumbare not honoured anywhere.- Prompts exist in
syn setupand thesyn initonboarding; they gate onis_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
| Capability | Vercel | gcloud | gh | cargo | Synapse 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 codes | partial | partial | partial | ✓ | ✓ (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
| # | Block | Justification | Cost | Notes |
|---|---|---|---|---|
| 1 | TTY-gated styling layer (colour, ✓/✗/▲, bold/dim) honouring NO_COLOR/--no-color/TERM=dumb/SYNAPSE_NO_COLOR | Closes the whole visual gap in one module; clig.dev-compliant | M | Needs a colour crate → ADR (anstyle/anstream, the clap/cargo stack, ~0 transitive weight) |
| 2 | Non-interactive mode: --no-input (alias --yes/-y) + --quiet/-q; every prompt respects it | Agents/CI must never block on a prompt | S | The syn init onboarding + syn setup are the prompts to gate |
| 3 | --format json to 100% + machine-readable JSON errors (stderr) in json mode | Agents get structured actions + errors, not just reads | M | Today print_error is always plain text |
| 4 | Finer exit-code branching (not-found vs already-exists vs conflict) | Agents branch on outcome without parsing text | S | Keep sysexits categories; add/Document sub-codes |
| 5 | Tables + visual structure for list/search/info/doctor on TTY | The human-readability win; plain columns on pipe | M | Light table crate or hand-rolled; gate on TTY |
| 6 | TTY progress (spinner/bar) for embed --all/reindex/watch; plain lines off-TTY | Long ops feel alive; clig.dev: no animation off-TTY | S | indicatif 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
--no-inputas a first-class “agent mode” that also flips output to compact + no-colour + no-progress in one switch (aSYNAPSE_AGENT=1env too), the single flag an autonomous runner sets.- 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 ID | Block | Size |
|---|---|---|
| SYN-186 | TTY-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 contract | M |
| SYN-189 | Finer exit-code branching for agents (not-found/already-exists/conflict) | S |
| SYN-190 | Tables + structured layout for list/search/info/doctor on TTY | M |
| SYN-191 | TTY spinner/progress for embed –all / reindex / watch | S |
| SYN-192 | Premium loading & animation experience (branded spinners, multi-phase bars, polished completion): AAA tier | M |
| SYN-193 | Rich rendering: unicode graph tree (├─└─), entity panels/cards for show, clean search snippets (strip raw markdown) | M |
| SYN-195 | Rendering opt-out contract: default AAA/rich; --plain/--raw + SYNAPSE_PLAIN for raw/token-economy; single is_rich() gate | S |
6. Risk register
| Risk | Severity | Mitigation |
|---|---|---|
| Colour crate = new dep | Low | anstyle/anstream are the clap stack (already transitively present); still write an ADR |
| Styling leaks into pipes/JSON, breaking scripts | High | Single chokepoint; gate on TTY + NO_COLOR; never style JSON; snapshot-test plain output |
| Output-field changes break agents | Med | Freeze the contract (block 3); additive-only after v1.0 |
7. Open decisions
- Colour crate:
anstyle(style only, BYO writer) vsanstream(auto-stripping stream)? Recommendanstream(handles the strip-on-pipe rule for free). --no-inputdefault 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.)- Tables: add a crate (
comfy-table/tabled) or hand-roll? (Hand-roll is dep-light but more code.)
8. References
- Command Line Interface Guidelines (clig.dev): colour, TTY, NO_COLOR, animation rules.
- NO_COLOR standard
- “You need to rewrite your CLI for AI agents” · “Designing a CLI for AI agents” (Arcjet)
- Code:
src/output.rs,src/cli.rs,src/exit.rs.