Developer Tools

Good CLI output is a kind of API

2 min read

Good CLI output is an API.

The reader is not only a person at a terminal. A shell script reads stdout. CI reads the exit code and whatever the command wrote before failing. An agent may read the tail of the log and try to infer whether it should retry, stop, or ask for help. Later, somebody pastes the failure into an issue and tries to reconstruct the machine state from whatever survived.

That means the command has to be predictable in a few boring ways. stdout should stay clean when another tool needs to parse it. stderr should carry diagnostics. exit codes should mean the same thing every time. If the command offers structured output, that shape should stay stable across releases.

good:
  stdout: machine-readable result
  stderr: progress and warnings
  exit code: 0 success, nonzero failure

bad:
  stdout: mixed status text and JSON
  stderr: silent
  exit code: 0 even when the command failed

That is the difference between a command somebody can compose and a command that only works in a demo. The user should not have to guess whether jq will parse the result or whether a CI step will misread a warning as data.

I also care about failure messages because they become part of the interface. “Permission denied” is fine if that is the whole story. “Permission denied while reading ./secrets.json; run with --scope repo or move the file under the workspace” is better because it tells the next step. A good CLI does not just fail. It makes the next move obvious.

The same rule applies to progress output. A command that prints one line per phase, or a --json mode that keeps machine output stable, gives scripts something to depend on. A release tool can parse {"status":"done","path":"dist/app.tar.gz"}. A human can still use the plain-text mode. The command is better when both can live with it.

Jeremy London

About Jeremy London

Engineering leader and builder in Denver. I write about AI platforms, agents, security, reliability, homelab infrastructure, and the parts of engineering work that have to survive production.