Variadic Arguments
Use ... for variadic arguments:
cli.command('install <packages...>', 'Install packages')
.action((packages: string[]) => {
console.log(`Installing: ${packages.join(', ')}`)
})
Multiple Arguments
Combine multiple arguments:
cli.command('move <source> <destination> [options...]', 'Move files')
.action((source: string, destination: string, options: string[]) => {
console.log(`Moving ${source} to ${destination}`)
if (options.length > 0) {
console.log(`Options: ${options.join(', ')}`)
}
})
Options
Boolean Options
cli.command('build', 'Build the project')
.option('--verbose, -v', 'Enable verbose output')
.option('--watch, -w', 'Watch for changes')
.option('--minify, -m', 'Minify output')
.action((options) => {
if (options.verbose) {
console.log('Verbose mode enabled')
}
// Build logic
})
Options with Values
cli.command('serve', 'Start the server')
.option('--port, -p <port>', 'Port to listen on', { default: 3000 })
.option('--host, -h <host>', 'Host to bind to', { default: 'localhost' })
.action((options) => {
console.log(`Starting server on ${options.host}:${options.port}`)
})
Required Options
cli.command('deploy', 'Deploy the application')
.option('--environment, -e <env>', 'Deployment environment', { required: true })
.action((options) => {
console.log(`Deploying to ${options.environment}`)
})
Option Types
cli.command('process', 'Process data')
// String option
.option('--format, -f <format>', 'Output format', { default: 'json' })
// Number option (parsed automatically)
.option('--count, -c <count>', 'Number of items', { default: 10 })
// Boolean option (flag)
.option('--dry-run, -d', 'Perform dry run')
// Array option
.option('--include, -i <patterns...>', 'Patterns to include')
.action((options) => {
console.log('Format:', options.format) // string
console.log('Count:', options.count) // number
console.log('Dry run:', options.dryRun) // boolean
console.log('Include:', options.include) // string[]
})
Option Validation
cli.command('resize', 'Resize an image')
.option('--width, -w <width>', 'Image width', {
default: 100,
validate: (value) => {
const num = parseInt(value, 10)
if (isNaN(num) || num < 1) {
return 'Width must be a positive number'
}
return true
},
})
.action((options) => {
console.log(`Resizing to width: ${options.width}`)
})
Input/Output
Console Output
import { log } from '@stacksjs/cli'
export default function (cli: CLI) {
cli.command('status', 'Check status')
.action(() => {
// Standard log levels
log.info('Information message')
log.success('Success message')
log.warn('Warning message')
log.error('Error message')
log.debug('Debug message')
// Formatted output
console.log('') // Empty line
console.log('Plain output')
})
}
Styled Output
import { bold, green, red, yellow, italic, dim } from '@stacksjs/cli'
export default function (cli: CLI) {
cli.command('styled', 'Show styled output')
.action(() => {
console.log(bold('Bold text'))
console.log(green('Green text'))
console.log(red('Red text'))
console.log(yellow('Yellow text'))
console.log(italic('Italic text'))
console.log(dim('Dimmed text'))
// Combine styles
console.log(bold(green('Bold and green')))
})
}
Progress Indicators
import { spinner } from '@stacksjs/cli'
export default function (cli: CLI) {
cli.command('download', 'Download files')
.action(async () => {
const spin = spinner('Downloading...')
spin.start()
try {
await downloadFiles()
spin.succeed('Download complete')
} catch (error) {
spin.fail('Download failed')
}
})
}
Interactive Prompts
import { prompt, confirm, select, multiselect } from '@stacksjs/cli'
export default function (cli: CLI) {
cli.command('setup', 'Interactive setup')
.action(async () => {
// Text input
const name = await prompt('What is your name?')
// Confirmation
const proceed = await confirm('Do you want to continue?')
// Single selection
const framework = await select('Choose a framework:', [
'Vue',
'React',
'Svelte',
])
// Multiple selection
const features = await multiselect('Select features:', [
{ name: 'TypeScript', value: 'ts', checked: true },
{ name: 'ESLint', value: 'eslint', checked: true },
{ name: 'Prettier', value: 'prettier' },
])
console.log({ name, proceed, framework, features })
})
}
Command Aliases
Single Alias
cli.command('inspire', 'Inspire yourself')
.alias('insp')
.action(() => {
// Can be run as `buddy inspire` or `buddy insp`
})
Multiple Aliases
cli.command('generate:component', 'Generate a component')
.alias('g:c')
.alias('gc')
.action(() => {
// Can be run as any of the aliases
})
Subcommands
Creating Subcommands
// app/Commands/User.ts
export default function (cli: CLI) {
// Main command group
cli.command('user', 'User management commands')
// Subcommands
cli.command('user:create <email>', 'Create a new user')
.option('--admin', 'Create as admin')
.action(async (email, options) => {
await createUser(email, options.admin)
})
cli.command('user:delete <id>', 'Delete a user')
.option('--force', 'Skip confirmation')
.action(async (id, options) => {
if (!options.force) {
const confirmed = await confirm('Are you sure?')
if (!confirmed) return
}
await deleteUser(id)
})
cli.command('user:list', 'List all users')
.option('--format, -f <format>', 'Output format', { default: 'table' })
.action(async (options) => {
const users = await getUsers()
displayUsers(users, options.format)
})
// Catch unknown subcommands
cli.on('user:*', () => {
log.error('Invalid command. See --help for available commands.')
process.exit(1)
})
return cli
}
Calling Commands Programmatically
From Actions
// app/Actions/MaintenanceAction.ts
import { runCommand } from '@stacksjs/cli'
export default new Action({
async handle() {
// Run a CLI command
await runCommand('buddy cache:clear')
await runCommand('buddy queue:restart')
return response.json({ message: 'Maintenance complete' })
},
})
From Other Commands
export default function (cli: CLI) {
cli.command('reset', 'Reset the application')
.action(async () => {
const { runCommand } = await import('@stacksjs/cli')
log.info('Clearing cache...')
await runCommand('buddy cache:clear')
log.info('Running migrations...')
await runCommand('buddy migrate:fresh')
log.info('Seeding database...')
await runCommand('buddy db:seed')
log.success('Application reset complete!')
})
}
Using exec
import { exec } from '@stacksjs/cli'
export default function (cli: CLI) {
cli.command('deploy', 'Deploy application')
.action(async () => {
// Run shell commands
const result = await exec('git status')
if (result.isOk()) {
console.log(result.value.stdout)
} else {
console.error(result.error)
}
})
}
Error Handling
Exit Codes
import { ExitCode } from '@stacksjs/types'
export default function (cli: CLI) {
cli.command('check', 'Check something')
.action(() => {
try {
// Check logic
process.exit(ExitCode.Success) // 0
} catch (error) {
process.exit(ExitCode.FatalError) // 1
}
})
}
Graceful Error Handling
import { handleError } from '@stacksjs/error-handling'
export default function (cli: CLI) {
cli.command('risky', 'Risky operation')
.action(async () => {
try {
await riskyOperation()
log.success('Operation completed')
} catch (error) {
handleError(error)
log.error('Operation failed')
process.exit(1)
}
})
}
Validation Errors
export default function (cli: CLI) {
cli.command('process <file>', 'Process a file')
.action(async (file) => {
// Validate input
if (!file.endsWith('.json')) {
log.error('File must be a JSON file')
process.exit(1)
}
// Check file exists
if (!await exists(file)) {
log.error(`File not found: ${file}`)
process.exit(1)
}
// Process file
await processFile(file)
})
}
Best Practices
Command Structure
// Recommended command structure
export default function (cli: CLI) {
cli
.command('namespace:action', 'Description of what this command does')
.option('--option, -o <value>', 'Option description', { default: 'default' })
.alias('n:a')
.action(async (options) => {
try {
// 1. Validate inputs
validateInputs(options)
// 2. Show progress
const spin = spinner('Processing...')
spin.start()
// 3. Perform action
const result = await performAction(options)
// 4. Show success
spin.succeed('Complete!')
displayResult(result)
// 5. Exit cleanly
process.exit(ExitCode.Success)
} catch (error) {
// 6. Handle errors gracefully
handleError(error)
process.exit(ExitCode.FatalError)
}
})
return cli
}
Help Text
cli.command('complex', 'A complex command')
.option('--input, -i <file>', 'Input file path')
.option('--output, -o <file>', 'Output file path')
.example('buddy complex -i data.json -o result.json')
.example('buddy complex --input=data.json --output=result.json')
Edge Cases and Gotchas
Option Name Conflicts
Avoid using reserved option names:
// Avoid these (used by CLI framework)
// --help, -h
// --version, -v (if version is enabled)
Async Actions
Always handle async operations properly:
// Correct: await async operations
cli.command('async', 'Async command')
.action(async () => {
await someAsyncOperation()
process.exit(0)
})
// Incorrect: missing await causes early exit
cli.command('async', 'Async command')
.action(() => {
someAsyncOperation() // Command exits before this completes
process.exit(0)
})
Environment Variables
Access environment variables in commands:
cli.command('config', 'Show configuration')
.action(() => {
const env = process.env.NODE_ENV || 'development'
const debug = process.env.DEBUG === 'true'
console.log(`Environment: ${env}`)
console.log(`Debug: ${debug}`)
})
API Reference
CLI Methods
| Method | Description |
|---|---|
command(name, description) | Define a command |
option(flags, description, config?) | Add an option |
alias(name) | Add a command alias |
action(handler) | Set the action handler |
example(text) | Add an example |
on(event, handler) | Listen for events |
Exit Codes
| Code | Constant | Description |
|---|---|---|
| 0 | ExitCode.Success | Successful execution |
| 1 | ExitCode.FatalError | General error |
Log Methods
| Method | Description |
|---|---|
log.info(message) | Information message |
log.success(message) | Success message |
log.warn(message) | Warning message |
log.error(message) | Error message |
log.debug(message) | Debug message |
Related Documentation
- Actions - HTTP request handlers
- Jobs - Background job processing
- Logging - Application logging
- Error Handling - Error management