Seed CLISeed CLI

Builder API

Configure your CLI with the fluent Builder pattern.

The Builder API provides a fluent, chainable interface for configuring every aspect of your CLI. It produces a Runtime that can be executed with .run().

Basic Usage

src/cli.ts
import { build } from '@seedcli/core'

const runtime = build('my-cli')
  .src(import.meta.dirname)
  .version('1.0.0')
  .help()
  .debug()
  .create()

await runtime.run()

Builder Methods

build(brand)

Creates a new Builder instance. The brand string is your CLI's name and is used in help output, error messages, and metadata.

const builder = build('my-cli')

.src(dir)

Auto-discover commands and extensions from a directory.

builder.src(import.meta.dirname)
// Discovers:
//   src/commands/*.ts  → registered as commands
//   src/extensions/*.ts → registered as extensions

.command(cmd) / .commands(cmds)

Register commands manually:

builder
  .command(deployCommand)
  .commands([initCommand, buildCommand])

.defaultCommand(cmd)

Set a fallback command that runs when no command is specified:

builder.defaultCommand(
  command({
    name: 'default',
    run: (seed) => {
      seed.print.info('Run --help to see available commands')
    },
  })
)

.plugin(name) / .plugins(dir)

Load plugins by name or discover from a directory:

// By name (resolved from node_modules)
builder.plugin('seedcli-plugin-docker')
builder.plugin(['seedcli-plugin-docker', 'seedcli-plugin-aws'])

// From directory
builder.plugins('./plugins')
builder.plugins('./plugins', { matching: 'my-*' })

.extension(ext)

Register an extension:

builder.extension(
  defineExtension({
    name: 'auth',
    setup: async (seed) => {
      // Initialize auth state
    },
    teardown: async (seed) => {
      // Cleanup
    },
  })
)

.help(options?) / .noHelp()

Enable or disable the built-in help system:

// Enable with defaults
builder.help()

// Custom options
builder.help({
  showAliases: true,
  showHidden: false,
})

// Disable entirely
builder.noHelp()

When enabled, --help and -h flags are automatically added to all commands.

.version(version?) / .noVersion()

Enable or disable the --version flag:

// Enable --version; auto-detects from your project's package.json
builder.version()

// Explicit version string
builder.version('2.1.0')

// Disable
builder.noVersion()

When called without an argument, .version() walks up from the entry script's location on disk to find the nearest package.json and reads its version field. This works in dev mode (bun src/index.ts), in bundled output (node dist/index.js), and when the CLI is installed globally — so you can keep the version in a single source of truth without importing package.json yourself.

.debug()

Enables --debug and --verbose flags. When active, seed.meta.debug is true and seed.print.debug() calls are visible.

builder.debug()

.completions()

Enables shell completion generation. Adds a completions subcommand:

my-cli completions bash  # Output bash completions
my-cli completions zsh   # Output zsh completions
my-cli completions install  # Install for current shell

.middleware(fn)

Add global middleware that runs before every command:

builder.middleware(async (seed, next) => {
  const start = Date.now()
  await next()
  seed.print.muted(`Completed in ${Date.now() - start}ms`)
})

.onReady(fn)

Hook that runs after the seed context is assembled, before extension setup and command execution:

builder.onReady((seed) => {
  seed.print.debug('CLI initialized')
})

.onError(fn)

Global error handler:

builder.onError((error, seed) => {
  seed.print.error(`Failed: ${error.message}`)
  if (seed.meta.debug) {
    console.error(error.stack)
  }
})

.exclude(modules)

Exclude modules for performance in lightweight CLIs:

builder.exclude(['http', 'template', 'patching'])

.create()

Build the runtime. Returns a Runtime instance:

const runtime = builder.create()

Runtime

The Runtime object has a single method:

runtime.run(argv?)

Execute the CLI:

// Use process.argv (default)
await runtime.run()

// Custom argv (useful for testing)
await runtime.run(['deploy', 'staging', '--force'])

Runtime Lifecycle

When runtime.run() is called, the following happens in order:

  1. Register SIGINT/SIGTERM handlers
  2. Strip --debug/--verbose flags (if enabled)
  3. Handle --version / --help early exits
  4. Discover and load plugins, register extensions
  5. Route to the matching command
  6. Parse command-specific args and flags
  7. Assemble the seed context
  8. Run onReady hooks
  9. Run extension setup functions (topological order)
  10. Run global middleware → command middleware → command.run(seed)
  11. Run extension teardown (reverse order)
  12. Cleanup and exit

The run() Shortcut

For simple CLIs that don't need the full builder, use the run() function:

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

await run({
  name: 'my-cli',
  version: '1.0.0',
  commands: [
    command({
      name: 'hello',
      args: { name: arg({ type: 'string', required: true }) },
      run: (seed) => seed.print.info(`Hello, ${seed.args.name}!`),
    }),
  ],
})

run() creates a Builder internally — it's syntactic sugar for quick scripts and simple CLIs.

On this page