Landscape.
Everyone already built half of this. Nobody built the same half.
The usual objection to a standard like this is that the problem is already solved — terraform emits JSON, ripgrep emits JSON, just use those. So we measured. Tools with a real machine-output mode were run and inspected byte by byte; where a flagship mode needs credentials or a cluster, the answer comes from primary documentation instead. Every row says which.
The result is not that the problem is solved. It is that 7 of 8 tools with a streaming machine mode independently invented a type discriminator, and spelled it 5 different ways. That convergence is the argument for a standard. The divergence is the cost of not having had one.
The streaming tier.
These are the tools that emit a stream a consumer can process incrementally — the closest thing to what AOI‑CLI specifies. Four properties matter: line‑delimited framing, a type discriminator, a version of the output format carried in the output, and a terminal event that says whether the run succeeded.
| Tool | NDJSON | Discriminator | Format version | Terminal event | Success bool |
|---|---|---|---|---|---|
| cargo | ✓ | reason | — | build-finished | success |
| terraform | ✓ | type | ui | — | — |
| ripgrep | ✓ | type | — | summary | — |
| git trace2 | ✓ | event | evt | atexit | — |
| git | — | leading sigil character (#, 1, 2, u, ?, !) | — | — | — |
| pytest-reportlog | ✓ | $report_type | pytest_version | SessionFinish | — |
| journalctl | varies | — | — | — | — |
| stripe-cli | tty-dependent | type | — | — | — |
- 5/8 chose newline‑delimited JSON. Independently, without coordinating.
- 7/8 invented a discriminator — spelled
reason,type,event,leading sigil character (#, 1, 2, u, ?, !), and$report_type. Cargo carries two in one stream:reasonon the envelope and$message_typeinside embedded rustc diagnostics. - 3/8 declare an output‑format version. Terraform's lives under a key called
ui, inside an event typedversion— invisible to any key‑name search. - 4/8 emit a terminal completion event, and 1/8 carries an explicit success boolean. Cargo's
build-finishedis the only one. Ripgrep'ssummarycarries statistics but no verdict, so a clean no‑match run is indistinguishable from an aborted one. - None has all four. Cargo and terraform each have three, and they are missing different ones.
What each is missing.
The useful form of this data is not a score. It is a list of specific, filable gaps — most of these tools are a field or two from a contract an agent could rely on.
No version declaration anywhere in the stream. A consumer cannot branch on format version.
No terminal event on failure, and no success boolean at any time.
No version field of any kind, and no success indicator on the terminal event.
Integer code rather than a boolean; no stability promise and no offline documentation (api-trace2 ships no man page).
Not JSON at all, and no in-band version. Arguably the most-invoked tool in any agent shell session.
File-only — it can never write to stdout, so it cannot be piped.
Three different output shapes under one --format json flag: `events`/`ps` are NDJSON, `inspect` is a JSON array, `version` is a single object. The tool does not agree with itself.
Buffered single document, no terminal event, no success boolean, no offline schema discovery.
A single buffered document — structurally cannot stream, so no terminal event and no success boolean.
Buffered array, no envelope, no version, no completion signal.
No envelope, no version, no completion signal.
`--show-cursor` emits a non-JSON `-- cursor: …` trailer onto stdout.
Stdout is not pure: EndpointResponse lines, POST errors, and logs-tail warnings print regardless of --format.
Buffered and emitted only at run end, so an aborted run produces nothing at all. `-v` writes to stdout by default and corrupts the document.
A bare array with no envelope and no totals — the consumer must sum errorCount. An inherited TIMING env var injects a text table into stdout.
A version number with no policy attached is not a compatibility contract.
Three ideas, invented three times each.
12 of 22 surveyed tools independently invented a type discriminator, in 9 mutually incompatible spellings.
type · reason · event · Type+Action · kind · $report_type · $message_type · metadataType · leading sigil char · level
cargo carries TWO differently-spelled discriminators in one stream: `reason` at the envelope level and `$message_type` inside embedded rustc diagnostics.
Three tools independently invented a sigil to mark envelope keys apart from payload keys. All three chose a different sigil.
terraform: @ (@level, @message, @module, @timestamp) · pytest/rustc: $ ($report_type, $message_type) · journalctl: __ and _ (__CURSOR, _BOOT_ID)
A consumer reading `.version` gets a different KIND of answer from each tool.
semgrep: the tool's version · npm ls: the described project's version · cargo metadata: the output format's version
1 of 22 tools emits a terminal event with an explicit success boolean: cargo's build-finished.success.
Everyone else who signals completion downgrades to an integer requiring an out-of-band code table: git trace2 `code`, pytest-reportlog `exitstatus`, pytest-json-report `exitcode`. Ansible gives counters. ESLint makes you sum errorCount.
No tool in the survey can be asked, offline and unauthenticated, to describe its own output.
gh enumerates field NAMES offline but publishes no types; aws ships 428 API models that describe the API rather than the CLI envelope; syft publishes 111 schemas but its own schema.url needs network and points at main.
How this was measured.
Two independent methods, deliberately. Probe: an assumption‑free instrument runs the command and inspects the bytes — it makes no AOI assumptions, which is what lets it measure non‑conforming tools at all. Every probed command is credential‑free and network‑free so the run reproduces anywhere. Docs: primary vendor documentation and source, for tools whose flagship mode needs a cluster, a daemon, or a token.
The two methods were run separately and agreed. That matters more than either result alone — the headline below was reached twice, by people and processes that could not see each other's work.
No surveyed tool has all four of {NDJSON, type discriminator, in-band format version, terminal event with an explicit success boolean}. Cargo and terraform each have three.
Two traps worth naming, because they are the easiest errors to make with this data. Syft's schema promise does not extend to grype — grype's published schemas cover grype db search, never grype -o json. And cargo's “stable and versioned” promise covers cargo metadata, not the build stream, which carries no version at all.
The raw dataset is landscape.json. Nothing on this page is written by hand — it is rendered from that file so the prose cannot drift from the evidence.