Files
stack/packages/cli/src/commands/select-dialog.ts
Jason Woltje 4da255bf04
Some checks failed
ci/woodpecker/push/ci Pipeline failed
feat(cli): command architecture — agents, missions, gateway-aware prdy (#158)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-03-15 23:10:23 +00:00

59 lines
1.5 KiB
TypeScript

/**
* Interactive item selection. Uses @clack/prompts when TTY, falls back to numbered list.
*/
export async function selectItem<T>(
items: T[],
opts: {
message: string;
render: (item: T) => string;
emptyMessage?: string;
},
): Promise<T | undefined> {
if (items.length === 0) {
console.log(opts.emptyMessage ?? 'No items found.');
return undefined;
}
const isTTY = process.stdin.isTTY;
if (isTTY) {
try {
const { select } = await import('@clack/prompts');
const result = await select({
message: opts.message,
options: items.map((item, i) => ({
value: i,
label: opts.render(item),
})),
});
if (typeof result === 'symbol') {
return undefined;
}
return items[result as number];
} catch {
// Fall through to non-interactive
}
}
// Non-interactive: display numbered list and read a number
console.log(`\n${opts.message}\n`);
for (let i = 0; i < items.length; i++) {
console.log(` ${i + 1}. ${opts.render(items[i]!)}`);
}
const readline = await import('node:readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const answer = await new Promise<string>((resolve) => rl.question('\nSelect: ', resolve));
rl.close();
const index = parseInt(answer, 10) - 1;
if (isNaN(index) || index < 0 || index >= items.length) {
console.error('Invalid selection.');
return undefined;
}
return items[index];
}