Skip to main content

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

args
z.ZodObject<any>
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
description
string
A short description of what the command does. Shown in help text, agent discovery, and skill manifests.
env
z.ZodObject<any>
Zod schema for command-specific environment variables. Merged with CLI-level env schema. Keys are the variable names (e.g. DEPLOY_TOKEN).
options
z.ZodObject<any>
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
output
z.ZodType
Zod schema for the return value. Used for output validation and type inference in the command registry.
alias
Record<string, string>
Map of option names to single-char aliases.
options: z.object({ verbose: z.boolean() }),
alias: { verbose: 'v' }
// Usage: my-cli deploy -v
examples
Example[]
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
usage
Usage[]
Alternative usage patterns shown in help output. Useful for documenting multiple invocation styles.
middleware
MiddlewareHandler[]
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

agent
boolean
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 {}
}
args
InferOutput<args>
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!)
}
name
string
The CLI name (not the command name).
env
InferOutput<env>
Parsed environment variables according to the env schema. Includes both CLI-level and command-level env vars.
options
InferOutput<options>
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!)
}
var
InferVars<vars>
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']
      }
    })
  }
}
version
string | undefined
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 }
  }
})

Build docs developers (and LLMs) love