docspack v1.5.0
Contents

cmdspec 0.1

A machine-readable description of a command-line interface.

Status: draft. Version 0.x makes no compatibility promise; 1.0 will, and will not be cut until three CLIs that docspack does not maintain have shipped a document. · Schema: schema/v0.1.json (JSON Schema 2020-12), published at https://docspack.dev/cmdspec/schema/v0.1.json · Licence: this text is CC BY 4.0; the schema and the examples are MIT.

The key words MUST, MUST NOT, SHOULD and MAY are used as in RFC 2119.

1. What a document is for

A cmdspec document says everything a caller needs to run a program correctly without running it first: the words it accepts, where each value can come from, what the program reads and writes, what it prints, what its exit status means, and whether it is safe to run. One document serves a person reading a reference page, a shell generating completions, a tool checking a release for breaking changes, and an agent deciding whether a command will delete something.

It describes; it does not implement. Nothing in a document is executed by a consumer, with one exception that a consumer opts into (valuesFrom, §6.4).

2. The document

A document is a JSON value. It MAY be written as JSON or as YAML 1.2; a YAML document MUST mean the same JSON value, which in practice means quoting any prose that contains , inside a flow mapping ({ … }) — unquoted, the comma silently starts a second key. Writing prose in block style avoids the trap entirely, and tooling that writes YAML SHOULD write it that way.

The conventional file name is cmdspec.yaml or cmdspec.json; examples use <program>.cmdspec.yaml.

yaml
cmdspec: "0.1"
info: { name: greet, version: 1.0.0 }
arguments:
  - { name: who, required: true }
options:
  - { name: --shout, aliases: [-s], summary: Print in capitals }
examples:
  - run: greet world --shout

2.1 Root fields

FieldTypeMeaning
cmdspecstringRequired. The specification version: "0.1".
infoInfoRequired.
syntaxSyntaxHow the parser reads a command line. Defaults to getopt_long’s behaviour.
environmentEnvironmentVariable[]Variables read that no option is bound to.
filesFile[]Files read or written on the program’s own account.
componentsComponentsDefinitions referenced by $ref.

The root is also the program’s root command: every command field except name and aliases may appear on it. A program without subcommands is a document with no commands.

2.2 Info

FieldTypeMeaning
namestringRequired. The program’s name.
versionstringRequired. The version of the program this document describes. A document describes one version.
binstringThe executable a shell runs, when it differs from name.
summarystringOne line.
descriptionstringCommonMark.
homepageURI
license{ name, identifier, url }identifier is an SPDX expression.
contact{ name, email, url }

2.3 Text

Every summary is one line of plain text with no trailing full stop, written to be read in a table. Every description is CommonMark and MAY be several paragraphs. A consumer that shows one SHOULD show the summary in lists and the description on the item’s own page.

2.4 Extensions

Every object except a $ref MAY carry fields whose names begin with x-. Their meaning is not defined here; a consumer MUST ignore extensions it does not understand. Any other unknown field makes a document invalid, so a misspelt field is an error rather than a silent no-op.

3. Syntax

syntax says how the program’s parser reads a command line. Every field has a default, and the defaults are GNU getopt_long’s behaviour, so a document for a conventional program omits it.

FieldTypeDefaultMeaning
bundlingbooleantrueSingle-character options combine: -abc is -a -b -c.
shortValueattached | separate | bothboth-ofile, -o file, or either.
longValueequals | separate | bothboth--out=dir, --out dir, or either.
terminatorstring | null"--"The word after which nothing is read as an option; null when there is none.
interspersedbooleantrueOptions may follow positional arguments.
abbreviationbooleanfalseA long option may be shortened to any unambiguous prefix.
precedence(flag | env | config | default)[]all four, in that orderWhere an option’s value comes from, highest priority first.

A single-character option is one whose spelling is a - and one character. Only those bundle, and only those follow shortValue; every other spelling follows longValue.

4. Commands

