// CLI prompt utilities import { createInterface } from 'readline'; export interface PromptOptions { message: string; defaultValue?: string; validate?: (input: string) => boolean | string; transform?: (input: string) => string; } export interface ConfirmOptions { message: string; defaultValue?: boolean; } export interface SelectOptions { message: string; choices: Array<{ value: T; label: string; description?: string }>; defaultValue?: T; } export function prompt(options: PromptOptions): Promise { return new Promise((resolve, reject) => { const rl = createInterface({ input: process.stdin, output: process.stdout }); const defaultText = options.defaultValue ? ` (${options.defaultValue})` : ''; const question = `${options.message}${defaultText}: `; rl.question(question, (input) => { rl.close(); const value = input.trim() || options.defaultValue || ''; if (options.validate) { const validation = options.validate(value); if (validation !== true) { const errorMessage = typeof validation === 'string' ? validation : 'Invalid input'; reject(new Error(errorMessage)); return; } } const result = options.transform ? options.transform(value) : value; resolve(result); }); }); } export function confirm(options: ConfirmOptions): Promise { return new Promise((resolve) => { const rl = createInterface({ input: process.stdin, output: process.stdout }); const defaultText = options.defaultValue !== undefined ? ` (${options.defaultValue ? 'Y/n' : 'y/N'})` : ' (y/n)'; const question = `${options.message}${defaultText}: `; rl.question(question, (input) => { rl.close(); const trimmed = input.trim().toLowerCase(); if (trimmed === '') { resolve(options.defaultValue ?? false); } else if (trimmed === 'y' || trimmed === 'yes') { resolve(true); } else if (trimmed === 'n' || trimmed === 'no') { resolve(false); } else { // Invalid input, use default or false resolve(options.defaultValue ?? false); } }); }); } export function select(options: SelectOptions): Promise { return new Promise((resolve, reject) => { console.log(`\n${options.message}`); options.choices.forEach((choice, index) => { const prefix = index + 1; const isDefault = choice.value === options.defaultValue ? ' (default)' : ''; const description = choice.description ? ` - ${choice.description}` : ''; console.log(` ${prefix}. ${choice.label}${description}${isDefault}`); }); const rl = createInterface({ input: process.stdin, output: process.stdout }); rl.question('\nSelect an option: ', (input) => { rl.close(); const trimmed = input.trim(); if (trimmed === '' && options.defaultValue !== undefined) { resolve(options.defaultValue); return; } const index = parseInt(trimmed) - 1; if (isNaN(index) || index < 0 || index >= options.choices.length) { reject(new Error('Invalid selection')); return; } const choice = options.choices[index]; resolve(choice ? choice.value : options.choices[0]!.value); }); }); } export async function multiSelect(options: SelectOptions): Promise { console.log(`\n${options.message}`); console.log('(Use comma-separated numbers, e.g., 1,3,5)'); options.choices.forEach((choice, index) => { const prefix = index + 1; const description = choice.description ? ` - ${choice.description}` : ''; console.log(` ${prefix}. ${choice.label}${description}`); }); const rl = createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve, reject) => { rl.question('\nSelect options (comma-separated): ', (input) => { rl.close(); const trimmed = input.trim(); if (trimmed === '') { resolve([]); return; } try { const indices = trimmed .split(',') .map(s => parseInt(s.trim()) - 1) .filter(i => !isNaN(i) && i >= 0 && i < options.choices.length); const selected = indices.map(i => options.choices[i]!.value); resolve(selected); } catch (error) { reject(new Error('Invalid selection format')); } }); }); }