| name | cac |
| description | Build and maintain command-line interfaces with CAC. Use when a task involves the `cac` package, authoring or reviewing CLI apps, defining commands/options/arguments, generating help or version output, handling parse errors, or explaining CAC-specific behavior such as default commands, boolean negation, variadic args, dot-nested options, and `--` passthrough. |
CAC
Use CAC to build small, expressive CLIs with a compact command grammar:
import { cac } from 'cac'
const cli = cac('my-cli')
cli
.command('build <entry> [...files]', 'Build project files')
.option('--minify', 'Minify output')
.action((entry, files, options) => {
console.info({ entry, files, options })
})
cli.help()
cli.version('1.0.0')
cli.parse()
Install with pnpm add cac.
Build CLIs This Way
- Choose the command shape first.
- Use global parsing only for tiny CLIs.
- Use
cli.command(...) for verb-based CLIs.
- Use a default command when the main behavior should run without an explicit verb.
- Model positional arguments before options.
<arg> is required.
[arg] is optional.
... is variadic and may only appear on the final positional argument.
- Attach options at the narrowest useful scope.
- Use
cli.option(...) for flags shared by multiple commands.
- Use
command.option(...) for command-specific flags.
- Add
help() and version() for user-facing tools.
- Use plain
cli.parse() for simple flows; use cli.parse(argv, { run: false }) plus await cli.runMatchedCommand() when you need centralized or async error handling.
Syntax and Parsing
| Need | CAC form | Result |
|---|
| Required arg | build <entry> | action receives entry |
| Optional arg | build [entry] | action receives undefined when omitted |
| Variadic arg | build <entry> [...rest] | final action arg is an array |
| Boolean flag | --open | accepts both --open and --no-open automatically |
| Optional option value | --scale [level] | value or true |
| Required option value | --out <dir> | missing value throws |
| Global option | cli.option('--cwd <dir>', ...) | available to all commands |
| Command option | command.option('--watch', ...) | scoped to one command |
| Repeated option | --include a --include b | becomes ['a', 'b'] |
| Dot-nested option | --env.API_SECRET xxx | becomes options.env.API_SECRET |
| Passthrough args | -- pnpm test | stored in options['--'] |
Kebab-case option names are read in camelCase:
cli.option('--clear-screen', 'Clear screen')
Only the first segment is camel-cased, so --env.API_SECRET becomes options.env.API_SECRET.
Bare booleans already accept negation:
cli.option('--open', 'Open browser')
Declare an explicit negated option only when the negated form should appear in help, should default to true, or must pair with a required-value option:
cli
.option('--no-config', 'Disable config file')
.option('--config <path>', 'Use a custom config file')
For array-valued options, prefer repeated flags:
--include a --include b --include c
CAC parses that as:
{ include: ['a', 'b', 'c'] }
Do not model array input as --include a,b,c and split the string manually unless the CLI intentionally documents comma-separated syntax. If consumers should always receive an array, use type: [] so even one --include a becomes ['a'].
Scenario References
Use the reference that matches the user's task:
Centralized Error Handling
try {
cli.parse(process.argv, { run: false })
await cli.runMatchedCommand()
} catch (error) {
console.error(error)
process.exit(1)
}
Important Behaviors
- When a matched command has an action, CAC validates unknown options, missing option values, missing required args, and unused extra args.
- Use
allowUnknownOptions() only when forwarding another tool's flags.
- Use
ignoreOptionDefaultValue() when help should show defaults but parsed output should stay sparse.
- Variadic positional args arrive as arrays.
options['--'] is reserved for everything after a double dash.
command.alias(name) accepts plain aliases; use alias('!') to mark a named command as the default command.
- Listen to
command:<name>, command:!, and command:* when you need matched/default/unknown-command hooks.
API Surface
CLI
cac(name?)
cli.command(name, description?, config?)
cli.option(name, description, config?)
cli.help(callback?)
cli.version(version, customFlags?)
cli.usage(text)
cli.example(example)
cli.parse(argv?, { run }?)
cli.runMatchedCommand()
cli.outputHelp()
cli.outputVersion()
cli.parse() returns { args, options } and also sets:
cli.rawArgs
cli.args
cli.options
cli.matchedCommand
cli.matchedCommandName
Command
command.option(name, description, config?)
command.action(callback)
command.alias(name)
command.allowUnknownOptions()
command.ignoreOptionDefaultValue()
command.example(example)
command.usage(text)
Option config supports:
{
default?: any
type?: any[]
}
Use type: [] to force array output and type: [String] to transform each value.