> ## 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.

# CLI Creation

> Learn how to create single-command and multi-command CLIs with incur

## Overview

Incur CLIs are created using `Cli.create()`, which can produce either a **single-command CLI** (one action) or a **multi-command CLI** (router with subcommands).

## Single-Command CLI

Pass a `run` function directly to `Cli.create()` to create a CLI that performs one action.

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

Cli.create('greet', {
  description: 'A greeting CLI',
  args: z.object({
    name: z.string().describe('Name to greet'),
  }),
  run(c) {
    return { message: `hello ${c.args.name}` }
  },
}).serve()
```

```bash theme={null}
$ greet world
message: hello world
```

<Note>
  Even single-command CLIs can register subcommands later using `.command()`. The root handler runs when no subcommand matches.
</Note>

## Multi-Command CLI

Omit the `run` function to create a router CLI that delegates to subcommands.

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

Cli.create('my-cli', {
  description: 'My 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()
```

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

$ my-cli install express -D
added: 1
packages: 451
```

## Configuration Options

### Required Options

<ParamField path="name" type="string" required>
  The CLI binary name. Used in help text, error messages, and CTAs.
</ParamField>

### Common Options

<ParamField path="description" type="string">
  A short description of what the CLI does. Shown in help output.
</ParamField>

<ParamField path="version" type="string">
  The CLI version string. Displayed with `--version`.
</ParamField>

<ParamField path="run" type="function">
  The root command handler. When provided, creates a single-command CLI. Accepts a context object with `args`, `options`, `env`, and helper functions like `ok()` and `error()`.
</ParamField>

### Advanced Options

<ParamField path="args" type="z.ZodObject<any>">
  Zod schema for positional arguments. Keys define the argument order.
</ParamField>

<ParamField path="options" type="z.ZodObject<any>">
  Zod schema for named options/flags.
</ParamField>

<ParamField path="env" type="z.ZodObject<any>">
  Zod schema for environment variables. Keys are variable names (e.g. `NPM_TOKEN`).
</ParamField>

<ParamField path="vars" type="z.ZodObject<any>">
  Zod schema for middleware variables. Enables typed dependency injection.
</ParamField>

<ParamField path="output" type="z.ZodType">
  Zod schema for the return value. Enforces output shape validation.
</ParamField>

<ParamField path="format" type="'toon' | 'json' | 'yaml' | 'md' | 'jsonl'">
  Default output format. Overridden by `--format` or `--json` flags.
</ParamField>

<ParamField path="outputPolicy" type="'all' | 'agent-only'">
  Controls when output data is displayed.

  * `'all'` — displays to both humans and agents (default)
  * `'agent-only'` — suppresses data in TTY mode, only returns to agents
</ParamField>

<ParamField path="alias" type="Record<string, string>">
  Map of option names to single-character aliases.
</ParamField>

<ParamField path="aliases" type="string[]">
  Alternative binary names for this CLI (e.g. shorter aliases in package.json `bin`).
</ParamField>

## Command Groups

Create separate CLI instances and mount them as command groups using `.command(cli)`.

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

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

// Create a `pr` group
const pr = Cli.create('pr', { description: 'Pull request commands' })
  .command('list', {
    description: 'List pull requests',
    options: z.object({
      state: z.enum(['open', 'closed', 'all']).default('open'),
    }),
    run(c) {
      return { prs: [], state: c.options.state }
    },
  })

cli
  .command(pr)  // Mount the `pr` group
  .serve()
```

```bash theme={null}
$ my-cli pr list --state closed
prs: (empty)
state: closed
```

Command groups inherit `outputPolicy` and middleware from their parent CLI unless explicitly overridden.

## CLI Name in Context

The CLI name is available in the `run` context as `c.name`. Useful for composing help text and error messages.

```typescript theme={null}
const cli = Cli.create('deploy-cli', { description: 'Deploy tools' })

cli.command('check', {
  output: z.string(),
  run(c) {
    if (!authenticated()) {
      return `Not logged in. Run \`${c.name} auth login\` to log in.`
    }
    return 'OK'
  },
})
```

## Single-Object Syntax

You can pass all options, including `name`, as a single object:

```typescript theme={null}
Cli.create({
  name: 'greet',
  description: 'A greeting CLI',
  args: z.object({
    name: z.string().describe('Name to greet'),
  }),
  run(c) {
    return { message: `hello ${c.args.name}` }
  },
}).serve()
```

This is useful when loading configuration from `package.json` or other sources.

## Next Steps

<CardGroup cols={2}>
  <Card title="Commands" icon="terminal" href="/core-concepts/commands">
    Learn how to register and define commands
  </Card>

  <Card title="Schemas" icon="shield-check" href="/core-concepts/schemas">
    Explore Zod schema validation and type inference
  </Card>
</CardGroup>
