--- name: developer-tools description: CLI tools, SDKs, and developer experience patterns domain: domain-applications version: 1.0.0 tags: [cli, sdk, api-client, devtools, dx] triggers: keywords: primary: [cli, sdk, developer tool, api client, dx, developer experience] secondary: [commander, chalk, api documentation, developer portal, openapi] context_boost: [tool, library, package, npm, developer] context_penalty: [frontend, ui, design, mobile] priority: medium --- # Developer Tools ## Overview Building command-line interfaces, SDKs, and tools that enhance developer experience. --- ## CLI Development ### CLI Framework (Commander) ```typescript #!/usr/bin/env node import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import inquirer from 'inquirer'; const program = new Command(); program .name('myctl') .description('CLI tool for managing resources') .version('1.0.0'); // Simple command program .command('init') .description('Initialize a new project') .option('-t, --template ', 'Template to use', 'default') .option('-d, --directory ', 'Target directory', '.') .action(async (options) => { const spinner = ora('Initializing project...').start(); try { await initProject(options.template, options.directory); spinner.succeed(chalk.green('Project initialized successfully!')); } catch (error) { spinner.fail(chalk.red(`Failed: ${error.message}`)); process.exit(1); } }); // Interactive command program .command('create ') .description('Create a new resource') .action(async (name) => { const answers = await inquirer.prompt([ { type: 'list', name: 'type', message: 'Select resource type:', choices: ['api', 'worker', 'database'], }, { type: 'input', name: 'description', message: 'Description:', }, { type: 'confirm', name: 'public', message: 'Make it public?', default: false, }, ]); await createResource(name, answers); console.log(chalk.green(`Created ${answers.type}: ${name}`)); }); // Subcommands const configCmd = program.command('config').description('Manage configuration'); configCmd .command('set ') .description('Set a config value') .action(async (key, value) => { await setConfig(key, value); console.log(`Set ${key}=${value}`); }); configCmd .command('get ') .description('Get a config value') .action(async (key) => { const value = await getConfig(key); console.log(value); }); configCmd .command('list') .description('List all config values') .action(async () => { const config = await getAllConfig(); console.table(config); }); // Global options program .option('--debug', 'Enable debug mode') .option('--json', 'Output as JSON') .hook('preAction', (thisCommand) => { if (thisCommand.opts().debug) { process.env.DEBUG = 'true'; } }); program.parse(); ``` ### CLI Output Formatting ```typescript import Table from 'cli-table3'; import boxen from 'boxen'; // Table output function printTable(data: Array>, columns: string[]) { const table = new Table({ head: columns.map((c) => chalk.bold(c)), style: { head: ['cyan'] }, }); data.forEach((row) => { table.push(columns.map((col) => row[col] ?? '')); }); console.log(table.toString()); } // JSON output function printJson(data: any) { console.log(JSON.stringify(data, null, 2)); } // Box output function printBox(message: string, title?: string) { console.log( boxen(message, { padding: 1, margin: 1, borderStyle: 'round', title, titleAlignment: 'center', }) ); } // Progress bar import cliProgress from 'cli-progress'; async function withProgress( items: T[], fn: (item: T) => Promise, label: string ) { const bar = new cliProgress.SingleBar({ format: `${label} |{bar}| {percentage}% | {value}/{total}`, barCompleteChar: '\u2588', barIncompleteChar: '\u2591', }); bar.start(items.length, 0); for (const item of items) { await fn(item); bar.increment(); } bar.stop(); } ``` --- ## SDK Development ### TypeScript SDK ```typescript // sdk/index.ts export class MyServiceClient { private baseUrl: string; private apiKey: string; constructor(options: { apiKey: string; baseUrl?: string }) { this.apiKey = options.apiKey; this.baseUrl = options.baseUrl || 'https://api.example.com'; } private async request( method: string, path: string, data?: any ): Promise { const response = await fetch(`${this.baseUrl}${path}`, { method, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }, body: data ? JSON.stringify(data) : undefined, }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new ApiError(response.status, error.message || 'Request failed'); } return response.json(); } // Resource: Users users = { list: (params?: ListUsersParams) => this.request>('GET', '/users?' + qs(params)), get: (id: string) => this.request('GET', `/users/${id}`), create: (data: CreateUserInput) => this.request('POST', '/users', data), update: (id: string, data: UpdateUserInput) => this.request('PUT', `/users/${id}`, data), delete: (id: string) => this.request('DELETE', `/users/${id}`), }; // Resource: Projects projects = { list: () => this.request('GET', '/projects'), get: (id: string) => this.request('GET', `/projects/${id}`), create: (data: CreateProjectInput) => this.request('POST', '/projects', data), }; } // Error class export class ApiError extends Error { constructor( public status: number, message: string, public code?: string ) { super(message); this.name = 'ApiError'; } } // Types export interface User { id: string; email: string; name: string; createdAt: string; } export interface CreateUserInput { email: string; name: string; } export interface UpdateUserInput { name?: string; } export interface PaginatedResponse { data: T[]; meta: { page: number; limit: number; total: number; }; } // Usage const client = new MyServiceClient({ apiKey: 'sk_...' }); const users = await client.users.list({ page: 1, limit: 10 }); const user = await client.users.create({ email: 'test@example.com', name: 'Test' }); ``` ### SDK with Retry and Rate Limiting ```typescript import pRetry from 'p-retry'; import pThrottle from 'p-throttle'; class RobustClient { private throttle = pThrottle({ limit: 100, interval: 60000, // 100 requests per minute }); private async requestWithRetry( fn: () => Promise, options?: { retries?: number } ): Promise { return pRetry( async () => { try { return await fn(); } catch (error) { if (error instanceof ApiError) { // Don't retry client errors if (error.status >= 400 && error.status < 500) { throw new pRetry.AbortError(error); } } throw error; } }, { retries: options?.retries ?? 3, onFailedAttempt: (error) => { console.log( `Attempt ${error.attemptNumber} failed. ${error.retriesLeft} retries left.` ); }, } ); } private request = this.throttle(async ( method: string, path: string, data?: any ): Promise => { return this.requestWithRetry(async () => { const response = await fetch(`${this.baseUrl}${path}`, { method, headers: this.getHeaders(), body: data ? JSON.stringify(data) : undefined, }); // Handle rate limiting if (response.status === 429) { const retryAfter = response.headers.get('Retry-After'); const delay = retryAfter ? parseInt(retryAfter) * 1000 : 60000; await sleep(delay); throw new Error('Rate limited, retrying...'); } if (!response.ok) { throw new ApiError(response.status, await response.text()); } return response.json(); }); }); } ``` --- ## API Documentation ### OpenAPI Spec Generation ```typescript import { generateOpenApi } from '@ts-rest/open-api'; import { contract } from './contract'; const openApiDocument = generateOpenApi(contract, { info: { title: 'My API', version: '1.0.0', description: 'API for managing resources', }, servers: [ { url: 'https://api.example.com', description: 'Production' }, { url: 'https://staging-api.example.com', description: 'Staging' }, ], }); // Export for documentation tools export { openApiDocument }; ``` ### Interactive Documentation ```tsx // Using Swagger UI React import SwaggerUI from 'swagger-ui-react'; import 'swagger-ui-react/swagger-ui.css'; function ApiDocs() { return ( ); } // Or Redoc import { RedocStandalone } from 'redoc'; function ApiDocs() { return ; } ``` --- ## Developer Portal ```tsx // API key management component function ApiKeyManager() { const [keys, setKeys] = useState([]); async function createKey(name: string) { const key = await api.createApiKey({ name }); // Show the secret only once showModal({ title: 'API Key Created', content: (

Save this key - it won't be shown again:

{key.secret}
), }); setKeys([...keys, key]); } async function revokeKey(keyId: string) { await api.revokeApiKey(keyId); setKeys(keys.filter((k) => k.id !== keyId)); } return (

API Keys

{keys.map((key) => ( ))}
Name Created Last Used Actions
{key.name} {formatDate(key.createdAt)} {key.lastUsedAt ? formatDate(key.lastUsedAt) : 'Never'}
); } ``` --- ## Related Skills - [[api-design]] - API design patterns - [[documentation]] - Technical writing - [[automation-scripts]] - Build automation