Releases: nerima-lisp/cl-cli
Release list
v1.0.1
Three renderer bugs that 1.0.0 shipped with, found by an adversarial audit that
reported after the tag went out. All three were reproduced against the real
consumer — elvish, zsh, and GitHub's own cmark-gfm — before and after the
fix, and each now has a regression test that fails on the old behavior.
Fixed
-
Elvish dynamic completion threw on every Tab. The renderer emitted
e:'app' __complete ...; elvish'se:external-command namespace accepts
only a bare word, so quoting the app name parsed as the bareworde:
concatenated with a string, producing the plain string"e:app"— which
elvish then refused to call (command must be callable or string containing slash). Any app with a:completeoption was affected. Now emits
(external 'app'), which takes a real string argument and so also stays
correct for a name that needs quoting — dropping the quotes would have fixed
the common case and broken that one.Two things let this ship:
elvish -compileonlycannot see a runtime value
error, andtests/cases-dynamic-completion.lispasserted the broken string
verbatim. The new check intests/cases-shell-verification.lispruns the
generated completer and asserts on what it returns. -
zsh truncated any candidate value containing a colon.
_describesplits
each entry at the first unescaped colon, and only the description half was
being sanitized. A candidatehost:8080completed ashost, with8080
folded into the description — a silently wrong insertion, on exactly the
host:portshape a candidate list tends to carry. The value half is now
backslash-escaped (it cannot be sanitized the way the description is, since
it is the text inserted on the command line). Only affected options whose
candidates carry descriptions; a bare:choiceslist takes thecompadd
path, which was already correct. -
Markdown tables broke on a
|in a value name.%md-inline-code
renders the first column of every option and argument table but did not
escape pipes, and a GFM cell ends at the first unescaped|even inside a
code span.--fmt <json|yaml|toml>split its row across the wrong cells,
destroyed the code span, droppedtoml>, and lost the description column
entirely. Verified againstcmark-gfm. The escape is scoped to table cells;
the per-command heading, the other caller, must not have it.
Changed
- The real-shell verification watchdog goes from 10s to 60s. It is not a
budget -- it exists only so a wedged child cannot block the run -- and 10s
was smaller thanpwsh's cold start inside the Nix sandbox (0.2s outside
it), which turnednix flake checkintermittently red. The self-test that
pins the timeout mechanism uses its own short deadline, so it still proves
the watchdog fires. - The ECL check no longer preloads a newer ASDF. That was needed only for
cl-log-kit's ASDF >= 3.3.1 requirement, andcl-log-kitis no longer on
ECL's path since the test systems were split — so the check now runs exactly
theecl --norc --load tests/run-tests.lispline the documentation gives.
Documentation
docs/src/README.mdstill said "Status: Pre-1.0".RELEASING.mdanddocs/src/releasing.mdsaid:versionappears twice in
cl-cli.asd; the test-system split made it three, and a release that
followed the old instruction would have shipped a mismatched system.- README described the dynamic-completion mechanism backwards (it is bash,
zsh, PowerShell and elvish that match the token before the cursor; fish is
the one attaching per option), and both README and the guide claimed all six
shells pass the partial word — nushell does not, and asks for the whole list
instead, so a:completecallback sees an empty prefix there. CONTRIBUTING.mdpointed new tests attests/run-tests.lisp, which is the
loader; it now names thetests/cases-*.lispfile and which of the two test
systems a case belongs to, and warns against the assert-the-current-output
habit that let the elvish bug through.- README's constructor list omitted
define-app/define-command.
v1.0.0
The API is now covered by Semantic Versioning. What that promise does and does
not cover is written down in
docs/src/compatibility.md,
along with the supported-implementation matrix and the deprecation policy.
This release is mostly about earning that promise rather than adding features.
The breaking changes below are deliberately concentrated here, because 1.0 is
the last point at which they are free: an accessor family that could not be
read, a condition taxonomy that made a programmer's bug look like a user's,
and a machine-readable format with no version marker would all otherwise have
been permanent. Verification was rebuilt to match: nix flake check was
previously guaranteed to fail, the checks that run generated scripts through
real shells were almost entirely skipping, and no non-SBCL implementation could
compile the suite at all.
Added
- The positional accessor family is exported.
app-positionals,
command-positionals, andmake-positionalwere all public, so callers
legitimately receivedpositional-specstructs -- but not one of its slot
readers was exported, leaving them opaque unless you reached through
cl-cli::. All sixteen are public now, renamed from thepositional-spec-
conc-name topositional-to match theoption-/command-/app-
families they sit beside. Also newly exported:command-subcommandsand
command-default-command(nested subcommands are a documented feature and
app-default-commandwas already public),option-groupplus
option-group-members/-mode/-required-p(so a custom help renderer
can render "exactly one of ..." instead of degrading to pairwise
conflicts), andbuilt-in-option-specs(so it can see the synthesized
--help/--version). Between them, a third-party renderer can now
reproduce everything the built-in one prints. cli-missing-required-option, a subtype ofcli-missing-option-value
for the single most common CLI mistake there is: a required option never
supplied. It was previously indistinguishable from "--outputtyped with
nothing after it". Being a subtype, a handler that does not need the
distinction is unaffected.cli-response-file-error(with acli-response-file-error-path
reader), for the three@fileexpansion failures that previously signaled
a barecli-usage-error-- "your@args.txtis missing" deserves a
different message from "you mistyped a flag".- Docstrings on every exported condition class and slot reader. The
condition hierarchy is the primary integration point for a consumer CLI and
had no prose at all;cli-invalid-option-value-causein particular was
unguessable. tests/cases-public-api.lisp, which pins the exported symbol set in both
directions.:exportfails silently in the direction that matters -- a
misspelled name interns a dead symbol rather than signaling -- and no other
test would notice, since they all call these symbols from a package that
:usescl-cli. It also asserts that every exported condition is reachable
fromcli-errorand thatcli-invalid-specificationstays out of the
cli-usage-errorbranch.render-jsonnow emits aschemaVersionmember first, and exports
+json-schema-version+(currently1). A machine-readable format with no
version marker gives a consumer nothing to branch on; a tool written against
today's shape would silently misread a future one. Adding it now, at the
1.0 boundary, is the last point where it costs nothing. New members may
still appear in a minor release -- readers should ignore unknown members --
but a change that would break a reader of the previous shape bumps the
number. This changesrender-jsonoutput; a consumer that parses the
0.3.0 document and rejects unknown keys needs updating.docs/src/compatibility.md, stating what the version number promises
now that the project is on Semantic Versioning for real: the exported
symbols of theCL-CLIpackage are covered,cl-cli::internals and the
byte-for-byte text of rendered help/completion/man output are not, plus a
supported-implementation matrix and a deprecation policy.- Dedicated unit tests for the PowerShell, Nushell, and Elvish completion
renderers (tests/cases-completion-powershell.lisp,-nushell.lisp,
-elvish.lisp). Three of the six shipped renderers previously had no
assertions on what they emit at all -- only a performance benchmark and a
"does it parse" smoke check. - An Elvish leg in
tests/cases-shell-verification.lisp: the generated script
is compiled by realelvish -compileonly. Becauseelvishonly exposes its
edit:module interactively, the check bindsedit:to a stub namespace so
the generated code itself is what gets compiled; a negative-control case
asserts that a deliberately broken completer still fails, so the check
cannot pass vacuously.
Changed
cli-invalid-specificationis no longer acli-usage-error. A new
cli-errorroot splits the hierarchy in two:cli-usage-errorfor what the
user typed,cli-invalid-specificationfor what the programmer
declared. The idiomatic
(handler-case (run-app ...) (cli-usage-error (e) (print-usage) (exit 2)))
previously swallowed the developer's own malformed spec and answered it with
a usage message shown to the end user. If such an error does reach
run-app, it now exits with:error-exit-code(70,EX_SOFTWARE)
instead of:usage-exit-code(64,EX_USAGE) -- which is what a bug in
the program actually is. In practice the condition is raised while the spec
is being built, before parsing.
cli-error-app/cli-error-commandare the new names for the context
readers;cli-usage-error-app/-commandstill read the same slots.- The test suite is split into two ASDF systems.
cl-cli/testsis the
portable core;cl-cli/tests/shell-verificationadds the checks that shell
out to real tools and is the only half that needscl-process-kit. The
transitivecl-log-kitdependency hard-codessb-thread:*
(upstream #1), which
previously made the entire suite fail to compile on any non-SBCL
implementation. ECL now runs 637 tests green instead of not building.
tests/run-tests.lispprints which half it loaded, so a run covering less
than expected cannot be mistaken for a pass. - The fuzz suite and the benchmark budgets are now gated on the capability
each actually needs, rather than failing where it is absent: the fuzz suite
needscl-weave's:timeoutplatform capability, and the benchmark
thresholds are absolute milliseconds calibrated against SBCL. Both report as
skips with a reason. No threshold was widened and no assertion was removed. - The benchmark workloads are resized so contention on a shared CI runner
cannot fail them. One case measured 520ms idle and 2534ms against a 2000ms
budget inside a concurrentnix flake check-- a flaky gate, and an
intermittently red check gets read as noise, which is how a real regression
gets waved through. Each case now targets an idle median around a tenth of
its budget; the 2000ms convention itself is unchanged, since raising it is
the one response that keeps the test green while destroying what it
measures. nix flake checkis green again, and now means something it did not before.
Theeclcheck was previously guaranteed to fail, so CI onmainwas
permanently red. Alongside the split above:- the flake defines outputs for
aarch64-linux,x86_64-darwin, and
aarch64-darwinas well asx86_64-linux, sonix developand
nix flake checkwork on a macOS machine instead of silently checking
nothing; - the checks put
bash,zsh,fish,nushell,pwsh,elvish, and
mandoconPATH. The shell-verification cases skip themselves when a
tool is missing, so in CI they had been skipping almost entirely -- the
generated scripts were never actually run by the shells that consume
them. The SBCL suite is now 664 passed, 0 skipped; - the documentation build is a check too, so a broken docs link fails CI
rather than the Pages deploy.
- the flake defines outputs for
- CI runs the flake check on
aarch64-darwinas well asx86_64-linux, so
the macOS support the project claims is verified rather than assumed. consume-value-option(src/parser-option-consumption.lisp) had a
provably unreachable:flag-kind guard clause -- its sole call site is
the(:value :optional-value :key-value)case arm of
consume-long-option-token, so(option-kind spec)can never be:flag
inside its body. Removed as dead code; no observable behavior changed.- Bumped
cl-weave,cl-process-kit,cl-boundary-kit,cl-log-kit, and
cl-json-kitto their latest upstream revisions (cl-prologwas already
current). Verifiedcl-boundary-kit's new default 60-second process
timeout (previously unbounded when:timeoutwas omitted) doesn't affect
cl-cli: everyprocess-kit:runcall site in the test suite already
passes an explicit:timeout.
Security
expand-response-files(:expand-response-files t) had no bound on the
total bytes read across a response-file expansion -- only
+response-file-max-depth+limited nesting depth, not fan-out (many
files referenced at one depth) or a single huge file. A caller piping a
response-file path derived from untrusted input into an app that
opted into response-file expansion could exhaust memory. Added
+response-file-max-total-bytes+(8 MiB, generous for any legitimate
argument list) as a total-size budget checked after every file read;
exceeding it signalscli-usage-errorthe same way exceeding the depth
limit already did.
v0.3.0
Added
- A full MkDocs (Material) documentation site under
docs/, published to
GitHub Pages vianix build .#docsand.github/workflows/docs.yml.
Covers installation, a quick start, a per-topic guide (option values,
option relations, commands, validation, CLI behavior, shell completion,
documentation generation), an API reference, the migration guide, scope
and non-goals, and the project's governance documents. define-app/define-command(src/model-dsl.lisp): a declarative,
clause-based DSL overmake-app/make-command/make-option/
make-positional.:option,:positional, and:commandclauses replace
the nested:global-options (list ...)/:positionals (list ...)/
:commands (list ...)keyword arguments;:commandclauses nest the same
vocabulary for a command's own options, positionals, and subcommands, and
:commands-fromsplices in an already-built command list (e.g.
make-standard-commands, or another spec bound viadefine-command).
Purely additive -- the functional API is unchanged and remains
independently usable. See README's "Declarative DSL" section.- Adopted
cl-json-kitas a
test-only dependency (mirroringcl-process-kit's test-only role): the
json renderertest suite now parsesrender-json's own output back
throughcl-json-kit's independent reader as an additional correctness
oracle, catching structural/escaping defects that substring-matching the
writer's output can't. Not needed to usecl-cliitself.
Fixed
- Two README code examples called
run-appwithargvas a positional
argument instead of:argv, which signalsodd number of &KEY arguments
if copy-pasted as-is.
Changed
- The Nushell and Elvish completion renderers' quoting functions
(%COMPLETION-NUSHELL-QUOTE,%COMPLETION-ELVISH-QUOTE) and the
Markdown documentation renderer's prose-escaping functions
(%MD-ESCAPE-PROSE,%MD-SINGLE-LINE,%MD-MAX-BACKTICK-RUN, all in
src/doc-renderers-markdown.lisp) had the same double-nested
WITH-OUTPUT-TO-STRINGpattern found and fixed in bash/zsh/PowerShell's
quoting functions earlier in this changelog -- each ran its own escaping
pass over the already-built result of a separate control-character-
stripping pass, rather than folding both into one.%MD-SINGLE-LINE's
second pass turned out to be provably dead code once traced through
(its input can never contain the newline/return characters it was
checking for, since the upstream control-stripping pass already maps
them to a space), so it now simply delegates.%MD-MAX-BACKTICK-RUN
needed care: a character control-stripping drops entirely doesn't
break a backtick run in the final output, while one it maps to a
space does, so its merged single pass reproduces that distinction
exactly rather than just concatenating both transformations (see the
new regression test intests/cases-doc-markdown.lisp, which checks a
control character between two backticks counts as one run of 2 while a
newline between them counts as two runs of 1). Nushell and Elvish
completion rendering were separately profiled and found already fast
enough (tens of microseconds per call on a 330-option benchmark app)
that this fix's effect is not separately measurable there; it is applied
for consistency with the other renderers, not for a measured win. No
public API or observable output changed. - The PowerShell completion renderer (
RENDER-POWERSHELL-COMPLETION):
%COMPLETION-POWERSHELL-QUOTEfolds control-character-stripping and
single-quote-doubling into one pass instead of two nested
WITH-OUTPUT-TO-STRINGcalls (the same fix already applied to
%COMPLETION-SHELL-QUOTEand%COMPLETION-ZSH-ARGUMENTS-FIELD), and
%COMPLETION-POWERSHELL-COMMAND-OPTION-MAPnow builds each command's
option-array literal once and reuses it across every alias instead of
rebuilding it (re-quoting every token again) per alias. (Profiled all
four remaining completion renderers this round: nushell and elvish are
already tens of microseconds per call -- optimizing them further would
be immeasurable -- so only PowerShell needed work.) Measured on a
330-option/33-command benchmark app: PowerShell rendering is roughly 1.3x
faster. No public API or observable script output changed; see
tests/cases-parser-benchmark.lisp. - The fish completion renderer (
RENDER-FISH-COMPLETION) no longer
re-shell-quotes the same loop-invariant value on every iteration of a
loop it's constant across: the app name, each level's "already seen a
sibling command" condition, and each command's description are now
quoted once and reused, instead of being re-quoted once per command
alias, once per option candidate, or once percompleteline as
before. (Profiling on a 330-option/33-command benchmark app after fixing
the bash and zsh renderers, above, found fish was the next-most-expensive
renderer, dominated almost entirely by this redundant re-quoting rather
than the tree-copy pattern those two had; nushell and elvish were also
profiled and are already fast enough -- tens of microseconds per call --
that optimizing them further would be immeasurable.) Measured: fish
rendering on the same benchmark app is roughly 1.5x faster. No public API
or observable script output changed; see
tests/cases-parser-benchmark.lisp. - The bash and zsh completion renderers (
RENDER-BASH-COMPLETION,
RENDER-ZSH-COMPLETION) write directly into one shared output stream
instead of building a separate string per recursive subcommand node (each
of which a parent then copied again viaWRITE-STRING) and per
shell-quoted or array/case-label value (each of which was quoted into its
own string, joined into a second string, then copied a third time into a
parent's buffer). Profiling a 330-option/33-command benchmark app showed
this quote-then-join-then-copy pattern accounting for the large majority
of sampled render time in both renderers, dominated by repeated
WITH-OUTPUT-TO-STRINGsetup/copy overhead rather than the actual
shell-quoting logic.%COMPLETION-SHELL-QUOTE's core loop is now also
exposed as%COMPLETION-WRITE-SHELL-QUOTED, which writes straight to a
caller-supplied stream, with shared%COMPLETION-WRITE-CASE-LABELS/
%COMPLETION-WRITE-SPACE-JOINED-QUOTEDwriters built on top of it used by
both renderers (%COMPLETION-BASH-ARRAY-LITERALand
%COMPLETION-CASE-LABELS, now dead, were removed). Zsh's
%COMPLETION-ZSH-ARGUMENTS-FIELD(used to build every_arguments
bracket/placeholder field) also folded its control-character-stripping and
bracket-blanking into one pass instead of two nested
WITH-OUTPUT-TO-STRINGcalls, the same fix already applied to
%COMPLETION-SHELL-QUOTE. Measured on the 330-option benchmark app: bash
rendering is roughly 1.5x faster and zsh rendering roughly 1.5x faster
than before this round of changes. No public API or observable script
output changed; seetests/cases-parser-benchmark.lisp.
Measured on the same 330-option benchmark app: bash completion rendering
is roughly twice as fast as before this round of changes (and faster
still relative to the pre-caching baseline). No public API or observable
script output changed; seetests/cases-parser-benchmark.lisp. PARSE-ARGVis dramatically faster, especially across repeated calls
against the sameAPP(a REPL, a long-running server, or simply a hot
loop).MAKE-APPnow precomputes and caches, once per app/command scope,
the built-in-augmented option list, its name/alias lookup table, and the
command-name/alias lookup table -- previously all three were rebuilt from
scratch (including re-runningMAKE-OPTIONfor the built-in--help/
--versionspecs) on every singlePARSE-ARGVcall. Three option-
relationship validators (VALIDATE-CONDITIONAL-REQUIREMENTS,
VALIDATE-REQUIRED-OPTION-GROUPS,VALIDATE-INCLUSIVE-GROUPS) now skip
building their lookup tables entirely when the app declares none of the
corresponding feature (conditional requirements / option groups), mirroring
VALIDATE-OPTION-RELATIONSHIPS' existing early exit. The per-parse
accumulating-option-value hash table is now allocated lazily, on the first
value that actually needs it, instead of unconditionally on every call.
Measured on a representative subcommand-dispatch benchmark: ~4.5x faster
for repeated parses against one app, up to ~15x for a flag-only app with
no subcommands.BUILT-IN-OPTION-SPECS-- also called directly by every
help and completion renderer, not just the parser -- is cached the same
way, so generating help text or a shell-completion script no longer
rerunsMAKE-OPTIONfor the built-ins either. No public API or
observable behavior changed; seetests/cases-parser-benchmark.lisp.CANONICAL-OPTION-NAME(the hot path for every long-option token during
parsing) now strips a leading-/--and downcases the remainder in one
pass instead of two separate allocations (SUBSEQthen
STRING-DOWNCASE). Shell-completion rendering is also faster on apps
with many options:%COMPLETION-SHELL-QUOTE(used by the bash/zsh/fish
renderers) now strips control characters and quote-escapes in a single
pass rather than two nested string streams, and three
REMOVE-DUPLICATEScall sites switched from:TEST #'STRING=to
:TEST #'EQUAL-- semantically identical for strings, but lets SBCL use
its hash-table-based dedup instead of an O(n^2) pairwise scan. Measured
on a 330-option benchmark app: shell-completion rendering is noticeably
faster; no observable behavior changed.cl-clino longer depends oncl-prologat runtime. Option-relation
validation (:requires,:requires-any-of,:conflicts-with) is now a
plain in-memory adjacency graph (src/option-relations.lisp)...
v0.2.0
Added
- Dynamic (runtime) completion now works in all six generated shells. Previously
only bash, zsh, and fish shelled out to the__completecallback for a
:completeoption; PowerShell, nushell, and elvish offered only a static
candidate pool. PowerShell now inspects the token before the cursor, elvish
branches on the previous word, and nushell attaches a per-flag custom
completer — each invokingapp __complete KEY <partial>. The generated
scripts were verified against realpwsh,nu, andelvish. - Terminal-aware help:
print-app-help,print-command-help, andrun-app
now accept:color :autoand:width :auto.:color :autohonorsNO_COLOR
andCLICOLOR_FORCEand otherwise enables ANSI styling only when the target
stream is a terminal (isatty on SBCL; environment-only elsewhere);:width :autoreads$COLUMNS. Explicitt/nil/ integer values still force the
decision, and the defaults remainnilso existing behavior is unchanged. option-value-sourcereports the provenance of an option value —
:command-line,:env,:config, or:default(ornilwhen unset) — the
analogue of clap'sArgMatches::value_source, so a handler can tell an
explicit choice apart from a fallback.invocation-option-sourcesexposes the
raw map.- Required options are now spelled out in the one-line usage synopsis (for
exampleUsage: demo run [options] --config <FILE>) instead of being hidden
inside the[options]catch-all; non-required and hidden options stay in the
catch-all. run-appaccepts:usage-exit-codeand:error-exit-codeto override the
exit codes returned for usage errors (default64,EX_USAGE) and other
unhandled errors (default70,EX_SOFTWARE).- Dynamic (runtime) completion: an option or positional may carry a
:complete
function(lambda (partial) => candidates). Addmake-complete-command(or
make-standard-commands :include-dynamic-p t) and the generated bash, zsh, and
fish completions call back into the program (app __complete KEY PARTIAL) at
completion time to offer runtime candidates — no re-parsing of the command
line. A candidate may be a plain value or a(value . description)cons
(emitted tab-separated; fish shows the description).render-complete-reply
exposes the same lookup directly, andoption-completereads the function. - Bash, zsh, and fish completion now complete nested subcommands at every level
of a command tree (app remote <TAB>offersadd/remove), with each
command's options — and its ancestors' — completing once that command is on
the line. All three were verified by exercising the generated scripts under the
real shells, not just syntax-checking them. - The built-in
completionanddocscommands now carry
:completion-candidates, so generated shell completion suggests the shell
names (demo completion <TAB>) and documentation formats
(demo docs <TAB>) while the parsers still accept aliases (pwsh,nu,
md,roff). :value-hint(:file/:dir) onmake-option(value-bearing) and
make-positional: the generated bash, zsh, and fish completions offer file or
directory path completion at the hinted slot (compgen -f/-d,_files/_files -/,
the fish directory completer). Surfaced in help ("expects a file/directory")
and JSON. Readeroption-value-hint.- Positional value completion:
make-positionalnow accepts
:completion-candidates(like options), and all six shell completers
(bash, zsh, fish, powershell, nushell, elvish) offer a positional's
:choices/:completion-candidatesvalues at its argument slot. Positional
candidates also appear in the JSON schema. - Opt-in help description word-wrapping:
print-app-help,print-command-help,
andrun-appaccept:width Nto word-wrap option/command/positional
descriptions to column N, with continuation lines aligned under the
description gutter. No terminal-width detection; off by default. :auto-helponmake-app(defaultt): passnilto suppress the built-in
-h/--helpflag for a CLI that manages its own help or forwards--help
to a wrapped tool. Ahelpcommand added viamake-standard-commandsis
unaffected. Readerapp-auto-help.- Greedy variadic
:value-counton a:valueoption::+(one or more) and
:*(zero or more) consume every following token up to the next option-like
token (--files a b c), storing the list.:+requires at least one value.
Help shows<NAME>...and JSON emits"+"/"*". :see-also,:authors, and:manual-dateonmake-app: rendered as the SEE
ALSO / AUTHORS sections and the.THdate ofrender-manpage, and exposed in
render-json(the first two).render-manpagealso emits standard EXIT STATUS
(0 / 64 / 70) and ENVIRONMENT (env-backed options) sections, and the generated
page passesmandoc -T lintcleanly. Readersapp-see-also,app-authors,
app-manual-date.:required-if/:required-unlessonmake-option: conditional
requirements.:required-ifmakes an option mandatory when any listed option
is present;:required-unlessmakes it mandatory unless any listed option is
present. Both signalcli-missing-option-value, are hidden-target safe, and
render in help. Readersoption-required-if,option-required-unless.:require-commandonmake-app: parsing fails withcli-unknown-command
(listing the available commands) unless a subcommand is dispatched, expressing
a "subcommand mandatory" contract. Readerapp-require-command.- Invalid
:choicesvalues (for options and positionals) now append a
nearest-match "Did you mean: ...?" suggestion to the error, reusing the same
suggestion machinery as unknown options and commands. :value-count Non a:valueoption: consume exactly N following tokens as a
parsed list (--point 1 2=>(1 2)); too few remaining tokens signal
cli-missing-option-value, and with:multiple-peach occurrence contributes
its own N-element list. Readeroption-value-count.:kind :key-value: each occurrence parseskey=value(a barekeyrecords
valuet) and accumulates the pairs into an alist, so-D a=1 -D b=2reads as
(("a" . "1") ("b" . "2"))— the compiler-define / docker-env shape.inclusive-group: an all-or-none option group (if any member is supplied, all
must be), the complement ofexclusive-group; rendered as "all or none of" in
help and signallingcli-missing-dependent-optionon a partial set.:allow-negative-numbersonmake-app: keeps a token that looks like a
negative number (-5,-1.5) from being parsed as a short-option cluster, so
it can serve as a positional or option value. Reader
app-allow-negative-numbers.:grouponmake-option: a help-section label that groups related options
under their own heading in help output (mirroring a command's:group) and is
exposed in JSON. Readeroption-help-group.- Response-file expansion:
make-app :expand-response-files texpands a@path
argument into the whitespace-separated arguments read from that file before
parsing (recursively;@@escapes a literal leading@; a missing file is a
usage error), mirroring the gcc/clap convention. Reader
app-expand-response-files. :min-count/:max-counton a restmake-positional: constrain how many
values it collects (too few signalscli-missing-positional, too many
cli-unexpected-argument); shown in help and JSON.:default-commandonmake-command: a command with:subcommandscan name a
default subcommand dispatched when no subcommand token is present, mirroring
the app-level:default-command.- Opt-in colored help:
print-app-help,print-command-help, andrun-app
accept:color tto wrap headings and names in ANSI styling. There is no
automatic terminal detection, so callers stay in control; off by default. - Nested subcommands:
make-commandaccepts:subcommands(a list of command
specs), so a command dispatches like a mini-app (git remote add). The next
non-option token selects a subcommand; the parent command's options stay
available to the whole subtree and global counters accumulate across the path.
Parsing, help (path-qualified usage plus a subcommand list), the man/Markdown/
JSON renderers, andrun-appdispatch all recurse to arbitrary depth. The new
invocation-command-pathaccessor returns the full root-to-leaf chain. :choicesonmake-positional: restricts a positional to a closed set,
validated before the parser runs (cli-invalid-positional-valueon mismatch)
and shown in help and JSON output, matching option:choices.:help-footeronmake-command: trailing help/epilog prose for a command,
mirroring the app-level:help-footerand falling back to it; reflected in the
man/Markdown/JSON renderers. Readercommand-help-footer.:allow-abbreviated-optionsonmake-app: opt-in GNU-style unambiguous
long-option prefix matching (--verbfor--verbose); an ambiguous prefix
signalscli-unknown-optionlisting the matches. Off by default to preserve
strict exact-match parsing. Readerapp-allow-abbreviated-options.render-elvish-completionandelvishsupport inrender-completionand the
completioncommand, bringing built-in completion coverage to six shells
(bash, zsh, fish, powershell, nushell, elvish).:configonparse-argvandrun-app: an optional plist of
option-key -> valuethat supplies option defaults, slotting into the
precedence chain below CLI arguments and environment variables but above a
literal:default. Values are coerced like defaults (a string runs through
the option parser, a list is spread, a delimited option splits a string), so a
caller can layer in configuration loaded from a file without forking the
parser.:deprecatedonmake-optionand `make-com...