Your CLI is an agent tool
Every burgee program answers --schema and --mcp from its one declaration — no tool definitions, no second interface.
A burgee program is data first. The same manifest that renders --help answers two
questions an agent asks before anything else.
--schema — the program as data
mytool --schemaPrints the commands, their arguments and options (with choices, defaults and which are
required), each command's effects, its examples, and a JSON Schema per command. It reads
the manifest and nothing else: no config file, no network, no handler runs (N8), so it is
safe to call first.
--mcp — the program as MCP tools
mytool --mcpServes the same manifest as MCP tools over stdio —
JSON-RPC 2.0, three methods, zero dependencies. A tool call runs the command exactly as a
--json caller would; the result is that envelope, byte for byte, so an agent reading
your CLI over MCP and one shelling out with --json see one payload.
Only what you publish
A command is a tool only when it declares what it does:
defineCommand({
name: 'deploy',
effects: 'non_idempotent', // read_only | idempotent | non_idempotent
// …
});effects generates MCP's readOnlyHint, idempotentHint and destructiveHint. The
protocol defaults destructiveHint to true, so a command that says nothing is not
served at all rather than served with the dangerous reading. On a commander-syntax
program the same declaration is .effects('read_only').
Client configuration
For Claude Desktop, Claude Code, Cursor or any stdio client, register the program as a server — nothing else to write:
{
"mcpServers": {
"mytool": {
"command": "npx",
"args": ["mytool", "--mcp"]
}
}
}If the program declares its own --schema or --mcp option, burgee leaves it alone: the
surfaces are additive, never a change to a program that already exists.
completion <shell> — static, never runs your CLI on TAB
mytool completion zsh > "${fpath[1]}/_mytool" # also: bash, fish, pwsh, figCommands, subcommands, options and choice values, with descriptions where the shell shows
them — generated from the same manifest, as plain shell. Pressing TAB never executes the
program. Each script is pinned by a snapshot and exercised by its real shell in CI. The
templates live in burgee/completions and load only on that command, so a program pays
for them when it prints a script, never at startup.
--explain <option> — where a value came from
Every value a command sees comes from exactly one place, in one fixed order:
flag > env > config file > package.json field > default. --explain says which, and
what it beat:
$ mytool deploy --explain region
region = "eu-1" from config file ./mytool.config.json
candidates: flag --region (unset), env MYTOOL_REGION (unset), default "us-1"The same information rides under --json as meta.provenance, per option — for an agent,
the difference between one call and five. envPrefix and config: true on
defineProgram opt a program into env names and config discovery; nothing is read that
the running command did not declare.
Stopping instead of prompting
Under a detected agent (AI_AGENT, CLAUDECODE, CURSOR_AGENT, CODEX_THREAD_ID,
GEMINI_CLI, …) a program is non-interactive by default, terminal or not; FORCE_TTY=1
overrides. A handler reads ctx.interactive and ctx.agent, and where it would have
prompted it stops with what the caller must do:
if (!ctx.interactive) ctx.actionRequired({
reason: 'confirm',
message: 'deploying to prod needs confirmation',
next: [{ command: 'deploy --target prod --yes', when: 'once you have reviewed the plan' }],
});The envelope carries status: "action_required", the reason, and next[] as runnable
commands with the program in front and the caller's own --json carried; the exit code is
4. An idempotent command reports changed: true | false, and --schema summarises above
a character budget with <command> --schema to drill into one.
Gallery
Every component and every registered plugin, rendered by running them — the static projection beside the animation, in all five modes.
Fly your own burgee
Every CLI built on burgee can have a flag of its own — generated from two colours, with your glyph on it, from the same code that draws ours.