> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/wevm/incur/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Command CLIs

> Create router CLIs with multiple subcommands using .command() chaining

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

<Steps>
  ### Create CLI without run

  Omit the `run` property to create a router:

  ```ts theme={null}
  import { Cli, z } from 'incur'

  const cli = Cli.create('my-cli', {
    description: 'My CLI',
  })
  ```

  ### Chain .command() calls

  Register subcommands using `.command()`. Each command is independent:

  ```ts theme={null}
  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()
  ```

  ### Run subcommands

  ```bash theme={null}
  my-cli status
  # → clean: true

  my-cli install express -D
  # → added: 1
  # → packages: 451
  ```
</Steps>

## Command Organization

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

```ts theme={null}
Cli.create('repo', {
  description: 'Repository management',
})
  .command('clone', {
    description: 'Clone a repository',
    args: z.object({
      url: z.string().describe('Repository URL'),
    }),
    options: z.object({
      depth: z.coerce.number().optional().describe('Clone depth'),
      branch: z.string().optional().describe('Branch to clone'),
    }),
    run(c) {
      return {
        cloned: c.args.url,
        branch: c.options.branch ?? 'main',
      }
    },
  })
  .command('list', {
    description: 'List repositories',
    options: z.object({
      limit: z.coerce.number().default(10).describe('Number of repos to show'),
      private: z.boolean().optional().describe('Include private repos'),
    }),
    run(c) {
      return {
        repos: ['repo1', 'repo2'],
        count: 2,
        limit: c.options.limit,
      }
    },
  })
  .command('delete', {
    description: 'Delete a repository',
    args: z.object({
      name: z.string().describe('Repository name'),
    }),
    options: z.object({
      force: z.boolean().optional().describe('Skip confirmation'),
    }),
    run(c) {
      if (!c.options.force) {
        return c.error({
          code: 'CONFIRMATION_REQUIRED',
          message: 'Use --force to confirm deletion',
        })
      }
      return { deleted: c.args.name }
    },
  })
  .serve()
```

## Help Text Generation

Help is automatically generated for the router and each command.

### Router Help

```bash theme={null}
my-cli --help
# my-cli – My CLI
#
# Usage: my-cli <command>
#
# Commands:
#   install  Install a package
#   status   Show repo status
#
# Built-in Commands:
#   completions  Generate shell completion script
#   mcp add      Register as an MCP server
#   skills add   Sync skill files to your agent
#
# Global Options:
#   --format <toon|json|yaml|md|jsonl>  Output format
#   --help                              Show help
#   --llms                              Print LLM-readable manifest
#   --mcp                               Start as MCP stdio server
#   --verbose                           Show full output envelope
#   --version                           Show version
```

### Command-Specific Help

```bash theme={null}
my-cli install --help
# my-cli install – Install a package
#
# Usage: my-cli install [package] [options]
#
# Arguments:
#   package  Package name (optional)
#
# Options:
#   -D, --saveDev  Save as dev dependency
#
# Global Options:
#   --format <toon|json|yaml|md|jsonl>  Output format
#   --help                              Show help
#   --json                              Shorthand for --format json
#   --verbose                           Show full output envelope
```

## Multiple Arguments Per Command

Each command can have its own argument schema:

```ts theme={null}
Cli.create('file', {
  description: 'File operations',
})
  .command('copy', {
    description: 'Copy a file',
    args: z.object({
      source: z.string().describe('Source file'),
      destination: z.string().describe('Destination file'),
    }),
    run(c) {
      return { copied: true, from: c.args.source, to: c.args.destination }
    },
  })
  .command('read', {
    description: 'Read a file',
    args: z.object({
      path: z.string().describe('File path'),
    }),
    options: z.object({
      lines: z.coerce.number().optional().describe('Number of lines to read'),
    }),
    run(c) {
      return { path: c.args.path, content: '...' }
    },
  })
  .serve()
```

```bash theme={null}
file copy input.txt output.txt
# → copied: true
# → from: input.txt
# → to: output.txt

file read data.txt --lines 10
# → path: data.txt
# → content: ...
```

## Complete Multi-Command Example

Here's a complete package manager CLI:

<CodeGroup>
  ```ts index.ts theme={null}
  import { Cli, z } from 'incur'

  Cli.create('pkg', {
    description: 'Package manager',
    version: '1.0.0',
  })
    .command('install', {
      description: 'Install packages',
      args: z.object({
        packages: z.array(z.string()).optional().describe('Packages to install'),
      }),
      options: z.object({
        dev: z.boolean().optional().describe('Install as dev dependencies'),
        global: z.boolean().optional().describe('Install globally'),
      }),
      alias: { dev: 'D', global: 'g' },
      run(c) {
        const packages = c.args.packages ?? []
        const count = packages.length || 1
        return {
          installed: count,
          dev: c.options.dev ?? false,
          global: c.options.global ?? false,
        }
      },
    })
    .command('uninstall', {
      description: 'Uninstall packages',
      args: z.object({
        package: z.string().describe('Package to uninstall'),
      }),
      run(c) {
        return { removed: c.args.package }
      },
    })
    .command('list', {
      description: 'List installed packages',
      options: z.object({
        depth: z.coerce.number().default(0).describe('Dependency depth'),
        json: z.boolean().optional().describe('Output as JSON'),
      }),
      run(c) {
        return {
          packages: [
            { name: 'express', version: '4.18.0' },
            { name: 'zod', version: '3.22.0' },
          ],
          depth: c.options.depth,
        }
      },
    })
    .command('update', {
      description: 'Update packages',
      args: z.object({
        package: z.string().optional().describe('Specific package to update'),
      }),
      options: z.object({
        latest: z.boolean().optional().describe('Update to latest version'),
      }),
      run(c) {
        return {
          updated: c.args.package ?? 'all',
          latest: c.options.latest ?? false,
        }
      },
    })
    .serve()
  ```
</CodeGroup>

### Usage Examples

```bash theme={null}
# Install all dependencies
pkg install
# → installed: 1
# → dev: false
# → global: false

# Install specific packages as dev dependencies
pkg install typescript vitest -D
# → installed: 2
# → dev: true
# → global: false

# List installed packages
pkg list --depth 2
# → packages[2]{name,version}:
# →   express,4.18.0
# →   zod,3.22.0
# → depth: 2

# Update a specific package
pkg update express --latest
# → updated: express
# → latest: true

# Get help for a command
pkg install --help
```

## Type Safety

All arguments and options are fully typed:

```ts theme={null}
Cli.create('calc', {})
  .command('add', {
    args: z.object({
      a: z.coerce.number(),
      b: z.coerce.number(),
    }),
    run(c) {
      // c.args.a and c.args.b are typed as `number`
      return { result: c.args.a + c.args.b }
    },
  })
  .serve()
```

<Note>
  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.
</Note>

## Router vs Single-Command

| Feature      | Router (no `run`)  | Single-Command (with `run`) |
| ------------ | ------------------ | --------------------------- |
| Subcommands  | ✓ Yes              | ✗ No                        |
| Root handler | ✗ No               | ✓ Yes                       |
| Use case     | Multi-purpose tool | Single-purpose utility      |
| Help format  | Lists commands     | Shows usage                 |
| Example      | `git`, `docker`    | `grep`, `curl`              |

<Warning>
  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](/building/sub-commands) instead.
</Warning>
