Skip to main content

Module output

Module output 

Source
Expand description

What a command writes, and how a consumer should read it.

A spec has always described a command’s inputs exhaustively and its outputs not at all. Across the jdx.dev fleet that gap is 123 hand-written flag declarations in three incompatible spellings — mise and pitchfork say -J --json, hk says --format=human|json|jsonl, aube says both --json and --reporter=default|append-only|ndjson|silent — and not one of them says what the JSON contains.

cmd "check" {
    output "human" default=#true help="Human-readable report"
    output "json" media_type="application/json" framing="json" help="One report object" { schema #"""{…}"""# }
    output "xml" media_type="application/xml" help="One XML document"
    output "jsonl" framing="jsonl" help="One event per line" { schema #"""{…}"""# }
    select "--format"
}

§Name, media type, and framing are not the same thing

The positional is the token a user types, media_type identifies the content, and SpecOutput::framing says how a consumer reads the stream. They are separate because XML and prose are both read to the end despite having different media types, while aube spells its line-delimited output ndjson and hk spells the identical wire format jsonl: a generated SDK that keyed off the name would offer exec_ndjson() for one and exec_jsonl() for the other, and a caller would be back to knowing which CLI they were talking to — the thing this exists to delete.

§Two ways to ask, one model

A command-level select "--format" names a flag whose value picks an output. An output "json" select="--json" names a boolean flag whose presence picks that one. Both are common in the wild and both lower here; SpecOutput::select_argv answers “which words pick this” so no consumer has to know which spelling was used.

§Selection is resolved, not just recorded

[resolve_selectors] runs once after the whole document is read and fills the selecting flag’s choices from the output names. That is what lets completion, the docs renderers, the fig exporter and the SDK choice types all work without any of them learning about outputs. Two consequences worth knowing before reading a re-emitted spec, both documented on docs/spec/reference/output.md:

  • the choices appear in the output even though the source did not write them, the same way include and flagset expansion do not survive a round trip;
  • a select naming an inherited global gets a narrowed copy of that flag inside the command, because two commands under one global rarely produce the same outputs.

Resolution is idempotent: a second pass sees choices that already match and validates them instead of rewriting, so a spec that has been through it round-trips unchanged.

Structs§

SpecOutput
One thing a command can write.

Enums§

Framing
The wire format of what a command writes to stdout.
Selector
How a command’s output is asked for.

Functions§

effective_outputs
The outputs in effect for a command, CLI-wide declarations folded in.
effective_outputs_ref
Reference-based form used by tree walkers that already hold the command chain.
effective_select
The value-taking selector in effect for a command, if it reaches that command.
effective_select_ref
Reference-based form used by tree walkers that already hold the command chain.
has_outputs
Whether anything at all is declared, so a consumer can skip the whole concept.