import { confirm as clackConfirm, intro as clackIntro, isCancel, multiselect as clackMultiselect, outro as clackOutro, select as clackSelect, text as clackText, type Option, } from "@clack/prompts"; export class SetupCancelledError extends Error { constructor(message = "setup cancelled") { super(message); this.name = "SetupCancelledError"; } } export type PromptSelectOption = { value: T; label: string; hint?: string; }; function ensureInteractiveTerminal(): void { if (!process.stdin.isTTY || !process.stdout.isTTY) { throw new Error("feynman setup requires an interactive terminal."); } } function guardCancelled(value: T | symbol): T { if (isCancel(value)) { throw new SetupCancelledError(); } return value; } export function isInteractiveTerminal(): boolean { return Boolean(process.stdin.isTTY && process.stdout.isTTY); } export async function promptIntro(title: string): Promise { ensureInteractiveTerminal(); clackIntro(title); } export async function promptOutro(message: string): Promise { ensureInteractiveTerminal(); clackOutro(message); } export async function promptText(question: string, defaultValue = "", placeholder?: string): Promise { ensureInteractiveTerminal(); const value = guardCancelled( await clackText({ message: question, initialValue: defaultValue || undefined, placeholder: placeholder ?? (defaultValue || undefined), }), ); const normalized = String(value ?? "").trim(); return normalized || defaultValue; } export async function promptSelect( question: string, options: PromptSelectOption[], initialValue?: T, ): Promise { ensureInteractiveTerminal(); const selection = guardCancelled( await clackSelect({ message: question, options: options.map((option) => ({ value: option.value, label: option.label, hint: option.hint, })) as Option[], initialValue, }), ); return selection; } export async function promptChoice(question: string, choices: string[], defaultIndex = 0): Promise { const options = choices.map((choice, index) => ({ value: index, label: choice, })); return promptSelect(question, options, Math.max(0, Math.min(defaultIndex, choices.length - 1))); } export async function promptConfirm(question: string, initialValue = true): Promise { ensureInteractiveTerminal(); return guardCancelled( await clackConfirm({ message: question, initialValue, }), ); } export async function promptMultiSelect( question: string, options: PromptSelectOption[], initialValues: T[] = [], ): Promise { ensureInteractiveTerminal(); const selection = guardCancelled( await clackMultiselect({ message: question, options: options.map((option) => ({ value: option.value, label: option.label, hint: option.hint, })) as Option[], initialValues, required: false, }), ); return selection; }