FieldTypeMeaning
namestringRequired (except on the root). The word that selects this command. It may begin with - — dotnet’s legacy dotnet new --list is a command, not an option — as long as no option of the same command has that spelling.
aliasesstring[]Other words that select it.
summary, descriptionstring
argumentsArgument[]Positional arguments, in the order they are written.
optionsOption[]
commandsCommand[]Subcommands.
subcommandRequiredbooleanThe command does nothing on its own; a subcommand must be named.
plugins{ executable, summary }An unknown subcommand runs another executable. executable contains {name}: git-{name}.
constraintsConstraint[]
exitsExits
ioIO
effects, idempotent, interactive, longRunningBehaviour.
examplesExample[]
groupstringThe heading this command is listed under in help.
deprecated, since, stability, hiddenLifecycle.

A command is addressed by its path: the program’s name followed by each command name, separated by single spaces — git remote add.

5. Arguments

FieldTypeDefaultMeaning
namestringRequired. How the argument is referred to in a usage line, a constraint and prose. Never typed on a command line. It does not begin with - or a space, so it cannot be mistaken for an option, but it may contain spaces: PROJECT | FILE.
summary, descriptionstring
valueValuea string
requiredbooleanfalse
variadicbooleanfalseTakes one or more words, or zero or more when not required.
min, maxintegerBounds on the words a variadic argument takes.
restbooleanfalseTakes every remaining word verbatim, including words that look like options: another program’s command line (kubectl exec pod -- sh -c …). Implies variadic.
afterTerminatorbooleanfalseMay be separated from what precedes it by the terminator, and must be when it could be mistaken for something else (git log -- path).
envstring[]Variables that supply the value when it is not given.
scopedOptionsOption[]See §6.3.
deprecated, since, hidden

In a valid document, a required argument MUST NOT follow an optional or variadic one unless it is afterTerminator, since the terminator then marks where it starts; and nothing follows an argument with rest.

6. Options

FieldTypeDefaultMeaning
namespellingRequired. The canonical spelling, exactly as typed: --output, -o, -name, +x, /q.
aliasesspelling[]Other spellings of the same option.
summary, descriptionstring
valueValueWhat the option takes. An option with neither value nor values is a switch.
valuesValue[]What it takes when followed by several words, in order: jq’s --arg name value. Never with value.
variadicbooleanfalseIts value takes one or more words: --property a=1 b=2. Zero or more when the value is optional.
min, maxintegerBounds on the words a variadic value takes.
qualifier{ separator, value }A suffix narrowing what it applies to: ffmpeg’s -c:v.
requiredbooleanfalse
repeaterror | last | list | countlastWhat giving it more than once does. count is for switches: -vvv.
repeatMin, repeatMaxintegerHow many times a list option must or may be given.
negationspelling[]Spellings that undo it: --no-color turns a switch off, --no-author discards a value given earlier.
inheritedbooleanfalseAccepted by every command below the one that declares it. A command that declares an option with the same name replaces the inherited one, for itself and the commands below it.
envstring[]Variables that supply the value when it is not given, first match wins.
configstringThe key in the program’s config files that sets it, dots separating nested keys.
secretbooleanfalseThe value is a credential: prefer the environment variable, and never echo it.
effectsEffect[]The command’s effects instead, whenever the option is given with a value other than its default.
longRunningbooleanfalseGiven, the command no longer returns on its own: --follow, --watch.
scopedOptionsOption[]See §6.3.
groupstringThe heading it is listed under in help.
deprecated, since, stability, hidden

A spelling is written exactly as typed, prefix included. Spelling options literally, rather than as a name plus a prefix rule, is what lets one format describe --output, -name (find), -filter_complex (ffmpeg), +x (set) and /q (Windows). No two different options visible to the same command — its own, its scoped ones and those it inherits — may share a spelling. One definition listed in several places, as a scoped option often is, is one option.

6.1 Values

A Value’s constraint keywords are JSON Schema’s, so an argument or option converts to a tool’s input schema without translation.

