← スキル一覧に戻る
cli-patterns
ADLenehan / battery
⭐ 0🍴 0📅 2026年1月13日
Patterns for building Battery CLI commands with Commander.js. Use this skill when creating new CLI commands, adding options/flags, formatting output, handling errors, or implementing interactive prompts.
SKILL.md
---
name: cli-patterns
description: Patterns for building Battery CLI commands with Commander.js. Use this skill when creating new CLI commands, adding options/flags, formatting output, handling errors, or implementing interactive prompts.
---
# CLI Patterns
## Command Structure
Use Commander.js for all CLI commands. Commands live in `packages/cli/src/commands/`.
### Basic Command
```typescript
import { Command } from 'commander'
export const deployCommand = new Command('deploy')
.description('Deploy an application to Battery')
.argument('<path>', 'Path to the application directory')
.option('-e, --env <environment>', 'Target environment', 'production')
.option('--dry-run', 'Show what would be deployed without deploying')
.action(async (path, options) => {
// Implementation
})
```
### Command with Subcommands
```typescript
export const configCommand = new Command('config')
.description('Manage Battery configuration')
configCommand
.command('set <key> <value>')
.description('Set a configuration value')
.action(async (key, value) => {})
configCommand
.command('get <key>')
.description('Get a configuration value')
.action(async (key) => {})
```
## Output Formatting
### Chalk for Colors
```typescript
import chalk from 'chalk'
// Status messages
console.log(chalk.green('✓'), 'Deployment successful')
console.log(chalk.yellow('!'), 'Warning: No auth detected')
console.log(chalk.red('✗'), 'Error: Invalid credentials')
// Emphasis
console.log(chalk.bold('Scanning...'))
console.log(chalk.dim('Press Ctrl+C to cancel'))
// URLs and paths
console.log(chalk.cyan('https://app.battery.dev'))
console.log(chalk.gray('./src/config.ts'))
```
### Ora for Spinners
```typescript
import ora from 'ora'
const spinner = ora('Scanning for credentials...').start()
try {
const results = await scan(path)
spinner.succeed('Scan complete')
} catch (error) {
spinner.fail('Scan failed')
throw error
}
```
### Tree Output
```typescript
function printTree(items: string[], indent = 0): void {
const prefix = ' '.repeat(indent)
items.forEach((item, i) => {
const isLast = i === items.length - 1
const branch = isLast ? '└──' : '├──'
console.log(`${prefix}${branch} ${item}`)
})
}
// Output:
// ├── Detected: Next.js
// ├── Found credentials:
// │ ├── SNOWFLAKE_PASSWORD in .env
// │ └── SALESFORCE_TOKEN in lib/api.ts
// └── Deploying...
```
## Error Handling
### Exit Codes
```typescript
export const ExitCode = {
Success: 0,
GeneralError: 1,
InvalidArgument: 2,
ConfigError: 3,
NetworkError: 4,
AuthError: 5,
} as const
process.exit(ExitCode.InvalidArgument)
```
### Error Display
```typescript
import chalk from 'chalk'
function handleError(error: Error): never {
console.error()
console.error(chalk.red('Error:'), error.message)
if (error.cause) {
console.error(chalk.dim('Cause:'), String(error.cause))
}
if (process.env.DEBUG) {
console.error(chalk.dim(error.stack))
}
process.exit(ExitCode.GeneralError)
}
```
### Graceful Shutdown
```typescript
process.on('SIGINT', () => {
console.log()
console.log(chalk.dim('Cancelled'))
process.exit(130)
})
```
## Interactive Prompts
Use @inquirer/prompts for user input.
### Confirmation
```typescript
import { confirm } from '@inquirer/prompts'
const proceed = await confirm({
message: 'Deploy to production?',
default: false,
})
```
### Selection
```typescript
import { select } from '@inquirer/prompts'
const environment = await select({
message: 'Select environment',
choices: [
{ name: 'Production', value: 'production' },
{ name: 'Staging', value: 'staging' },
{ name: 'Development', value: 'development' },
],
})
```
### Text Input
```typescript
import { input } from '@inquirer/prompts'
const projectName = await input({
message: 'Project name',
default: path.basename(process.cwd()),
validate: (value) => {
if (!/^[a-z0-9-]+$/.test(value)) {
return 'Must be lowercase alphanumeric with hyphens'
}
return true
},
})
```
### Password Input
```typescript
import { password } from '@inquirer/prompts'
const token = await password({
message: 'Enter your API token',
mask: '*',
})
```
## Configuration Files
### Config Location
```typescript
import { homedir } from 'os'
import { join } from 'path'
const CONFIG_DIR = join(homedir(), '.battery')
const CONFIG_FILE = join(CONFIG_DIR, 'config.json')
const CREDENTIALS_FILE = join(CONFIG_DIR, 'credentials.json')
```
### Config Schema
```typescript
interface BatteryConfig {
defaultOrg?: string
defaultEnvironment?: 'production' | 'staging'
telemetry?: boolean
}
```
## Patterns to Follow
1. **Always show progress** - Use spinners for operations > 500ms
2. **Confirm destructive actions** - Prompt before deleting or overwriting
3. **Support --json flag** - For programmatic output
4. **Respect NO_COLOR** - Disable colors when env var is set
5. **Use stderr for errors** - Keep stdout clean for piping