ADR-038: Every command with a data answer is readable in a terminal and JSON in a pipe, and --json forces JSON¶
Status¶
Accepted.
Amends ADR-016 (the listings that stayed JSON everywhere, and the
absence of a flag),
ADR-012 (status follows the stream, and --json becomes
global) and ADR-015 (db start and db status follow it too).
Context¶
ADR-016 gave job list, the palace hierarchy and the source commands a rendering for a person and JSON for a pipe,
but it left the rest as it was.
memcastle mine . therefore still printed the whole job as JSON in a terminal,
as did audit, embed, extract, repair, checkpoint, every job subcommand, search, recall, wake-up, diary,
migrate and daemon stop.
status, db and integration went the other way: they printed text even in a pipe, with a --json flag to opt out.
The result was three contracts for one binary, and a person had to know which one a command followed.
memcastle mine . is the first thing a new user runs, and it answered with a page of fields in which the one the user
needs next, the job's id, is somewhere in the middle.
Decision¶
- One rule for every command with a data answer: a readable rendering when standard output is a terminal, and JSON otherwise. A pipe or a file never gets decoration, and a script never has to ask for JSON.
--jsonis a global flag that forces JSON on a terminal. It is accepted before or after any subcommand and replaces the per-command flags ofstatus,db start,db statusandintegration, which keep working in the same position. It never changes an exit code:statusstill exits0,1or3.- The rule has one implementation.
term::pretty()answers it from the flag and the stream (term::is_prettyis the pure form that tests cover), andprint_for_terminal_or_jsoninmain.rsis the one place a command with a renderer prints. A renderer is a function from the daemon's answer to aStringinclient/, so the CLI still has no logic that MCP or HTTP could not reuse. - A job has a card.
mine,audit,embed,extract,repair,checkpointandjob demoprint the queued job as a few labelled lines and the command that follows it (memcastle job show <id>).job showprints the same card with its timestamps, error and result.job pause,resume,cancelandretryprint one sentence that says a pause or a cancel is a request. - Retrieval is excerpted in a terminal.
searchandrecallprint a numbered block per hit, with its score, where it came from and the first lines of its content, because a hit points at a drawer anddrawer showand the JSON hold the whole of it.diary readprints each entry whole. - Four groups of commands have no answer to render and are unchanged on every stream:
daemon startanddaemon restart(thestarted:line), the localsourcecommands (their progress),auth generate(the token alone, so it can be captured, and a secret is never put in JSON) andcompletions.--jsonis accepted on them and changes nothing. - The readable form is not a contract. It may change between releases, and the JSON is what a script depends on.
Alternatives rejected¶
- Keep ADR-016's exceptions (
search,recall,diary readas JSON everywhere). The reason was that free text does not fit a table, and an excerpt does not need one. - Keep
status,dbandintegrationas text in a pipe. It is the one place the rule was reversed, and a script that wants those has to know it. A script that parsed the text must now read the JSON, which--jsonalready gave it. - A JSON form for the four unchanged groups.
daemon startand the localsourcecommands report progress on a machine, not an answer from the daemon, and a token in JSON would end up in logs. - A per-command
--jsonflag on everything. It would be thirty copies of one definition, and a command added later would forget it. - Following the job to completion (
--wait). Useful, and a different feature: the output rule holds for it too.
Consequences¶
- Scripts that read the text of
status,dborintegrationfrom a pipe break and must read JSON. This is a breaking change and is released as one. - At the time,
CLICOLOR_FORCEno longer put colour into a command's result in a pipe. The current CLI usesFORCE_COLORfor help, diagnostics and progress instead; JSON remains plain in pipes and is syntax-highlighted on a coloured terminal (see the CLI reference). - The readable forms are only exercised by unit tests: no test runs the binary on a pseudo-terminal,
so the stream decision is covered through
term::is_prettyand through the piped output of every command.