FieldTypeDefaultMeaning
namestringThe placeholder in a usage line: file, n, when.
typestring | integer | number | booleanstring
formatstringWhat a string denotes. Known formats: path, file, directory, url, email, date, date-time, duration, size, regex, glob, json, key-value, command. Others are allowed and mean nothing to a consumer that does not know them.
enumscalar[]The only values accepted.
defaultscalarThe value when none is given.
defaultDescriptionstringThe default when it is computed: “the number of CPUs”.
minimum, maximum, pattern, minLength, maxLengthAs in JSON Schema.
optionalbooleanfalseThe option may be given without its value: --color or --color=always. syntax says which forms can carry one.
separatorstringOne word carries several values: --tags a,b,c.
stdiobooleanfalseThe word - means standard input or output rather than a file of that name.
valuesFromstringA command whose output, one value per line, lists the values currently valid. See §6.4.

6.2 Referring to an option

Wherever a field refers to an option — constraints, selectedBy, interactive.disable — it writes the option’s canonical spelling, optionally followed by = and a value: --output=json, -i=-. This is a reference, not a command line, and is written the same way whatever syntax says. Arguments are referred to by name.

6.3 Scoped options

Some programs apply an option to the next operand rather than to the whole run. ffmpeg’s -c:v libx264 out.mp4 sets the codec for that output only, and -ss 10 -i in.mp4 seeks that input only. scopedOptions on an argument or on an option lists the options that, written before it, apply to it alone. A scoped option MAY appear in several lists.

6.4 valuesFrom

valuesFrom names a command that lists valid values: kubectl get namespaces -o name. A consumer MUST NOT run it without the user’s consent, and a document MUST NOT rely on it being run. It exists because “the valid values are whatever this prints” is the most useful thing a reference page, a shell or an agent can be told about a value that no static document can enumerate.

7. Constraints

Each constraint has exactly one of these forms. Every name in one is an option reference or an argument name visible to the command.

FormMeaning
exclusive: [a, b, …]At most one of these.
oneOf: [a, b, …]Exactly one.
anyOf: [a, b, …]At least one.
when: x, requires: [a, …]Whenever x is given, all of these are too. x MAY carry a value: --kind=incorrect.

Any constraint MAY carry a summary for the message a parser prints when it is broken.

8. What running a command does

8.1 Exit status

exits maps an exit status — a decimal number from 0 to 255, or * for every non-zero status not listed — to { meaning, description, retryable }. meaning is one line telling the caller what happened. retryable: true says running the same command again, unchanged, may succeed.

A command inherits every entry of its ancestors’ exits and MAY override one. So the root declares what every command means by 0, 1 and 2, and docspack ask adds 3 and 4.

8.2 Standard streams

io has stdin, stdout and stderr, each a list of { mediaType, summary, selectedBy, schema }. More than one entry means the stream carries different things depending on selectedBy, an option reference; the entry without one is the default. schema is a JSON Schema for the content, inline or as a $ref to a file or URL.

yaml
io:
  stdout:
    - { mediaType: text/plain }
    - { mediaType: application/json, selectedBy: --output=json, schema: { $ref: ./pod.schema.json } }

8.3 Behaviour

FieldTypeMeaning
effectsEffect[]What running the command can change. [] means nothing beyond its output; absent means not stated, and a consumer MUST NOT read absence as safety.
idempotentbooleanRunning it twice with the same input leaves the same state as running it once.
interactive{ when, disable }When it prompts — never, tty (only on a terminal) or always — and the option reference that answers every prompt.
longRunningbooleanIt does not return on its own: a server, a watcher.

An Effect is one of:

EffectMeaning
readReads files or state beyond its arguments.
writeCreates or changes local state.
destructiveRemoves something, or changes it in a way that cannot be undone.
networkTalks to another machine.
execRuns other programs or user-supplied code: hooks, scripts, a container’s command.

effects describes the command as invoked with no options. An option’s own effects replaces the command’s whenever that option is given with a value other than its default, which is how --dry-run and --force are described.

