Skip to main content
Multi-command CLIs act as routers, directing execution to different handlers based on the subcommand. This is the pattern used by tools like git, docker, and npm.

When to Use Multi-Command Pattern

Use this pattern when your CLI:
  • Provides multiple related operations
  • Needs a clean namespace for different actions
  • Benefits from command-specific help
Examples: git commit, docker build, npm install

Creating a Router CLI

1
Create CLI without run
2
Omit the run property to create a router:
3
import { Cli, z } from 'incur'

const cli = Cli.create('my-cli', {
  description: 'My CLI',
})
4
Chain .command() calls
5
Register subcommands using .command(). Each command is independent:
6
cli
  .command('status', {
    description: 'Show repo status',
    run() {
      return { clean: true }
    },
  })
  .command('install', {
    description: 'Install a package',
    args: z.object({
      package: z.string().optional().describe('Package name'),
    }),
    options: z.object({
      saveDev: z.boolean().optional().describe('Save as dev dependency'),
    }),
    alias: { saveDev: 'D' },
    run(c) {
      return { added: 1, packages: 451 }
    },
  })
  .serve()
7
Run subcommands
8
my-cli status
# → clean: true

my-cli install express -D
# → added: 1
# → packages: 451

Command Organization

Each command is self-contained with its own args, options, and handler:

Help Text Generation

Help is automatically generated for the router and each command.

Router Help

Command-Specific Help

Multiple Arguments Per Command

Each command can have its own argument schema:

Complete Multi-Command Example

Here’s a complete package manager CLI:

Usage Examples

Type Safety

All arguments and options are fully typed:
The .command() method returns the same CLI instance, so you can chain as many commands as you need. The type system tracks all registered commands for features like CTAs.

Router vs Single-Command

FeatureRouter (no run)Single-Command (with run)
Subcommands✓ Yes✗ No
Root handler✗ No✓ Yes
Use caseMulti-purpose toolSingle-purpose utility
Help formatLists commandsShows usage
Examplegit, dockergrep, curl
Don’t mix patterns! A router CLI (without run) is meant to route to subcommands. If you need both a root handler and subcommands, use sub-commands instead.