Skip to main content
Mark options as deprecated with .meta({ deprecated: true }). Deprecated flags show [deprecated] in --help, **Deprecated.** in skill docs, deprecated: true in JSON Schema (--llms), and emit a stderr warning when used in TTY mode.

Basic Usage

import { Cli, z } from 'incur'

cli.command('deploy', {
  options: z.object({
    zone: z.string().optional().describe('Availability zone').meta({ deprecated: true }),
    region: z.string().optional().describe('Target region'),
  }),
  run(c) {
    return { region: c.options.region }
  },
})
$ my-cli deploy --zone us-east-1
# Warning: --zone is deprecated

Help Text

Deprecated options show [deprecated] in help output:
$ my-cli deploy --help
# my-cli deploy
#
# Usage: my-cli deploy [options]
#
# Options:
#   --zone <string>    Availability zone [deprecated]
#   --region <string>  Target region

Skill Documentation

Skill files (generated by skills add) include **Deprecated.** for deprecated options:
### Options

- `--zone <string>`**Deprecated.** Availability zone
- `--region <string>` — Target region

JSON Schema

--llms output includes deprecated: true in the JSON Schema:
$ my-cli --llms --format json
{
  "version": "incur.v1",
  "commands": [
    {
      "name": "deploy",
      "schema": {
        "options": {
          "type": "object",
          "properties": {
            "zone": {
              "type": "string",
              "description": "Availability zone",
              "deprecated": true
            },
            "region": {
              "type": "string",
              "description": "Target region"
            }
          }
        }
      }
    }
  ]
}

Runtime Warnings

When a deprecated flag is used in human/TTY mode, incur emits a warning to stderr:
$ my-cli deploy --zone us-east-1
# Warning: --zone is deprecated
# → region: us-east-1
Warnings are only shown in TTY mode. Agent invocations (--json, piped output, MCP) do not emit warnings.

Migration Patterns

Renaming an Option

cli.command('deploy', {
  options: z.object({
    // Old name — deprecated
    env: z.string().optional().describe('Deployment environment').meta({ deprecated: true }),
    // New name
    environment: z.string().optional().describe('Deployment environment'),
  }),
  run(c) {
    const environment = c.options.environment ?? c.options.env
    if (!environment) return c.error({ code: 'MISSING_ENV', message: 'environment is required' })
    return { environment }
  },
})

Removing an Option

Deprecate first, remove later:
// Step 1: Mark as deprecated
cli.command('deploy', {
  options: z.object({
    zone: z.string().optional().describe('Availability zone').meta({ deprecated: true }),
  }),
  run(c) {
    // Ignore the deprecated option
    return { deployed: true }
  },
})

// Step 2 (later): Remove the option entirely
cli.command('deploy', {
  options: z.object({
    // zone removed
  }),
  run(c) {
    return { deployed: true }
  },
})

Replacing with a Different Type

cli.command('install', {
  options: z.object({
    // Old: --count 5
    count: z.number().optional().describe('Number of instances').meta({ deprecated: true }),
    // New: --replicas 5
    replicas: z.number().optional().describe('Number of replicas'),
  }),
  run(c) {
    const replicas = c.options.replicas ?? c.options.count ?? 1
    return { replicas }
  },
})

Short Aliases

Deprecated options also apply to their short aliases:
cli.command('deploy', {
  options: z.object({
    zone: z.string().optional().describe('Availability zone').meta({ deprecated: true }),
  }),
  alias: { zone: 'z' },
  run(c) {
    return { region: c.options.zone }
  },
})
$ my-cli deploy -z us-east-1
# Warning: --zone is deprecated

Boolean Flags

Deprecation warnings apply to both --flag and --no-flag forms:
cli.command('run', {
  options: z.object({
    color: z.boolean().optional().describe('Enable colors').meta({ deprecated: true }),
  }),
  run(c) {
    return { color: c.options.color }
  },
})
$ my-cli run --color
# Warning: --color is deprecated

$ my-cli run --no-color
# Warning: --color is deprecated

Documentation Flow

Deprecation annotations flow through all output formats:
FormatAnnotation
--help[deprecated] suffix
skills add**Deprecated.** prefix
--llms --format json"deprecated": true
--llms --format md**Deprecated.** prefix
TTY usagestderr warning

Best Practices

Do

  • Mark options as deprecated before removing them
  • Provide migration guidance in the description
  • Support both old and new options during the transition period
  • Remove deprecated options in a major version bump

Don’t

  • Don’t mark required options as deprecated — make them optional first
  • Don’t remove deprecated options without a deprecation period
  • Don’t rely on deprecated options in examples or documentation

Example: Full Migration

import { Cli, z } from 'incur'

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

// Version 1.x: both options supported, old one deprecated
cli.command('deploy', {
  options: z.object({
    env: z.string().optional()
      .describe('Deployment environment (use --environment instead)')
      .meta({ deprecated: true }),
    environment: z.string().optional()
      .describe('Deployment environment'),
  }),
  run(c) {
    const environment = c.options.environment ?? c.options.env
    if (!environment) {
      return c.error({
        code: 'MISSING_ENV',
        message: 'environment is required. Use --environment <env>',
      })
    }
    return { environment, deployed: true }
  },
})

// Version 2.x: old option removed
cli.command('deploy', {
  options: z.object({
    environment: z.string().describe('Deployment environment'),
  }),
  run(c) {
    return { environment: c.options.environment, deployed: true }
  },
})
  • See Schemas for defining options
  • See Commands for command definition patterns

Build docs developers (and LLMs) love