Fireargs
Define each command with typed input and output models. Get composability, LLM-ready manifests, and structured JSON I/O baked in — for humans, agents, and scripts alike. Built on pydantic and click; Python 3.12+.
Installation
pip install fireargs
# or
uv add fireargs
A 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.")]
# Typed input, validated on every run
class GreetInput(BaseModel):
name: Name
times: Times
shout: Shout
# Typed output, validated on every run
class GreetOutput(BaseModel):
greeting: str
# A click command, typed from the handler's annotations
@command(description="Greet someone politely.")
def greet(input: GreetInput) -> GreetOutput:
greeting = " ".join([f"hello {input.name}"] * input.times)
return GreetOutput(greeting=greeting.upper() if input.shout else greeting)
# Output for humans; --json prints the data instead
@greet.render
def render_greeting(output: GreetOutput) -> 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.
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": "GreetOutput",
"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.
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 integer
stdout only ever carries the data, so a script can parse it without checking for noise.
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.
- LLMs mode —
--llmsand the MCP-shaped manifest. - JSON mode —
--jsonfor programmatic and pipeline use. - 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.
Metadata
Release files for fireargs 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| fireargs-0.1.1.tar.gz | 29.8 kB | Without provenance | Details for fireargs-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| fireargs-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for fireargs-0.1.1-py3-none-any.whl |
Total release size: 71.3 kB
Release files / fireargs-0.1.1.tar.gz
| Download URL | fireargs-0.1.1.tar.gz |
|---|---|
| Size | 29.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1b6ef3a1bcf7c1b507fdfb50f33b3e859745aab4a7dc5ec909e8dc1332957baa
|
|
BLAKE2b-256 checksum How to use checksums |
41f92b733b3e19a3dbfa67a43c99118e9fd6cee453227d5a44433ec7ad9b50b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / fireargs-0.1.1-py3-none-any.whl
| Download URL | fireargs-0.1.1-py3-none-any.whl |
|---|---|
| Size | 41.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e958b67c41e46df4b6da80a26bc786a22e1408071e4d359d66296dc6ae3352b1
|
|
BLAKE2b-256 checksum How to use checksums |
23dcc867546d24240f4b1b6617c22888af86bf69f673beaa912db9bd6043e0fc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for fireargs 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.