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
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:
- Register SIGINT/SIGTERM handlers
- Strip
--debug/--verboseflags (if enabled) - Handle
--version/--helpearly exits - Discover and load plugins, register extensions
- Route to the matching command
- Parse command-specific args and flags
- Assemble the
seedcontext - Run
onReadyhooks - Run extension
setupfunctions (topological order) - Run global middleware → command middleware →
command.run(seed) - Run extension
teardown(reverse order) - 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.