Fireargs is in technical previewShare feedback

Search

Search documentation

JSON mode

Updated Oct 21, 2026

--json is a built-in flag on every fireargs command. It lets the same binary serve human users at the shell, scripts in a pipeline, and LLM agents — all backed by one zod-validated handler.

Enabled by default. Disable per-command with jsonOption: false, or customize the flag string/description with jsonOption: { flags?, description? }.

Three modes

greet world                              # CLI in, .render output (if any)
greet world --json                       # CLI in, JSON out
greet --json '{"name":"world"}'          # JSON in, JSON out

The flag takes an optional value (--json [value]):

  • Absent — normal CLI parsing, handler runs, and the validated output goes to .render(...) if the command has one; otherwise nothing is printed.
  • Present without a value — CLI parsing for input, but the handler's return is serialized as JSON to stdout.
  • Present with a JSON value — input comes from the JSON blob; CLI positionals/options are skipped, so required positionals may be left off; output is JSON.

What happens on --json '<value>'

  1. JSON.parse's the value.
  2. Validates the parsed object through the input zod schema.
  3. Calls the handler with the validated input and context.json === true.
  4. Validates the handler's return through the output schema.
  5. Writes JSON.stringify(result, null, 2) + "\n" to commander's writeOut — configureOutput.writeOut can intercept it (handy for tests).

When --json is passed alone (no value), step 1 is skipped and input is collected from the regular CLI parse instead. Steps 3–5 are identical.

Keeping stdout clean

The handler's second argument tells it which mode it runs in. In JSON mode, anything else written to stdout corrupts the result, so spinners, colors and progress output should check context.json:

.handler(async (input, context) => {
  const spinner = context.json ? undefined : startSpinner("Loading")
  // ...
})

Errors

Without a hook, a value that is not JSON and input that fails the zod schema are reported like a commander parse error, written to stderr with exit code 1:

error: --json value is not valid JSON: ...
error: invalid --json input at 'a.b[0]': Invalid input: expected number, received string

With exitOverride: true they throw a CommanderError instead, coded fireargs.invalidJson or fireargs.invalidInput. Output-schema failures and handler exceptions reject the returned Command's parseAsync(...).

Set renderError in the command's or program's config to turn all of them into output instead — the hook receives the original SyntaxError or ZodError for input failures:

f.program({
  name: "myapp",
  renderError: (error, context) => {
    const message = error instanceof Error ? error.message : String(error)
    return context.json ? { error: message } : `error: ${message}`
  },
})
  • In JSON mode a returned value is written as JSON to writeOut; otherwise a returned string is written to writeErr. Return nothing to write it yourself; rethrow to reject parseAsync(...) as before.
  • process.exitCode is then set to 1. The process is not exited, so a large payload still drains into a pipe — and exitOverride does not apply.
  • It also catches .render(...) failures. The nearest hook wins: the leaf's own, then each enclosing program's.

Commander parse errors (unknown option, bad choice, missing positional, too many arguments) happen before the run starts: they are written to stderr and exit with code 1, or throw a CommanderError with exitOverride: true.

Composes with subcommands

Every leaf in a program tree gets its own --json:

myapp greet --json '{"name":"world"}'
myapp api deploy --json '{"env":"prod"}'

The program (root) doesn't have its own --json — programs dispatch to subcommands; only leaves take input. See Programs.

Pairs with --llms

Together with LLMs mode, --json is the call-time half of fireargs's agentic interface: an LLM reads the --llms manifest to learn the schemas, then calls back through --json with a value matching the documented inputSchema.

Created with ❤ and Livemark