From schema to shipping CLI
Sane defaults, every escape hatch, no extra plumbing.
Schema-first
Inputs and outputs are zod schemas. Descriptions, defaults, choices, and variadics propagate to commander automatically.
Validated end-to-end
Zod validates every input before your handler runs and every output before it ships. One source of truth for types and runtime.
Fully composable
Share common parameters via zod composition (.extend, .merge, shape spread). Group leaves into programs and nest programs in programs — same builder shape at every level.
Built-in --json
Every command takes JSON in and emits JSON out via --json. Drive it from a script, a pipeline, or another agent — same binary.
Built-in --llms
Each binary publishes an MCP-shaped tools/list manifest. LLM agents read it and call straight back through --json.
Powered by Commander
The thing you get back is a commander Command. Every commander API stays available — fireargs just fills in the boring parts.
One schema, every interface
The same zod schema drives --help, --json, and the LLM manifest.
import { f } from "fireargs"
import { z } from "zod"
const greet = f
.command({ name: "greet" })
.input(z.object({
name: f.argument().string(),
times: z.number().default(1),
}))
.output(z.object({ greeting: z.string() }))
.handler(input => ({
greeting: `hello ${input.name}`.repeat(input.times),
}))
greet.parseAsync(process.argv)$ greet --llms
{
"tools": [
{ "name": "help", ... },
{
"name": "greet",
"description": "Greet someone politely",
"inputSchema": { "type": "object", ... },
"outputSchema": { "type": "object", ... }
}
]
}
JSDoc descriptions surface in --help and the JSON Schema.
Defaults, choices, and optionality flow to commander automatically.
Every leaf gets --json '<value>' and --llms for free.
Stop hand-rolling argument parsers. Write a schema.
Install, write a command, ship a CLI that humans, scripts, and LLMs can all call.