Search
Search documentation
Introduction
Updated Oct 21, 2026
Define each command with typed input and output models. Get composability, structured JSON I/O, and LLM-ready manifests baked in — for humans, agents, and scripts alike. Built on pydantic and click; Python 3.12+.
Installation
pip install fireargs
# or
uv add fireargsA first command
The handler's first parameter annotation is the input model and its return annotation the output type. Fields become arguments and options; Argument(...) and Option(...) are pydantic Field()s that also carry CLI config. Import the names you use, as here, or the module (import fireargs as fa, then fa.Option(...)).
from typing import Annotated
from pydantic import BaseModel
from fireargs import Argument, Option, command
# Composable arguments and options
Name = Annotated[str, Argument(description="The user's name.")]
Times = Annotated[int, Option(default=1, ge=1, description="Repeat it n times.")]
Shout = Annotated[bool, Option(short="s", default=False, description="Use capitals.")]
# Composable groups of them
class GreetInput(BaseModel):
name: Name
times: Times
shout: Shout
# Typed output, validated on every run
class Greeting(BaseModel):
greeting: str
# A click command, typed from the handler's annotations
@command(description="Greet someone politely.")
def greet(input: GreetInput) -> Greeting:
greeting = " ".join([f"hello {input.name}"] * input.times)
return Greeting(greeting=greeting.upper() if input.shout else greeting)
# Output for humans; --json prints the data instead
@greet.render
def render_greeting(output: Greeting) -> str:
return output.greeting
# Use as a normal click command, e.g. configure or apply plugins
if __name__ == "__main__":
greet()That gets you:
$ python greet.py world --times 2 -s # CLI input, rendered output
HELLO WORLD HELLO WORLD
$ python greet.py world --json # CLI input, JSON output
{
"greeting": "hello world"
}
$ python greet.py --json '{"name":"world","times":2}' # JSON input, JSON output
{
"greeting": "hello world hello world"
}
$ python greet.py world --times 0
error: option '--times <value>' argument '0' is invalid. Input should be greater than or equal to 1--help comes from click:
Usage: greet.py [OPTIONS] <name>
Greet someone politely.
Arguments:
name The user's name.
Options:
--times <value> Repeat it n times. [default: 1]
-s, --shout Use capitals.
--json [value] write output as JSON; pass a JSON string to also drive
input
--llms print this command's schema (name, input, output) as JSON
for LLM tool-use
--help Show this message and exit.A first program
Group leaves with Program(router={...}):
# Composable commands: a router of leaves and nested programs
cli = Program(
name="myapp",
description="My CLI",
version="1.0.0",
router={"greet": greet, "deploy": deploy, "db": db},
)
# Use as a normal click group, e.g. configure or apply plugins
if __name__ == "__main__":
cli()That gives:
$ myapp --help
Usage: myapp [OPTIONS] [COMMAND] [ARGS]...
My CLI
Options:
-V, --version output the version number
--llms print the program manifest (subcommand schemas) as JSON for
LLM tool-use
--help Show this message and exit.
Commands:
greet Greet someone politely.
deploy Deploy the current build.
db Database tasks.
$ myapp db migrate --json '{}'
{
"applied": 0
}Programs nest — router accepts other programs the same way it accepts
leaves. myapp db migrate routes through both levels.
JSON mode
Every command takes --json. Bare, it switches the output to JSON; given a value, that value is
the input, checked against the input schema, so scripts never assemble argv:
$ python greet.py world --times 2 --json # CLI input, JSON output
{
"greeting": "hello world hello world"
}
$ python greet.py --json '{"name":"world","times":3,"shout":true}' # JSON input, JSON output
{
"greeting": "HELLO WORLD HELLO WORLD HELLO WORLD"
}
$ python greet.py world --json | jq -r .greeting # pipe it into other tools
hello world
$ python greet.py --json '{"times":2}' # input errors go to stderr, exit code 1
error: invalid --json input at 'name': Field required
$ python greet.py --json '{"name":"world","times":"2"}'
error: invalid --json input at 'times': Input should be a valid integerstdout only ever carries the data, so a script can parse it without checking for noise.
LLMs mode
Every command and program ships with --llms, which prints an
MCP-tools/list-shaped manifest. For greet.py above:
{
"tools": [
{
"name": "help",
"description": "Documentation tool, not callable. Each `tools[].name` is the space-separated path beneath this binary. Invoke a tool with `<binary> <name> --json '<value>'` matching that tool's `inputSchema`; output is JSON on stdout matching `outputSchema`.",
"inputSchema": { "type": "object", "properties": {} },
"outputSchema": { "type": "object", "properties": {} }
},
{
"name": "greet",
"inputSchema": {
"properties": {
"name": {
"description": "The user's name.",
"title": "Name",
"type": "string"
},
"times": {
"default": 1,
"description": "Repeat it n times.",
"minimum": 1,
"title": "Times",
"type": "integer"
},
"shout": {
"default": false,
"description": "Use capitals.",
"title": "Shout",
"type": "boolean"
}
},
"required": ["name"],
"title": "GreetInput",
"type": "object"
},
"outputSchema": {
"properties": { "greeting": { "title": "Greeting", "type": "string" } },
"required": ["greeting"],
"title": "Greeting",
"type": "object"
},
"description": "Greet someone politely."
}
]
}An LLM agent reads the manifest, picks a tool, and invokes it with
<binary> <name> --json '<value>'. Output comes back as JSON on stdout.
Learn more
- Commands — the
@command(...)decorator and the leaf it builds. - Inputs —
Argument()/Option()and per-field click config. - Outputs — the output type, serialization, and JSON Schema in
--llms. - Programs — subcommand trees, nesting, and program-level config.
- JSON mode —
--jsonfor programmatic and pipeline use. - LLMs mode —
--llmsand the MCP-shaped manifest. - Composability — share common params across commands by composing pydantic models.
- Agent Skills — teach your coding agent to write and drive Fireargs CLIs.
- Contributing — local setup, conventions, release flow.
License
MIT.
Created with ❤ and Livemark