CommandDefinition
Defines a command’s schema, behavior, and documentation. Commands are registered with cli.command().
Type Signature
type CommandDefinition<
args extends z.ZodObject<any> | undefined = undefined,
env extends z.ZodObject<any> | undefined = undefined,
options extends z.ZodObject<any> | undefined = undefined,
output extends z.ZodType | undefined = undefined,
parentVars extends z.ZodObject<any> | undefined = undefined,
parentEnv extends z.ZodObject<any> | undefined = undefined,
> = {
args?: args
description?: string
env?: env
options?: options
output?: output
alias?: Record<string, string>
examples?: Example[]
format?: 'toon' | 'json' | 'yaml' | 'md'
outputPolicy?: 'all' | 'agent-only'
usage?: Usage[]
middleware?: MiddlewareHandler[]
run: (context: Context) => InferReturn<output> | Promise<InferReturn<output>> | AsyncGenerator
}
Properties
Zod schema for positional arguments. Arguments are parsed in the order they are defined in the schema.args: z.object({
name: z.string(),
age: z.number()
})
// Usage: my-cli greet John 25
A short description of what the command does. Shown in help text, agent discovery, and skill manifests.
Zod schema for command-specific environment variables. Merged with CLI-level env schema. Keys are the variable names (e.g. DEPLOY_TOKEN).
Zod schema for named options/flags. Options are passed as --key value or --flag for booleans.options: z.object({
verbose: z.boolean().default(false),
output: z.string().optional()
})
// Usage: my-cli deploy --verbose --output ./dist
Zod schema for the return value. Used for output validation and type inference in the command registry.
Map of option names to single-char aliases.options: z.object({ verbose: z.boolean() }),
alias: { verbose: 'v' }
// Usage: my-cli deploy -v
Usage examples for this command. Shown in help output with --help.
format
'toon' | 'json' | 'yaml' | 'md'
default:"'toon'"
Default output format for this command. Overrides CLI-level format. Can still be overridden by --format or --json flags.
outputPolicy
'all' | 'agent-only'
default:"'all'"
Controls when output data is displayed:
'all' — displays to both humans and agents
'agent-only' — suppresses data output in human/TTY mode while still returning it to agents
Alternative usage patterns shown in help output. Useful for documenting multiple invocation styles.
Command-specific middleware that runs only for this command. Runs after CLI-level and group-level middleware.
run
(context: Context) => InferReturn<output>
required
The command handler function. Receives a context object with parsed arguments, options, environment variables, and utility functions.Can return:
- A plain value (synchronous)
- A Promise (asynchronous)
- An AsyncGenerator (streaming)
See Context Type below for details.
Context Type
The context object passed to the run handler. Contains parsed inputs, CLI metadata, and control flow functions.
Properties
Whether the consumer is an agent (stdout is not a TTY). Use this to customize output for different audiences.run(c) {
if (c.agent) {
return { structured: 'data' }
}
console.log('Human-friendly message')
return {}
}
Positional arguments parsed according to the args schema. Fully typed based on the Zod schema.args: z.object({ name: z.string() }),
run(c) {
c.args.name // string (typed!)
}
The CLI name (not the command name).
Parsed environment variables according to the env schema. Includes both CLI-level and command-level env vars.
Named options/flags parsed according to the options schema. Fully typed based on the Zod schema.options: z.object({ verbose: z.boolean().default(false) }),
run(c) {
c.options.verbose // boolean (typed!)
}
Variables set by middleware. Typed according to the CLI’s vars schema.vars: z.object({ user: z.string() }),
// In middleware: c.set('user', 'alice')
run(c) {
c.var.user // string (typed!)
}
ok
(data: InferReturn<output>, meta?: { cta?: CtaBlock }) => never
Return a success result with optional metadata. Calling this function short-circuits execution and immediately returns.run(c) {
return c.ok({ success: true }, {
cta: {
description: 'Next steps:',
commands: ['my-cli deploy']
}
})
}
error
(options: ErrorOptions) => never
Return an error result with optional CTAs. Calling this function short-circuits execution, exits with code 1, and displays the error.run(c) {
if (!isValid) {
return c.error({
code: 'INVALID_INPUT',
message: 'Invalid configuration',
retryable: true,
cta: {
description: 'Try:',
commands: ['my-cli validate']
}
})
}
}
Error code (e.g. 'INVALID_INPUT', 'NOT_FOUND').
Human-readable error message.
Whether the operation can be retried. Helps agents decide if they should retry.
Call-to-action suggestions for the user.
The CLI version string, if provided.
Command Registration Patterns
Basic Command
Register a simple command with args and options:
import { Cli, z } from 'incur'
const cli = Cli.create('my-cli')
cli.command('greet', {
description: 'Greet a person',
args: z.object({
name: z.string()
}),
options: z.object({
loud: z.boolean().default(false)
}),
run(c) {
const greeting = `Hello ${c.args.name}!`
return {
message: c.options.loud ? greeting.toUpperCase() : greeting
}
}
})
Async Command
Commands can be async and use await:
cli.command('fetch', {
args: z.object({ url: z.string() }),
async run(c) {
const response = await fetch(c.args.url)
const data = await response.json()
return { data }
}
})
Streaming Command
Return an AsyncGenerator to stream results:
cli.command('watch', {
description: 'Watch for changes',
async *run(c) {
for (let i = 0; i < 5; i++) {
await new Promise(r => setTimeout(r, 1000))
yield { count: i, timestamp: Date.now() }
}
}
})
Command with Environment Variables
Access typed environment variables:
cli.command('deploy', {
description: 'Deploy to production',
env: z.object({
DEPLOY_TOKEN: z.string(),
DEPLOY_URL: z.string().default('https://api.example.com')
}),
async run(c) {
const response = await fetch(c.env.DEPLOY_URL, {
headers: { Authorization: `Bearer ${c.env.DEPLOY_TOKEN}` }
})
return { status: response.status }
}
})
Command with Aliases
Provide short aliases for options:
cli.command('build', {
options: z.object({
output: z.string(),
verbose: z.boolean().default(false)
}),
alias: {
output: 'o',
verbose: 'v'
},
run(c) {
// Can be called with: build -o ./dist -v
return { output: c.options.output }
}
})
Command Groups
Mount sub-CLIs as command groups:
// Create a command group
const pr = Cli.create('pr', {
description: 'Pull request management'
})
pr.command('list', {
description: 'List pull requests',
run() {
return { items: [] }
}
})
pr.command('create', {
description: 'Create a pull request',
args: z.object({ title: z.string() }),
run(c) {
return { title: c.args.title }
}
})
// Mount the group
cli.command(pr)
// Now available as:
// my-cli pr list
// my-cli pr create "My PR"
Error Handling
Use context methods for structured error handling:
cli.command('validate', {
args: z.object({ file: z.string() }),
run(c) {
if (!existsSync(c.args.file)) {
return c.error({
code: 'FILE_NOT_FOUND',
message: `File not found: ${c.args.file}`,
retryable: false,
cta: {
description: 'Available files:',
commands: ['my-cli list']
}
})
}
return { valid: true }
}
})
Using Middleware Variables
Access variables set by middleware:
const cli = Cli.create('api', {
vars: z.object({
userId: z.string().default('anonymous')
})
})
cli.use(async (c, next) => {
// Authenticate and set user
c.set('userId', 'user-123')
await next()
})
cli.command('profile', {
run(c) {
// Access the userId set by middleware
return { userId: c.var.userId }
}
})