8.4 Examples

examples is a list of { run, summary, output, exit }. run is the command line exactly as typed. output and exit (default 0) are what it prints and returns. An example with output is a test: a tool MAY run it and compare.

9. Environment and files

environment lists variables the program reads that no option is bound to — NO_COLOR, GIT_EDITOR — each { name, summary, description, value, secret }. A variable bound to an option is declared on the option’s env instead, and not here.

files lists { path, role, format, summary }. path may use ~ for the home directory and $VAR for a variable. role is config, data, cache, state or log. Config files are listed in the order they are consulted, and an option’s config key refers to them.

10. Components and references

components holds options, arguments and schemas, each a map from a key to a definition. Wherever an option or argument may appear, { $ref: "#/components/options/<key>" } (or …/arguments/<key>) MAY appear instead and means exactly the definition it names. A reference MUST resolve. A referenced option keeps its inherited value, so a shared definition is shared, not re-declared.

11. Lifecycle

FieldTypeMeaning
deprecatedboolean | { since, use, summary }use is what to type instead.
sincestringThe program version that introduced it.
stabilityexperimental | stableDefault stable.
hiddenbooleanAccepted, but left out of help. A document SHOULD still describe hidden options; omitting them makes a document wrong about what the program accepts.

12. Validity

A document is valid when it validates against the schema and all of these hold. A validator MUST report every rule that fails, not only the first.

  1. Every $ref resolves.
  2. No two different options visible to one command share a spelling, counting aliases and negations.
  3. No two subcommands of one command share a name or an alias, and none shares one with an option that command accepts.
  4. No two arguments of one command share a name.
  5. A required argument does not follow an optional or variadic one unless it is afterTerminator, and nothing follows a rest argument.
  6. min and max appear only on a variadic argument or option, variadic only on an option with a value, and repeatMin and repeatMax only on an option whose repeat is list.
  7. An option with repeat: count has no value.
  8. Every name in a constraint, every selectedBy and every interactive.disable refers to an option or argument visible to that command.
  9. A command with subcommandRequired has subcommands or plugins.
  10. A required option has no negation.

13. Discovery

Two conventions let a tool find a program’s document without being told where it is. A program SHOULD follow at least one.

  • In a package. An npm package’s package.json MAY carry "cmdspec": "<path>", relative to the package root, naming the document for its bin. A package with several executables uses an object: "cmdspec": { "<bin>": "<path>" }. The document’s info.version MUST equal the package’s version.
  • From the program. A program MAY print its document as JSON when run with --cmdspec, and exit 0. It MUST NOT do anything else when given that option.

14. Relationship to other formats

Anything OpenCLI 0.1 or Usage can say converts into a cmdspec document without loss, and converts back to the same document.

  • OpenCLI. What both formats can say is mapped field to field. An OpenCLI field cmdspec has no place for is kept on the same object as x-opencli-<field>. Going the other way, what OpenCLI cannot say is written into the OpenCLI object’s own metadata, under the name cmdspec, so the result is valid OpenCLI and comes back unchanged.
  • Usage. Usage mirrors clap and has some eighty node properties. Those with a cmdspec field are mapped; every other property and child node is kept as written in x-usage on its object, and written back. complete also becomes valuesFrom or a format, and an implicit clause — flags scoped to the operand that ends each repetition — becomes an argument with scopedOptions. Usage has no extension mechanism, so converting cmdspec to Usage loses what Usage cannot say; a converter MUST report what it dropped.

OpenCLI 0.1 leaves one thing open that a converter must decide: its arity.maximum has a default of 1, and an absent maximum is also described as unlimited. This specification reads no arity as exactly one value and an arity without maximum as unbounded, which is how OpenCLI’s published examples use it.

The mappings are part of the reference implementation, @docspack/cmdspec, and are tested in both directions on published documents.

The specification is CC BY 4.0; the schema and the examples are MIT. The validator, the converters and the renderers are @docspack/cmdspec.

The same ranking docspack search uses, over the same 15 documents.