// minimal readline prompts — no external dependencies import { createInterface } from 'readline' import { accent, bold, muted } from './ui' const rl = () => createInterface({ input: process.stdin, output: process.stdout, }) function ask(question: string): Promise { return new Promise((resolve) => { const r = rl() r.question(question, (answer) => { r.close() resolve(answer.trim()) }) }) } export async function confirm(message: string, defaultYes = true): Promise { const hint = defaultYes ? 'Y/n' : 'y/N' // non-interactive stdin (piped, /dev/null, closed) — readline's question // callback would never fire, leaving the await unsettled and the process // exiting 0 with a `Detected unsettled top-level await` warning. degrade // to the default instead so scripted invocations behave predictably. if (!process.stdin.isTTY) { console.log( ` ${message} (${hint}) ${defaultYes ? 'y' : 'n'} ${muted('(non-interactive)')}`, ) return defaultYes } const answer = await ask( ` ${accent('?')} ${bold(message)} ${muted(`(${hint})`)} ${accent('›')} `, ) if (answer === '') return defaultYes return answer.toLowerCase().startsWith('y') } export async function select(message: string, options: string[]): Promise { console.log(`\n ${accent('?')} ${bold(message)}\n`) for (let i = 0; i < options.length; i++) { console.log(` ${accent(String(i + 1))} ${options[i]}`) } console.log() // non-interactive stdin — pick the first option rather than hanging. if (!process.stdin.isTTY) { console.log(` Choose (1-${options.length}): 1 ${muted('(non-interactive)')}`) return 0 } const answer = await ask(` ${muted(`Choose 1-${options.length}`)} ${accent('›')} `) const num = parseInt(answer, 10) if (num >= 1 && num <= options.length) return num - 1 return 0 // default to first }