Generate a carapace completion spec from any commander.js-based command-line tool by parsing its --help output recursively. Point it at a binary, get a carapace-spec YAML in your carapace specs directory.
It exists because carapace bridges clap, cobra, click, yargs, argcomplete, and urfave, but not commander.js, and commander ships no machine-readable completion. Much of the Node CLI ecosystem (including claude) is built on commander, so this fills exactly that gap. One reusable binary instead of a folder of per-tool scripts, and zero config: just name the tool.
cargo install --git https://github.com/futuun/carapace-commander
# or, from a clone:
cargo install --path .You'll need carapace installed, and the target CLI on your PATH.
carapace-commander claude # writes <config-dir>/carapace/specs/claude.yaml
carapace-commander pm2 # any commander.js CLIThen claude <TAB> completes its flags and subcommands. The first time you generate a spec for a command carapace didn't already know, start a new shell so carapace registers it (it loads the specs directory at startup); later updates to that command are picked up live.
The spec directory follows carapace's own layout (macOS ~/Library/Application Support, Linux $XDG_CONFIG_HOME or ~/.config, Windows %AppData%), under carapace/specs/.
For a CLI you invoke through a launcher rather than directly:
carapace-commander npx cypress # writes npx.yaml with cypress nested inside
carapace-commander npx -y pm2 # accumulates: npx.yaml now has cypress and pm2npx cypress <TAB> then completes cypress's own subcommands and flags. The wrapped command is nested under the launcher's spec because that's the command you actually type. Re-running is a no-op while the package's version is unchanged, and running carapace-commander npx on its own won't clobber the packages you've nested.
Re-running is cheap: the spec header records the tool's version, and the run short-circuits when it still matches <cmd> --version. That makes it safe to call on every shell or editor startup. For example, a Claude Code SessionStart hook that keeps claude's completion fresh:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "carapace-commander claude",
"timeout": 60
}
]
}
]
}
}When a regen actually changes the surface (the tool updated), it prints a short changelog of the commands and flags added or removed.
A small four-stage pipeline behind an always-on version gate:
- Fetch: run
<cmd> [subpath...] --helpthrough a pipe (commander wraps to its 80-column non-TTY default; the parser folds the wrapping). - Parse: turn the commander help dialect (
Options:/Commands:headers,<arg>/[arg]metavars,name|aliasaliases, width-wrapped continuation lines) into a command tree, recursing into each subcommand. - Enrich: attach value completions from three generic, config-free layers: machine-readable
choices: "a", "b"clauses; the metavar shape (<dir>for directories,<file>/<path>for files); and trailing prose enums like(low, medium, high). - Emit: serialize the tree to YAML under a
#header and write it to the specs dir.
The honest cost of staying config-free: values that never appear as an enum in the help (e.g. --model, documented as prose) get no suggestions. The flag still completes, just without value hints.
- The target must be a commander.js CLI. carapace-commander detects this by commander's default
display help for commandand refuses otherwise (other frameworks already have carapace bridges). - carapace-commander installs its own completion spec on first run, so
carapace-commander <TAB>completes commands on yourPATH.
cargo test
cargo clippy --all-targetsThe --help fetch is injected as a closure, so the parser is tested against fixture strings without shelling out (see the tests in src/parse.rs).
The carapace-commander source code is licensed under MIT.