Context
Who: developers deploying services, often from a laptop, a CI runner, or a pipe into another tool. Jobs: deploy, check status, read failures, script the same operations. Constraints: the terminal may be interactive or a pipe, ANSI may be disabled, and the width is unknown until runtime. The CLI controls the colors it sets — but only when it is allowed to set them.
Principles
| Rule | Rationale |
|---|---|
| stdout is data, stderr is commentary | Piped output stays parseable; progress and warnings never corrupt the data stream. |
| Color is a runtime decision | TTY, NO_COLOR, CI, and dumb terminals each change what is honest to emit; meaning never depends on color anyway. |
| Width is read, not assumed | Wrapping and truncation follow the real terminal; 80 columns is a guess, not a contract. |
| An error ends with the next command | Recovery is part of the interface; “what happened / why / next” is one block. |
| Exit codes are a public API | Scripts branch on them; they are specified and tested like any other contract. |
Sessions
Whole-use transcripts: a success, a failure, and a pipe. These are the CLI's “scenes”.
Color modes
Switch the rendering context; the same transcripts adapt. In CI, progress spinners disappear and machine-readable log lines appear instead.
Width
The CLI reads the terminal width; slide to see help and output re-wrap. Nothing is designed for one magic width.
Errors and exit codes
Error anatomy
- What happened — one line, past tense, no jargon.
- Why — the rule or state that caused it.
- Next — a runnable command or a named owner.
Exit codes
| Code | Meaning | Typical cause |
|---|---|---|
0 | success | operation completed |
1 | usage error | unknown flag or missing argument |
2 | preflight failed | environment refused the deploy |
3 | runtime failure | remote system failed mid-operation |
4 | timeout | operation exceeded --timeout |
Machine mode
--json is a documented contract, not a debug dump: closed state vocabulary, ISO-8601 UTC timestamps, no diagnostics on stdout, and the same exit codes.
Typography
The terminal owns the font. The CLI cannot choose a face, so it owns alignment, weight, and case instead — and columns are computed in characters, never in pixels.
- One size, one fixed-pitch face; there is no font axis to design with.
- Never align with spaces alone when a pipe is possible — provide a column or table mode.
- Case is content, not emphasis: do not SHOUT commands or status.
Components and states
The components of a CLI are output blocks. Each has defined states, and every state is reachable through the mode matrix above.
Step line · running / done / failed
Result line · success / skipped
Interactive prompt · waiting / confirmed
Table · two widths of the same data
Motion and accessibility
Motion budget
- Spinners and progress bars are the only motion; result lines never animate.
- Non-TTY,
NO_COLOR, and CI disable spinners and animated progress entirely. prefers-reduced-motionfreezes the spinner at a static frame.
Accessibility
- Every status keeps a word or glyph, so a screen reader hears meaning without color.
- Output is a linear stream: no cursor-addressing, no two-column layout that breaks reading order.
- Errors are complete sentences with the next command, not codes alone.
Wraps to the real terminal and never assumes 80 columns.
Every interactive prompt has a default answer and an escape.
A closed, documented set that scripts can branch on.
Contrast
When the CLI is allowed to colorize, it controls the palette and the pairs are computed for both common background assumptions. If a pair fails, the token changes — not the report.
| Role | Value | on dark terminal | on light terminal |
|---|
Both assumptions are shipped because the CLI cannot detect the background reliably; every role also carries a word or symbol.