cli — CLI Output Formatting

CLI Output Formatting

Structured I/O layer for machine-readable output. Provides NDJSON streaming for MCP and plugin integrations, normalized exit points, pretty-printing for human users, and remote output routing for headless sessions.

19
Files
12k
Lines
NDJSON
Protocol
TS
TypeScript

🔵 Structured I/O 🟢 NDJSON 🟡 Exit Codes 🔴 Remote Routing

📚 What is this?

The cli module is the output formatting layer that sits between Claude Code's internal logic and the outside world. Whether the consumer is a human reading coloured terminal text or a machine parsing NDJSON lines, this module ensures consistent, predictable output with well-defined exit semantics.

💡 Analogy

Think of the CLI module as a multilingual interpreter at a press conference. The speaker (internal logic) says the same thing, but the interpreter (structuredIO) translates it into NDJSON for robots, print formats it with colours for journalists, and remoteIO relays it over a radio link to reporters who couldn't attend in person. Meanwhile exit is the protocol officer ensuring everyone leaves through the right door with the right badge.

Architecture

flowchart LR
  subgraph Core["Internal Logic"]
    CMD["Command Result"]
    ERR["Error Object"]
  end
  subgraph CLI["cli Module"]
    EXIT["exit.ts"]
    SIO["structuredIO.ts"]
    PRINT["print.ts"]
    RIO["remoteIO.ts"]
  end
  subgraph Outputs["Output Targets"]
    STDOUT["stdout (human)"]
    NDJSON["NDJSON stream"]
    REMOTE["Remote socket"]
  end
  CMD --> EXIT
  ERR --> EXIT
  EXIT -->|cliOk| SIO
  EXIT -->|cliOk| PRINT
  EXIT -->|cliError| SIO
  EXIT -->|cliError| PRINT
  SIO --> NDJSON
  PRINT --> STDOUT
  RIO --> REMOTE
  SIO --> RIO

Data Flow

flowchart TD
  INPUT["Command Execution"] --> RESULT{"Success?"}
  RESULT -->|yes| OK["cliOk(data)"]
  RESULT -->|no| FAIL["cliError(err)"]
  OK --> NORM["Normalize Payload"]
  FAIL --> NORM
  NORM --> MODE{"Output Mode?"}
  MODE -->|interactive| PRETTY["print.ts → ANSI"]
  MODE -->|json| STRUCT["structuredIO → NDJSON"]
  MODE -->|remote| RELAY["remoteIO → socket"]
  PRETTY --> STDOUT["process.stdout"]
  STRUCT --> STDOUT
  RELAY --> SOCK["WebSocket / pipe"]
  NORM --> CODE["Exit Code"]
  CODE --> EXIT["process.exit()"]

🚶 Step-by-Step Walkthrough: NDJSON output for an MCP tool call

Trace a tool call result from internal execution to NDJSON output:

  1. Tool execution — An MCP tool finishes and returns a result object.
  2. cliOk() — exit.ts wraps the result with exit code 0 and metadata.
  3. Payload normalization — Timestamps, request IDs, and version info are injected.
  4. Output mode detection — The --output json flag routes to structuredIO.
  5. NDJSON serialization — Each message is JSON.stringify'd and terminated with \n.
  6. stdout.write() — The NDJSON line is flushed to stdout in a single write.
  7. Remote relay — If a remote listener is attached, remoteIO also forwards the line.
  8. Exit code — On final output, process.exit(0) is called for success.

Key Classes & Functions

SymbolFilePurpose
cliOk()exit.tsNormalized success exit with data payload
cliError()exit.tsNormalized error exit with error code and message
exitProcess()exit.tsControlled process.exit with cleanup hooks
StructuredIOstructuredIO.tsNDJSON writer with line buffering and flush control
formatNDJSON()structuredIO.tsSingle-line JSON serializer with error fallback
prettyPrint()print.tsANSI-coloured human-readable output formatter
formatTable()print.tsAuto-aligned table renderer for list output
RemoteIOremoteIO.tsForwards output to remote listeners over sockets
OutputRouterrouter.tsRoutes output to correct sink based on mode flags
parseOutputMode()flags.tsReads --output flag and env vars to pick mode
ProgressReporterprogress.tsStreaming progress bar for long operations
ErrorFormattererrorFormat.tsConverts Error objects to structured output

Directory Map

Source tree for cli
cli/
├── exit.ts                      ← cliOk(), cliError(), exitProcess()
├── structuredIO.ts              ← NDJSON writer with buffering
├── print.ts                     ← ANSI pretty-print for humans
├── remoteIO.ts                  ← Remote output relay over sockets
├── router.ts                    ← Output mode routing logic
├── flags.ts                     ← --output flag parsing
├── progress.ts                  ← Streaming progress indicators
├── errorFormat.ts               ← Error → structured payload converter
├── types.ts                     ← Shared interfaces (CliResult, etc.)
├── constants.ts                 ← Exit codes, format strings
├── __tests__/
│   ├── exit.test.ts
│   ├── structuredIO.test.ts
│   ├── print.test.ts
│   └── remoteIO.test.ts
└── index.ts                     ← Barrel export

Configuration

OptionDefaultDescription
--outputtextOutput mode: text (human), json (NDJSON), silent
CLAUDE_OUTPUT_FORMATtextEnv var override for output mode
NO_COLORunsetWhen set, disables ANSI colour codes in text mode
NDJSON_BUFFER_SIZE64Max buffered NDJSON lines before forced flush