Skip to content

Arguments & Options

Arguments and options are declared on the command, and the type of run(args) is inferred from that declaration. Nothing else needs to be written down.

Arguments are positional, matched in the order they are declared. name and description are required; type defaults to string.

import { cli, z } from 'clibuilder'
cli({ name: 'app', version: '1.0.0' }).default({
arguments: [
{ name: 'name', description: 'your name' }
],
run(args) {
// args.name: string
this.ui.info(`hello, ${args.name}`)
}
})

An argument typed as an array is variadic — it collects every remaining positional value:

cli({ name: 'app', version: '1.0.0' }).command({
name: 'cat',
description: 'print files',
arguments: [
{ name: 'files', description: 'files to print', type: z.array(z.string()) }
],
run(args) {
// args.files: string[]
for (const file of args.files) this.ui.info(file)
}
})
Terminal window
$ app cat a.txt b.txt c.txt

Options are declared as a record keyed by option name. description is required; type defaults to boolean | undefined, which is what you want for a flag.

cli({ name: 'app', version: '1.0.0' }).default({
options: {
'no-progress': { description: 'disable progress bar' }
},
run(args) {
// args['no-progress']: boolean | undefined
if (args['no-progress']) this.ui.info('progress bar disabled')
}
})

Option values are coerced to the declared type, and a value that doesn’t fit is a usage error.

options: {
port: { description: 'port to listen on', type: z.number() }
}
Terminal window
$ app --port=3000 # args.port === 3000
$ app --port=abc # usage error: expected to be number
options: {
project: { description: 'project directory', alias: ['p'] }
}
Terminal window
$ app -p ./packages/core
$ app --project ./packages/core
$ app --project=./packages/core

Single-character options bundle: -abc sets a and b to true and gives c the following value. An alias can be hidden from the help message:

options: {
project: { description: 'project directory', alias: ['p', { alias: 'proj', hidden: true }] }
}

default supplies the value when the option is absent.

options: {
port: { description: 'port to listen on', type: z.number(), default: 3000 }
}

conflicts names the options this one cannot be used with.

options: {
full: { description: 'print the whole file', conflicts: ['lines'] },
lines: { description: 'lines to print', type: z.number(), alias: ['n'], default: 10 }
}
Terminal window
$ app --full -n 20
option --full cannot be used with option -n

Passing both is a usage error (exit code 2), reported with the other parse errors. A default value does not count as passed, so app --full is valid here. The conflict applies in both directions, and the help message lists it on both options.

An array-typed option accumulates every occurrence:

options: {
tag: { description: 'a tag', type: z.array(z.string()) }
}
Terminal window
$ app --tag=a --tag=b # args.tag === ['a', 'b']

A non-array option given more than once is a usage error, rather than silently keeping the last one.

Wrap the type in z.optional() to make an argument or option optional. Without it, a declared argument is required, and omitting it is a usage error that prints the help message.

cli({ name: 'app', version: '1.0.0' }).default({
arguments: [
{ name: 'a', description: 'an optional name', type: z.optional(z.string()) }
],
options: {
y: { description: 'an optional number', type: z.optional(z.number()) }
},
run(args) {
// args.a: string | undefined
// args.y: number | undefined
}
})
Declaration args type Argument Option
(omitted) string / boolean | undefined The default — a string The default — a flag
z.string() string
z.number() number ✓ coerced ✓ coerced
z.boolean() boolean ✓ coerced --flag, --flag=true, --flag false
z.array(z.string()) string[] ✓ variadic ✓ repeatable
z.array(z.number()) number[] ✓ variadic, coerced ✓ repeatable, coerced
z.optional(...) T | undefined ✓ makes it optional

Option validation happens before run() is called. A value that fails its schema, an unknown flag, a missing required argument, or an extra positional argument all produce the same outcome: the CLI prints the help message and does not run the command.

Every command inherits these; you don’t declare them, and you shouldn’t shadow them.

Flag Alias Effect
--help -h Print the resolved command’s help message
--version -v Print the CLI version
--verbose -V Display level debug
--silent Display level none
--debug-cli Display level trace, including clibuilder’s own internal messages

args.help is always present in the inferred type for that reason.

One more is added only when the CLI declares config, since it would have nothing to report otherwise:

Flag Alias Effect
--show-config Print the resolved config and the file it came from — see Configuration