Agent-First Data v0.21: Even `--version` Speaks the Protocol
v0.21 turns the last bespoke line of CLI output into the protocol. `--version` no longer prints `tool 1.2.3` as human text — it always answers with a structured protocol-v1 version event, JSON by default, carrying name, version, and optional display_name/build. The pre-parser that intercepts it before your argument parser now recognizes your own value-taking global flags, so `tool --stdout-file x --version` still works. Shipped identically across Rust, Go, Python, and TypeScript.
An agent-first CLI collapses its output into one protocol: a kind:"result" (or
kind:"error") envelope with a trace, JSON by default, the same shape whether
it left over stdout, an HTTP body, an MCP tool result, or an SSE frame. Every
output followed that rule except one. --version still printed tool 1.2.3 — a
bespoke line of human text, the single place an agent had to stop parsing the
protocol and start scraping a string.
v0.21 closes that crack. It’s a breaking release; AFDATA is still pre-1.0 and carries no compatibility layer.
One less line to special-case
--version and -V now always answer with a structured version event — JSON by
default, --output yaml|plain or --json for another renderer. There is no
conventional-text mode and no knob to bring one back:
$ tool --version # v0.20
tool 1.2.3
$ tool --version # v0.21
{"kind":"result","result":{"code":"version","name":"tool","version":"1.2.3"},"trace":{}}
The old API carried a conventional_default() seam precisely so bare --version
could stay human text while an explicit --output went structured. That seam is
gone. Version output is a result like any other result, so the same reader that
handles a tool’s real output handles its version too.
A richer payload
The event now names itself. Alongside version it carries name (the short bin
identity), and optionally a human-facing display_name and an opaque build
identifier the caller supplies — a git commit SHA, most usefully:
{"kind":"result","result":{"code":"version","name":"afdata",
"version":"0.21.0","display_name":"Agent-First Data","build":"a1b2c3d"},"trace":{}}
display_name and build are omitted when absent, so the minimal event stays
{code, name, version}. What build means is entirely the caller’s business;
AFDATA just carries it through.
The pre-parser learned your flags
Here is the part that was actually hard. --version has to be handled before
your real argument parser — clap, argparse, commander — or the parser’s own
built-in version handling swallows it and --version --output json never gets a
chance to be structured. So AFDATA scans argv for a top-level version request
first, stopping at the first positional (your subcommand) so that
tool sub --version <value> stays the subcommand’s business.
The trap is a value-taking global flag standing in front of --version:
tool --stdout-file /var/log/out --version
A naive scanner sees /var/log/out, decides “that’s a positional, must be the
subcommand,” and stops — one token short of the --version it was looking for.
The version request vanishes.
v0.21 fixes it by asking the caller which of their flags take a value. In Rust
you hand over your clap Command and AFDATA introspects it; in Go, Python, and
TypeScript you pass an explicit list of your value-taking global flags. Either
way the pre-parser consumes the value and keeps scanning:
let build = option_env!("GIT_SHA");
cli_handle_version_or_continue(&argv, &Cli::command(), "tool", Some("Tool"), version, build)?;
There is no hardcoded flag list inside AFDATA. It recognizes global flags it has
never heard of — including ones you add long after this release — because the
knowledge lives in your command definition, not in ours. The subcommand-boundary
check itself is unchanged: tool sub --version 1.3.0 still belongs to sub.
Four languages, one behavior
This lands identically in Rust, Go, Python, and TypeScript. The normative spec’s
version-output section is rewritten to match, and the four-language end-to-end
suite now drives --version through every SDK and asserts the same structured
event out of each — so the behavior can’t quietly drift apart again.
The shape now
Version is no longer the exception. Everything an agent-first CLI emits — its results, its errors, and now its own version — is one protocol an agent can read without a single special case. The one line of bespoke text is gone.