Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:04:14 +08:00
commit 70c36b5eff
248 changed files with 47482 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
# Basic CLI Example
Complete example of a simple oclif CLI with multiple commands.
## Project Structure
```
mycli/
├── package.json
├── tsconfig.json
├── src/
│ ├── commands/
│ │ ├── hello.ts
│ │ ├── goodbye.ts
│ │ └── config.ts
│ └── index.ts
├── test/
│ └── commands/
│ └── hello.test.ts
└── bin/
└── run.js
```
## Step-by-Step Setup
### 1. Initialize Project
```bash
mkdir mycli && cd mycli
npm init -y
npm install @oclif/core
npm install --save-dev @oclif/test @types/node typescript ts-node oclif
```
### 2. Create package.json Configuration
Add oclif configuration:
```json
{
"name": "mycli",
"version": "1.0.0",
"oclif": {
"bin": "mycli",
"commands": "./lib/commands",
"plugins": [
"@oclif/plugin-help"
]
}
}
```
### 3. Create Hello Command
File: `src/commands/hello.ts`
```typescript
import { Command, Flags, Args } from '@oclif/core'
export default class Hello extends Command {
static description = 'Say hello to someone'
static examples = [
'<%= config.bin %> <%= command.id %> Alice',
'<%= config.bin %> <%= command.id %> Bob --greeting="Hi"',
]
static flags = {
greeting: Flags.string({
char: 'g',
description: 'Greeting to use',
default: 'Hello',
}),
excited: Flags.boolean({
char: 'e',
description: 'Add exclamation',
default: false,
}),
}
static args = {
name: Args.string({
description: 'Name to greet',
required: true,
}),
}
async run(): Promise<void> {
const { args, flags } = await this.parse(Hello)
const punctuation = flags.excited ? '!' : '.'
this.log(`${flags.greeting}, ${args.name}${punctuation}`)
}
}
```
### 4. Build and Test
```bash
npm run build
./bin/run.js hello World
# Output: Hello, World.
./bin/run.js hello World --greeting="Hi" --excited
# Output: Hi, World!
```
### 5. Add Help Documentation
```bash
./bin/run.js hello --help
# Output:
# Say hello to someone
#
# USAGE
# $ mycli hello NAME [-g <value>] [-e]
#
# ARGUMENTS
# NAME Name to greet
#
# FLAGS
# -e, --excited Add exclamation
# -g, --greeting=<value> [default: Hello] Greeting to use
```
## Testing
File: `test/commands/hello.test.ts`
```typescript
import { expect, test } from '@oclif/test'
describe('hello', () => {
test
.stdout()
.command(['hello', 'World'])
.it('says hello', ctx => {
expect(ctx.stdout).to.contain('Hello, World.')
})
test
.stdout()
.command(['hello', 'Alice', '--excited'])
.it('says hello with excitement', ctx => {
expect(ctx.stdout).to.contain('Hello, Alice!')
})
test
.stdout()
.command(['hello', 'Bob', '--greeting=Hi'])
.it('uses custom greeting', ctx => {
expect(ctx.stdout).to.contain('Hi, Bob.')
})
})
```
## Run Tests
```bash
npm test
```
## Distribution
### Package for npm
```bash
npm pack
npm publish
```
### Install Globally
```bash
npm install -g .
mycli hello World
```
## Key Concepts Demonstrated
1. **Command Structure**: Basic command with flags and args
2. **Flag Types**: String and boolean flags with defaults
3. **Arguments**: Required string argument
4. **Help Documentation**: Auto-generated from metadata
5. **Testing**: Using @oclif/test for command testing
6. **Build Process**: TypeScript compilation to lib/
7. **CLI Binary**: bin/run.js entry point

View File

