ADR-016: CLI presentation follows the output stream: tables, colour and prompts for a terminal, plain data for a pipe¶
Status¶
Accepted, amends ADR-012
(a command may choose its form by stream instead of by flag),
and amended by ADR-038
(every command with a data answer follows the stream, search, recall and diary read included, and --json is global)
Context¶
Apart from status and db, every client command printed pretty JSON.
That is right for a script and poor for a person: jobs list was a wall of nested objects, a destructive command
(repair --apply, jobs cancel, auth revoke) ran the moment it was typed, and the help text was monochrome.
The same output is also read by machines.
memcastle jobs list | jq is in the quickstart, auth generate prints a token that is captured into a secret manager,
and the test-suite parses stdout.
Anything that changes what a pipe sees breaks them silently.
ADR-012 settled --json as a per-command flag for the commands with a human rendering.
Adding the same flag to jobs list would make scripts opt in to what they always had.
Decision¶
- A person gets decoration, a pipe gets plain data, and the stream decides. The decision is made from whether the stream is a terminal, never from a flag, so a script needs no change.
- Colour is on for a stream that is a terminal and wants it.
At the time,
NO_COLORandCLICOLOR=0turned it off,CLICOLOR_FORCEturned it on, andTERM=dumbwas respected. The current CLI instead supportsNO_COLORandFORCE_COLOR; see the CLI reference. stdout and stderr are decided independently. Colour surrounds words and never replaces them: with escapes stripped, a coloured report is the plain report. Help and clap's own errors use a palette from the same rules. jobs listis a table on a terminal and JSON otherwise. There is no--jsonflag on it: a pipe already gets JSON, and a person who wants JSON on a terminal can pipe tocat. The other listings (search,recall,diary read) return free text that a table would truncate, so they stay JSON everywhere.status,db startanddb statuskeep their--jsonflag, which predates this decision and is a scripting contract.- Destructive commands ask first, on a terminal only.
repair --apply,auth generate(which replaces the previous token),auth revokeandjobs cancelprompt on stderr, default to "no", and accept--yes. When stdin or stderr is not a terminal they never prompt and proceed, because a script that runsmemcastle repair --applyhas already decided. A declined prompt is the errormemcastle::cli::aborted, so a chained command stops. - Stdout stays clean.
The token printed by
auth generateis the only thing on its stdout, and its prompt and guidance go to stderr. - Completion is a subcommand,
memcastle completions <shell>, generated from the command definition. It needs no daemon and no valid configuration. Enumerated arguments (--mode,jobs list --status) are declared with their values so completion can offer them. - There is no
helpsubcommand.memcastle helpandmemcastle jobs helponly repeated--help. - One colour stack.
consoledoes the terminal detection and styling fordialoguerandcomfy-tablealike, and clap styles its own help.
Alternatives rejected¶
- A
--jsonflag onjobs list, table by default. It breaks every existing pipe until it is edited, and asks scripts to opt in to what they already had. - A global
--formator--color. ADR-012 already rejected the former for promising a human form most commands lack. The latter duplicated the colour environment settings (thenNO_COLORandCLICOLOR_FORCE). - Prompt unless
--yesis given, even without a terminal. The safest default and the one that hangs CI until a timeout kills it, or fails every script on its first run. The protection a prompt gives is against a typo at a keyboard, which is exactly the case a terminal check covers. - Tables for every listing. Search hits and recalled content are paragraphs, and a table either truncates them or wraps into noise.
- Dynamic completion that asks the daemon for job ids.
Useful, but unstable in
clap_complete, and a completion that needs a running daemon is slow and surprising when it is not. - Generating completions in
build.rs. Releases are bare executables per platform, so there is no archive to carry the files, and a build script cannot produce them for a cross-compiled target without running it.
Consequences¶
jobs liston a terminal is not stable to parse, and a script that wants JSON from a terminal session must pipe.- An interactive session that wanted to skip a confirmation must say
--yes, and a script that relied on a command executing without a question is unaffected. - A terminal narrower than the table lets the free-text column shrink first, wrapping its text without losing any of it, and then overflows, rather than breaking identifiers across lines.
- The package builds (Homebrew, deb and rpm, AUR) generate the completion files by running the released binary.
- Four new dependencies:
clap_complete,console,dialoguerandcomfy-table(withcrosstermbehind it).