Agent-First Slug v0.5: Discovery Without Guessing
An agent meeting a CLI for the first time does the same three things: asks what it can do, asks what version it is, and runs something. afslug answered the first two in prose an agent had to parse and the third by mixing results and errors into one stream. v0.5 makes all three structured. `afslug --help` returns a scoped result — this command level only, globals marked global, `--recursive` for the whole tree. `afslug --version` returns a result, not a sentence. And `--output-to` decides whether errors land on stderr beside the results or join them in one ordered stream.
An agent meeting a CLI for the first time does the same three things in the same order: asks what the tool can do, asks what version it is, and then runs something and reads what came back.
afslug used to answer the first two in prose. --help printed clap’s usage
block — indentation, column alignment, [default: -] in brackets — and the
agent parsed English to find out that --charset takes three values. --version
printed a line. Both are fine for a person at a terminal and both are a parsing
task for everything else.
v0.5 makes all three structured.
Help is a result
$ afslug --help
{"kind":"result","result":{"code":"help","help":{
"command_path":"afslug","scope":"one_level",
"arguments":[{"name":"--output","value":"OUTPUT","default":"json","global":true,
"help":"Output format: json, yaml, or plain"}, ...],
"subcommands":[{"name":"slugify","about":"Generate a slug from input text"}, ...]
}},"trace":{}}
The shape comes from afdata v0.24’s help protocol, so it reads the same across every agent-first CLI. Three properties matter more than the schema:
Help is scoped. scope: "one_level" means you got this command and the
names of its children, not the entire tree. Ask for what you need next:
afslug slugify --help returns slugify’s flags, and lists afslug under
inherited_arguments_from instead of repeating --output in every subcommand.
Discovery is a walk, not a dump.
--recursive is the index. When you do want the whole surface at once —
writing a wrapper, generating docs — afslug --help --recursive expands every
nested subcommand in one compact payload. The tests hold it to a token budget
proportional to the number of commands and arguments, so the full tree stays
cheap enough to read in a single turn.
Humans still have a format. --output plain returns clap’s rendering
unchanged; --output markdown is what generates docs/cli.md. The format is a
request, not a guess about who’s asking.
The help pseudo-subcommand is gone. afslug help slugify was a second
spelling for afslug slugify --help, and clap advertised it in every command
listing as though it were part of the tool. And running afslug with no
command no longer dumps the whole usage block at you — it returns a short
structured error with a hint:
$ afslug
{"error":{"code":"cli_error","hint":"try: afslug --help",
"message":"a command is required","retryable":false},"kind":"error","trace":{}}
An error should tell you what to do next, not spend a thousand tokens guessing what you meant.
One spelling for version
afslug --version now always returns the structured protocol result — name,
display name, version, and the git SHA the binary was built from. There is no
human variant and no -V shorthand:
$ afslug --version
{"kind":"result","result":{"build":"0f7dbe74","code":"version",
"display_name":"Agent-First Slug","name":"afslug","version":"0.5.0"},"trace":{}}
v0.4 kept -V and a plain-text line for people. Two spellings of one concept is
a decision every caller has to make and every wrapper has to handle. One
spelling, one shape.
Where errors go
The last piece is which stream carries what. --output-to is global and
defaults to split:
# split (default): results on stdout, errors on stderr
slug=$(afslug slugify "Hello, World!" --output plain) || echo "failed"
# stdout: one ordered stream of events, results and errors interleaved
afslug slugify "!!!" --validation url-path --output-to stdout
Under split, $(...) captures only successful output — a failed slugify
leaves the variable empty instead of filling it with a JSON error object that
the next command treats as a slug. Under stdout, you get a single ordered
event stream, which is what you want when you’re piping everything into one
reader. stderr sends both the other way for logging.
Install
cargo add agent-first-slug --no-default-features # the library, no CLI deps
cargo install agent-first-slug # the afslug CLI
brew install agentfirstkit/tap/afslug # prebuilt binaries
v0.5 also ships the release workflow that builds those binaries for macOS
(Apple silicon and Intel), Linux, and Windows, so brew and scoop install
afslug without a Rust toolchain.
The library API is unchanged. Everything here is about the ten seconds before an agent calls it for the first time.