A Coding Agent Gets a Scriptable JSONL Interface and a Real CLI Binary
A CLI that only prints final text is a dead end for automation. JSONL output with per-line versioning gives scripts, CI pipelines, and monitoring tools a stable machine-readable contract they can consume incrementally, without fragile screen-scraping or waiting for completion.
The di-code coding agent now speaks JSON Lines. A new `runJsonMode` function subscribes to every `AgentEvent`—start, turn, message update, end—and writes each as a versioned JSON record to stdout. Scripts can parse the stream line by line without waiting for the agent to finish, and every record carries a protocol version so consumers stay resilient to future schema changes. Structured failures keep their event history on stdout while routing diagnostics to stderr, and a `finally` block guarantees the listener is always cleaned up.
Behind the scenes, `runMain` now dispatches to either print or JSON mode from a single Agent instance, avoiding duplicated CLI parsing. A thin `entry.ts` file wires `process.argv`, real stdout/stderr, and the package version into the application, and the package.json `bin` field points to the built `dist/entry.js` so `di-code` works as a global command.
Subprocess tests spawn the actual binary to verify help text, version output, deterministic print responses, versioned JSON event streams, and proper separation of usage errors onto stderr. The whole pipeline—argument parsing, mode selection, event streaming, and process exit codes—now runs deterministically without touching the network, using a faux provider that returns fixed responses.
Putting a protocol version on every JSONL line, not just the header, is a defensive design choice that pays off when logs are truncated, sharded, or tailed from the middle—a lesson applicable to any streaming CLI contract.
Keeping the entry point thin and deferring all logic to testable functions means the entire application can be verified without spawning processes, while still having integration tests that catch real-world wiring bugs.
Using a faux provider that returns fixed responses turns the CLI into a deterministic test harness; the entire event pipeline can be validated without network calls, API keys, or model variability.