Fireargs is in technical previewShare feedback

Search

Search documentation

Outputs

Updated Oct 21, 2026

The handler's return annotation defines the shape of whatever it returns. There's no Argument() / Option() equivalent for outputs — outputs aren't CLI inputs, so click has no per-field surface to configure. The type is pure data shape.

from pydantic import BaseModel
from fireargs import command


class GreetInput(BaseModel):
    name: str


class Greeting(BaseModel):
    greeting: str
    iterations: int


@command()
def greet(input: GreetInput) -> Greeting:
    return Greeting(greeting=f"hello {input.name}", iterations=1)

Any type pydantic validates works: list[str] writes a bare JSON array under --json, a scalar writes a scalar, and None writes null. Prefer a model when the output may grow fields later — and when the CLI is exposed to MCP clients, whose outputSchema must be an object.

Where the output type is used

  1. Validation — the handler's return value is validated against the type on every invocation, through a pydantic TypeAdapter. A matching dict is accepted for a model, validators run, and a mismatch raises ValidationError.
  2. --json serialization — when --json is set, the validated value is dumped in pydantic's JSON mode, by alias, and written to stdout as JSON indented by two spaces. See JSON mode.
  3. Human rendering — without --json, the validated value goes to the command's render when it has one. See Commands.
  4. --llms manifest — the type's JSON Schema, in pydantic's serialization mode, is surfaced as the tool's outputSchema. See LLMs mode.

Optional fields

A field spelled X | None = None is Python's optional: while it is None it is left out of the --json output instead of being written as null, and the outputSchema does not require it — the same wire shape as a TypeScript twin's .optional() field. Any other defaulted field is always present, so the outputSchema marks it required.

from pydantic import BaseModel


class Page(BaseModel):
    items: list[str]
    total: int = 0  # always written, required in outputSchema
    next_cursor: str | None = None  # left out while None

Serialization

Pydantic's JSON mode does the work, so most standard types have a plain-JSON wire form:

TypeOn the wireIn outputSchema
datetime / date / timeISO 8601 stringstring with format: date-time, …
timedeltaISO 8601 duration ("PT1M30S")string with format: duration
Decimalstring ("1.5")string
UUID / Pathstringstring with format: uuid / path
set[T] / tuple[T, ...]arrayarray
bytesUTF-8 stringstring with format: binary
Enumits valueenum under $defs
a nested modelobject, by alias$ref into $defs

A type pydantic cannot describe or serialize — an arbitrary class allowed with arbitrary_types_allowed — fails the run under --json and fails --llms. Convert it to a model or a JSON-native type in the handler.

fireargs deliberately stays plain JSON, so the wire is consumable by any LLM, shell, or non-fireargs client.

Dates round-trip

Unlike JSON.stringify and zod, pydantic writes a datetime as ISO 8601 and reads the same string back, and its JSON Schema says so with format: date-time. A datetime field needs no workaround in either the input or the output model.

What flows to --llms

Per property, the JSON Schema in the manifest carries:

  • type, format, pattern and bounds — derived from the Python type and the field's constraints.
  • title — pydantic adds one per field and per model.
  • description — from description= on Field(...).
  • enum — from Literal[...] and Enum.
  • required — every field without a default, plus each defaulted field except X | None = None ones.

There's no CLI-side metadata on outputs — Argument() / Option() are inputs-only — so every key pydantic emits passes through.

Why no separate slots like Argument()/Option()?

Click's Command has no output abstraction. CLI users see whatever the render prints (or what --json serializes); there's nothing for fireargs to "wire" beyond running the validation. Keeping outputs as plain pydantic types matches click's surface and avoids inventing concepts the runtime can't honor.

Created with ❤ and Livemark