Seed CLISeed CLI

Commands

Define commands with fully typed arguments, flags, and subcommands.

Commands are the core building block of any Seed CLI application. Each command defines its name, arguments, flags, and a run function that receives the fully typed seed context.

Defining a Command

import { command, arg, flag } from '@seedcli/core'

export default command({
  name: 'deploy',
  description: 'Deploy the application',
  alias: ['d'],
  args: {
    environment: arg({
      type: 'string',
      required: true,
      description: 'Target environment',
      choices: ['staging', 'production'] as const,
    }),
  },
  flags: {
    force: flag({ type: 'boolean', alias: 'f', description: 'Skip confirmation' }),
    tag: flag({ type: 'string', description: 'Deploy tag' }),
  },
  run: async (seed) => {
    // seed.args.environment is typed as 'staging' | 'production'
    // seed.flags.force is typed as boolean | undefined
    // seed.flags.tag is typed as string | undefined
  },
})

Arguments

Arguments are positional values passed after the command name.

arg() Options

OptionTypeDescription
type'string' | 'number'The argument type
requiredbooleanWhether the argument is required (default: false)
descriptionstringHelp text description
defaultstring | numberDefault value if not provided
choicesreadonly string[]Restrict to specific values
validate(value) => boolean | stringCustom validation function

Type Inference with Choices

When you use choices with as const, the argument type narrows to a union:

const env = arg({
  type: 'string',
  choices: ['dev', 'staging', 'prod'] as const,
})
// seed.args.env is typed as 'dev' | 'staging' | 'prod'

Validation

const port = arg({
  type: 'number',
  validate: (value) => {
    if (value < 1 || value > 65535) return 'Port must be between 1 and 65535'
    return true
  },
})

Flags

Flags are named options prefixed with -- (or - for aliases).

flag() Options

OptionTypeDescription
type'boolean' | 'string' | 'number' | 'string[]' | 'number[]'The flag type
aliasstringShort alias (e.g., 'f' for --force)
requiredbooleanWhether the flag is required
defaultunknownDefault value
descriptionstringHelp text
choicesreadonly string[]Restrict to specific values
validate(value) => boolean | stringCustom validation
hiddenbooleanHide from help output

Flag Types

// Boolean — presence toggles to true
flag({ type: 'boolean' })
// Usage: --verbose

// String
flag({ type: 'string', default: 'json' })
// Usage: --format json

// Number
flag({ type: 'number' })
// Usage: --port 3000

// String array — can be repeated
flag({ type: 'string[]' })
// Usage: --tag v1 --tag v2

// Number array
flag({ type: 'number[]' })
// Usage: --port 3000 --port 3001

Choices with Type Narrowing

const format = flag({
  type: 'string',
  choices: ['json', 'yaml', 'toml'] as const,
})
// seed.flags.format is typed as 'json' | 'yaml' | 'toml' | undefined

Command Options

OptionTypeDescription
namestringCommand name (required)
descriptionstringHelp text
aliasstring[]Command aliases
hiddenbooleanHide from help output
argsRecord<string, ArgDef>Positional arguments
flagsRecord<string, FlagDef>Named flags
subcommandsCommand[]Nested subcommands
middlewareMiddleware[]Command-specific middleware
run(seed: Seed) => void | Promise<void>Command handler

Subcommands

Commands can have nested subcommands:

import { command } from '@seedcli/core'

const migrate = command({
  name: 'migrate',
  description: 'Run database migrations',
  flags: {
    up: flag({ type: 'boolean', description: 'Migrate up' }),
    down: flag({ type: 'boolean', description: 'Migrate down' }),
  },
  run: async (seed) => { /* ... */ },
})

const dbSeed = command({
  name: 'seed',
  description: 'Seed the database',
  run: async (seed) => { /* ... */ },
})

export default command({
  name: 'db',
  description: 'Database operations',
  subcommands: [migrate, dbSeed],
})
my-cli db migrate --up
my-cli db seed

The Seed Context

Every command's run function receives a seed object with:

interface Seed<TArgs, TFlags> {
  args: TArgs                    // Typed arguments
  flags: TFlags                  // Typed flags
  parameters: {
    raw: string[]                // Original argv
    argv: string[]               // Positional arguments after parsing
    command: string | undefined  // Matched command name
  }

  // Modules (lazily loaded)
  print: PrintModule
  prompt: PromptModule
  filesystem: FilesystemModule
  system: SystemModule
  http: HttpModule
  template: TemplateModule
  patching: PatchingModule
  strings: StringsModule
  semver: SemverModule
  packageManager: PackageManagerModule
  config: ConfigModule

  // Metadata
  meta: {
    version: string
    commandName: string
    brand: string
    debug: boolean
  }
}

Modules are loaded lazily — they're only initialized when first accessed, keeping startup fast.

Auto-Discovery

Place commands in a commands/ directory and use .src() to auto-discover them:

src/
├── commands/
│   ├── deploy.ts    → my-cli deploy
│   ├── init.ts      → my-cli init
│   └── db/
│       ├── migrate.ts → my-cli db migrate
│       └── seed.ts    → my-cli db seed
└── cli.ts
src/cli.ts
const runtime = build('my-cli')
  .src(import.meta.dir)
  .create()

Each command file should export default a command() call. Subdirectories automatically become nested subcommands.

On this page