@@ -0,0 +1,427 @@
# Enterprise CLI Example
Complete production-ready oclif CLI with all best practices.
## Overview
This example demonstrates:
- Custom base command with common functionality
- Configuration management
- Logging system
- Error handling
- Plugin support
- Comprehensive testing
- CI/CD integration
- Auto-update capability
## Project Structure
```
enterprise-cli/
├── package.json
├── tsconfig.json
├── .eslintrc.json
├── .github/
│ └── workflows/
│ ├── test.yml
│ └── release.yml
├── src/
│ ├── base-command.ts
│ ├── config/
│ │ ├── manager.ts
│ │ └── schema.ts
│ ├── commands/
│ │ ├── deploy.ts
│ │ ├── status.ts
│ │ └── config/
│ │ ├── get.ts
│ │ └── set.ts
│ ├── hooks/
│ │ ├── init.ts
│ │ └── prerun.ts
│ ├── utils/
│ │ ├── logger.ts
│ │ └── error-handler.ts
│ └── index.ts
├── test/
│ ├── commands/
│ ├── helpers/
│ └── fixtures/
└── docs/
└── commands/
```
## Base Command Implementation
File: `src/base-command.ts`
```typescript
import { Command, Flags } from '@oclif/core'
import { ConfigManager } from './config/manager'
import { Logger } from './utils/logger'
import { ErrorHandler } from './utils/error-handler'
export default abstract class BaseCommand extends Command {
protected configManager!: ConfigManager
protected logger!: Logger
protected errorHandler!: ErrorHandler
static baseFlags = {
config: Flags.string({
char: 'c',
description: 'Path to config file',
env: 'CLI_CONFIG',
}),
'log-level': Flags.string({
description: 'Set log level',
options: ['error', 'warn', 'info', 'debug'],
default: 'info',
env: 'LOG_LEVEL',
}),
json: Flags.boolean({
description: 'Output as JSON',
default: false,
}),
'no-color': Flags.boolean({
description: 'Disable colors',
default: false,
}),
}
async init(): Promise<void> {
await super.init()
// Initialize logger
const { flags } = await this.parse(this.constructor as typeof BaseCommand)
this.logger = new Logger(flags['log-level'], !flags['no-color'])
// Initialize config manager
this.configManager = new ConfigManager(flags.config)
await this.configManager.load()
// Initialize error handler
this.errorHandler = new ErrorHandler(this.logger)
// Log initialization
this.logger.debug(`Initialized ${this.id}`)
}
protected async catch(err: Error & { exitCode?: number }): Promise<any> {
return this.errorHandler.handle(err)
}
protected output(data: any, humanMessage?: string): void {
const { flags } = this.parse(this.constructor as typeof BaseCommand)
if (flags.json) {
this.log(JSON.stringify(data, null, 2))
} else if (humanMessage) {
this.log(humanMessage)
} else {
this.log(JSON.stringify(data, null, 2))
}
}
}
```
## Configuration Manager
File: `src/config/manager.ts`
```typescript
import * as fs from 'fs-extra'
import * as path from 'path'
import * as os from 'os'
export class ConfigManager {
private config: any = {}
private configPath: string
constructor(customPath?: string) {
this.configPath = customPath || this.getDefaultConfigPath()
}
async load(): Promise<void> {
if (await fs.pathExists(this.configPath)) {
this.config = await fs.readJson(this.configPath)
} else {
// Create default config
this.config = this.getDefaultConfig()
await this.save()
}
}
async save(): Promise<void> {
await fs.ensureDir(path.dirname(this.configPath))
await fs.writeJson(this.configPath, this.config, { spaces: 2 })
}
get(key: string, defaultValue?: any): any {
const keys = key.split('.')
let value = this.config
for (const k of keys) {
if (value && typeof value === 'object' && k in value) {
value = value[k]
} else {
return defaultValue
}
}
return value
}
set(key: string, value: any): void {
const keys = key.split('.')
const lastKey = keys.pop()!
let current = this.config
for (const k of keys) {
if (!(k in current) || typeof current[k] !== 'object') {
current[k] = {}
}
current = current[k]
}
current[lastKey] = value
}
private getDefaultConfigPath(): string {
return path.join(os.homedir(), '.config', 'mycli', 'config.json')
}
private getDefaultConfig(): any {
return {
version: '1.0.0',
defaults: {
region: 'us-east-1',
timeout: 30000,
},
}
}
}
```
## Logger Implementation
File: `src/utils/logger.ts`
```typescript
import chalk from 'chalk'
export class Logger {
constructor(
private level: string = 'info',
private color: boolean = true
) {}
debug(message: string): void {
if (this.shouldLog('debug')) {
this.output('DEBUG', message, chalk.gray)
}
}
info(message: string): void {
if (this.shouldLog('info')) {
this.output('INFO', message, chalk.blue)
}
}
warn(message: string): void {
if (this.shouldLog('warn')) {
this.output('WARN', message, chalk.yellow)
}
}
error(message: string, error?: Error): void {
if (this.shouldLog('error')) {
this.output('ERROR', message, chalk.red)
if (error && error.stack) {
console.error(chalk.red(error.stack))
}
}
}
success(message: string): void {
if (this.shouldLog('info')) {
this.output('SUCCESS', message, chalk.green)
}
}
private shouldLog(level: string): boolean {
const levels = ['error', 'warn', 'info', 'debug']
const currentIndex = levels.indexOf(this.level)
const messageIndex = levels.indexOf(level)
return messageIndex <= currentIndex
}
private output(level: string, message: string, colorFn: any): void {
const timestamp = new Date().toISOString()
const prefix = this.color ? colorFn(`[${level}]`) : `[${level}]`
console.log(`${timestamp} ${prefix} ${message}`)
}
}
```
## Enterprise Deploy Command
File: `src/commands/deploy.ts`
```typescript
import BaseCommand from '../base-command'
import { Flags, Args, ux } from '@oclif/core'
export default class Deploy extends BaseCommand {
static description = 'Deploy application to environment'
static examples = [
'<%= config.bin %> deploy myapp --env production',
'<%= config.bin %> deploy myapp --env staging --auto-approve',
]
static flags = {
...BaseCommand.baseFlags,
env: Flags.string({
char: 'e',
description: 'Environment to deploy to',
options: ['development', 'staging', 'production'],
required: true,
}),
'auto-approve': Flags.boolean({
description: 'Skip confirmation prompt',
default: false,
}),
'rollback-on-failure': Flags.boolean({
description: 'Automatically rollback on failure',
default: true,
}),
}
static args = {
app: Args.string({
description: 'Application name',
required: true,
}),
}
async run(): Promise<void> {
const { args, flags } = await this.parse(Deploy)
this.logger.info(`Starting deployment of ${args.app} to ${flags.env}`)
// Confirmation prompt (skip in CI or with auto-approve)
if (!flags['auto-approve'] && !process.env.CI) {
const confirmed = await ux.confirm(
`Deploy ${args.app} to ${flags.env}? (y/n)`
)
if (!confirmed) {
this.logger.info('Deployment cancelled')
return
}
}
// Pre-deployment checks
ux.action.start('Running pre-deployment checks')
await this.runPreDeploymentChecks(args.app, flags.env)
ux.action.stop('passed')
// Deploy
ux.action.start(`Deploying ${args.app}`)
try {
const result = await this.deploy(args.app, flags.env)
ux.action.stop('done')
this.logger.success(`Deployed ${args.app} to ${flags.env}`)
this.output(result, `Deployment URL: ${result.url}`)
} catch (error) {
ux.action.stop('failed')
if (flags['rollback-on-failure']) {
this.logger.warn('Deployment failed, rolling back...')
await this.rollback(args.app, flags.env)
}
throw error
}
}
private async runPreDeploymentChecks(
app: string,
env: string
): Promise<void> {
// Check credentials
// Validate app exists
// Check environment health
// Verify dependencies
await new Promise(resolve => setTimeout(resolve, 1000))
}
private async deploy(app: string, env: string): Promise<any> {
// Actual deployment logic
await new Promise(resolve => setTimeout(resolve, 3000))
return {
app,
env,
version: '1.2.3',
url: `https://${app}.${env}.example.com`,
deployedAt: new Date().toISOString(),
}
}
private async rollback(app: string, env: string): Promise<void> {
// Rollback logic
await new Promise(resolve => setTimeout(resolve, 2000))
this.logger.info('Rollback complete')
}
}
```
## Testing Setup
File: `test/helpers/test-context.ts`
```typescript
import * as path from 'path'
import * as fs from 'fs-extra'
import { Config } from '@oclif/core'
export class TestContext {
testDir: string
config: Config
constructor() {
this.testDir = path.join(__dirname, '../fixtures/test-run')
}
async setup(): Promise<void> {
await fs.ensureDir(this.testDir)
this.config = await Config.load()
}
async teardown(): Promise<void> {
await fs.remove(this.testDir)
}
async createConfigFile(config: any): Promise<string> {
const configPath = path.join(this.testDir, 'config.json')
await fs.writeJson(configPath, config)
return configPath
}
}
```
## Key Enterprise Features
1. **Base Command**: Shared functionality across all commands
2. **Configuration**: Centralized config management
3. **Logging**: Structured logging with levels
4. **Error Handling**: Consistent error handling
5. **Confirmation Prompts**: Interactive confirmations
6. **Rollback**: Automatic rollback on failure
7. **Pre-deployment Checks**: Validation before operations
8. **CI Detection**: Different behavior in CI environments
9. **JSON Output**: Machine-readable output option
10. **Environment Variables**: Config via env vars

View File

@@ -0,0 +1,307 @@
# Plugin CLI Example
Complete example of an oclif CLI with plugin support.
## Overview
This example shows:
- Main CLI with core commands
- Plugin system for extensibility
- Plugin installation and management
- Shared hooks between CLI and plugins
## Main CLI Structure
```
mycli/
├── package.json
├── src/
│ ├── commands/
│ │ └── core.ts
│ └── hooks/
│ └── init.ts
└── plugins/
└── plugin-deploy/
├── package.json
└── src/
└── commands/
└── deploy.ts
```
## Step 1: Create Main CLI
### Main CLI package.json
```json
{
"name": "mycli",
"version": "1.0.0",
"oclif": {
"bin": "mycli",
"commands": "./lib/commands",
"plugins": [
"@oclif/plugin-help",
"@oclif/plugin-plugins"
],
"hooks": {
"init": "./lib/hooks/init"
}
},
"dependencies": {
"@oclif/core": "^3.0.0",
"@oclif/plugin-help": "^6.0.0",
"@oclif/plugin-plugins": "^4.0.0"
}
}
```
### Core Command
File: `src/commands/core.ts`
```typescript
import { Command, Flags } from '@oclif/core'
export default class Core extends Command {
static description = 'Core CLI functionality'
static flags = {
version: Flags.boolean({
char: 'v',
description: 'Show CLI version',
}),
}
async run(): Promise<void> {
const { flags } = await this.parse(Core)
if (flags.version) {
this.log(`Version: ${this.config.version}`)
return
}
this.log('Core CLI is running')
// List installed plugins
const plugins = this.config.plugins
this.log(`\nInstalled plugins: ${plugins.length}`)
plugins.forEach(p => {
this.log(` - ${p.name} (${p.version})`)
})
}
}
```
### Init Hook
File: `src/hooks/init.ts`
```typescript
import { Hook } from '@oclif/core'
const hook: Hook<'init'> = async function (opts) {
// Initialize CLI
this.debug('Initializing mycli...')
// Check for updates, load config, etc.
}
export default hook
```
## Step 2: Create Plugin
### Plugin package.json
```json
{
"name": "@mycli/plugin-deploy",
"version": "1.0.0",
"description": "Deployment plugin for mycli",
"oclif": {
"bin": "mycli",
"commands": "./lib/commands",
"topics": {
"deploy": {
"description": "Deployment commands"
}
}
},
"dependencies": {
"@oclif/core": "^3.0.0"
}
}
```
### Deploy Command
File: `plugins/plugin-deploy/src/commands/deploy.ts`
```typescript
import { Command, Flags } from '@oclif/core'
export default class Deploy extends Command {
static description = 'Deploy application'
static examples = [
'<%= config.bin %> deploy --env production',
]
static flags = {
env: Flags.string({
char: 'e',
description: 'Environment to deploy to',
options: ['development', 'staging', 'production'],
required: true,
}),
force: Flags.boolean({
char: 'f',
description: 'Force deployment',
default: false,
}),
}
async run(): Promise<void> {
const { flags } = await this.parse(Deploy)
this.log(`Deploying to ${flags.env}...`)
if (flags.force) {
this.log('Force deployment enabled')
}
// Deployment logic here
this.log('✓ Deployment successful')
}
}
```
## Step 3: Build and Link Plugin
```bash
# Build main CLI
cd mycli
npm run build
# Build plugin
cd plugins/plugin-deploy
npm run build
# Link plugin to main CLI
cd ../../
mycli plugins:link ./plugins/plugin-deploy
```
## Step 4: Use Plugin Commands
```bash
# List plugins
mycli plugins
# Use plugin command
mycli deploy --env production
# Get help for plugin command
mycli deploy --help
```
## Step 5: Install Plugin from npm
### Publish Plugin
```bash
cd plugins/plugin-deploy
npm publish
```
### Install Plugin
```bash
mycli plugins:install @mycli/plugin-deploy
```
## Plugin Management Commands
```bash
# List installed plugins
mycli plugins
# Install plugin
mycli plugins:install @mycli/plugin-name
# Update plugin
mycli plugins:update @mycli/plugin-name
# Uninstall plugin
mycli plugins:uninstall @mycli/plugin-name
# Link local plugin (development)
mycli plugins:link /path/to/plugin
```
## Advanced: Plugin with Hooks
File: `plugins/plugin-deploy/src/hooks/prerun.ts`
```typescript
import { Hook } from '@oclif/core'
const hook: Hook<'prerun'> = async function (opts) {
// Check deployment prerequisites
if (opts.Command.id === 'deploy') {
this.log('Checking deployment prerequisites...')
// Check environment, credentials, etc.
}
}
export default hook
```
Register in plugin package.json:
```json
{
"oclif": {
"hooks": {
"prerun": "./lib/hooks/prerun"
}
}
}
```
## Key Concepts Demonstrated
1. **Plugin System**: @oclif/plugin-plugins integration
2. **Plugin Discovery**: Automatic command loading from plugins
3. **Plugin Management**: Install, update, uninstall commands
4. **Local Development**: plugins:link for local plugin development
5. **Hooks**: Shared hooks between main CLI and plugins
6. **Topic Commands**: Organized plugin commands (deploy:*)
7. **Plugin Metadata**: Package.json oclif configuration
8. **Plugin Distribution**: Publishing to npm
## Testing Plugins
File: `plugins/plugin-deploy/test/commands/deploy.test.ts`
```typescript
import { expect, test } from '@oclif/test'
describe('deploy', () => {
test
.stdout()
.command(['deploy', '--env', 'production'])
.it('deploys to production', ctx => {
expect(ctx.stdout).to.contain('Deploying to production')
expect(ctx.stdout).to.contain('successful')
})
test
.command(['deploy'])
.catch(error => {
expect(error.message).to.contain('Missing required flag')
})
.it('requires env flag')
})
```

View File

@@ -0,0 +1,393 @@
# oclif Patterns Quick Reference
Fast lookup for common oclif patterns and commands.
## Command Creation
### Basic Command
```typescript
import { Command, Flags, Args } from '@oclif/core'
export default class MyCommand extends Command {
static description = 'Description'
static flags = {
name: Flags.string({ char: 'n', required: true }),
}
async run(): Promise<void> {
const { flags } = await this.parse(MyCommand)
this.log(`Hello ${flags.name}`)
}
}
```
### Using Scripts
```bash
# Create command from template
./scripts/create-command.sh my-command basic
# Create advanced command
./scripts/create-command.sh deploy advanced
# Create async command
./scripts/create-command.sh fetch async
```
## Flag Patterns
### String Flag
```typescript
name: Flags.string({
char: 'n',
description: 'Name',
required: true,
default: 'World',
})
```
### Boolean Flag
```typescript
verbose: Flags.boolean({
char: 'v',
description: 'Verbose output',
default: false,
allowNo: true, // Enables --no-verbose
})
```
### Integer Flag
```typescript
port: Flags.integer({
char: 'p',
description: 'Port number',
min: 1024,
max: 65535,
default: 3000,
})
```
### Option Flag (Enum)
```typescript
env: Flags.string({
char: 'e',
description: 'Environment',
options: ['dev', 'staging', 'prod'],
required: true,
})
```
### Multiple Values
```typescript
tags: Flags.string({
char: 't',
description: 'Tags',
multiple: true,
})
// Usage: --tags=foo --tags=bar
```
### Custom Flag
```typescript
date: Flags.custom<Date>({
parse: async (input) => new Date(input),
})
```
## Argument Patterns
### Required Argument
```typescript
static args = {
file: Args.string({
description: 'File path',
required: true,
}),
}
```
### File Argument
```typescript
static args = {
file: Args.file({
description: 'Input file',
exists: true, // Validates file exists
}),
}
```
### Directory Argument
```typescript
static args = {
dir: Args.directory({
description: 'Target directory',
exists: true,
}),
}
```
## Output Patterns
### Simple Log
```typescript
this.log('Message')
```
### Error with Exit
```typescript
this.error('Error message', { exit: 1 })
```
### Warning
```typescript
this.warn('Warning message')
```
### Spinner
```typescript
import { ux } from '@oclif/core'
ux.action.start('Processing')
// ... work
ux.action.stop('done')
```
### Progress Bar
```typescript
import { ux } from '@oclif/core'
const total = 100
ux.progress.start(total)
for (let i = 0; i < total; i++) {
ux.progress.update(i)
}
ux.progress.stop()
```
### Table Output
```typescript
import { ux } from '@oclif/core'
ux.table(data, {
id: {},
name: {},
status: { extended: true },
})
```
### Prompt
```typescript
import { ux } from '@oclif/core'
const name = await ux.prompt('What is your name?')
const password = await ux.prompt('Password', { type: 'hide' })
const confirmed = await ux.confirm('Continue? (y/n)')
```
## Testing Patterns
### Basic Test
```typescript
import { expect, test } from '@oclif/test'
test
.stdout()
.command(['mycommand', '--name', 'Test'])
.it('runs command', ctx => {
expect(ctx.stdout).to.contain('Test')
})
```
### Test with Error
```typescript
test
.command(['mycommand'])
.catch(error => {
expect(error.message).to.contain('Missing')
})
.it('fails without flags')
```
### Test with Mock
```typescript
test
.nock('https://api.example.com', api =>
api.get('/data').reply(200, { result: 'success' })
)
.stdout()
.command(['mycommand'])
.it('handles API call', ctx => {
expect(ctx.stdout).to.contain('success')
})
```
### Test with Environment
```typescript
test
.env({ API_KEY: 'test-key' })
.stdout()
.command(['mycommand'])
.it('reads from env')
```
## Plugin Patterns
### Create Plugin
```bash
./scripts/create-plugin.sh my-plugin
```
### Link Plugin
```bash
mycli plugins:link ./plugin-my-plugin
```
### Install Plugin
```bash
mycli plugins:install @mycli/plugin-name
```
## Hook Patterns
### Init Hook
```typescript
import { Hook } from '@oclif/core'
const hook: Hook<'init'> = async function (opts) {
// Runs before any command
}
export default hook
```
### Prerun Hook
```typescript
const hook: Hook<'prerun'> = async function (opts) {
const { Command, argv } = opts
// Runs before each command
}
```
## Common Commands
### Generate Documentation
```bash
npm run prepack
# Generates oclif.manifest.json and updates README.md
```
### Build
```bash
npm run build
```
### Test
```bash
npm test
npm run test:coverage
```
### Lint
```bash
npm run lint
npm run lint:fix
```
## Validation
### Validate Command
```bash
./scripts/validate-command.sh src/commands/mycommand.ts
```
### Validate Plugin
```bash
./scripts/validate-plugin.sh ./my-plugin
```
### Validate Tests
```bash
./scripts/validate-tests.sh
```
## Configuration Patterns
### Read Config
```typescript
const configPath = path.join(this.config.home, '.myclirc')
const config = await fs.readJson(configPath)
```
### Write Config
```typescript
await fs.writeJson(configPath, config, { spaces: 2 })
```
### Environment Variables
```typescript
const apiKey = process.env.API_KEY || this.error('API_KEY required')
```
## Error Handling
### Try-Catch
```typescript
try {
await riskyOperation()
} catch (error) {
this.error(`Operation failed: ${error.message}`, { exit: 1 })
}
```
### Custom Error
```typescript
if (!valid) {
this.error('Invalid input', {
exit: 1,
suggestions: ['Try --help for usage']
})
}
```
## Async Patterns
### Concurrent Operations
```typescript
const results = await Promise.all([
operation1(),
operation2(),
operation3(),
])
```
### Sequential Operations
```typescript
for (const item of items) {
await processItem(item)
}
```
### With Timeout
```typescript
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 5000)
try {
const response = await fetch(url, { signal: controller.signal })
} finally {
clearTimeout(timeout)
}
```
## Best Practices
1. Always provide clear descriptions for flags and commands
2. Use char flags for common options (e.g., -v for verbose)
3. Validate inputs early in the run() method
4. Use ux.action.start/stop for long operations
5. Handle errors gracefully with helpful messages
6. Test both success and failure cases
7. Generate documentation with oclif manifest
8. Use TypeScript strict mode
9. Follow naming conventions (kebab-case for commands)
10. Keep commands focused and single-purpose