Files
stack/packages/mosaic/src/commands/select-dialog.ts
Jarvis c6fc090c98
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
feat(mosaic): merge @mosaic/cli into @mosaic/mosaic
@mosaic/mosaic is now the single package providing both:
- 'mosaic' binary (CLI: yolo, coord, prdy, tui, gateway, etc.)
- 'mosaic-wizard' binary (installation wizard)

Changes:
- Move packages/cli/src/* into packages/mosaic/src/
- Convert dynamic @mosaic/mosaic imports to static relative imports
- Add CLI deps (ink, react, socket.io-client, @mosaic/config) to mosaic
- Add jsx: react-jsx to mosaic's tsconfig
- Exclude packages/cli from workspace (pnpm-workspace.yaml)
- Update install.sh to install @mosaic/mosaic instead of @mosaic/cli
- Bump version to 0.0.17

This eliminates the circular dependency between @mosaic/cli and
@mosaic/mosaic that was blocking the build graph.
2026-04-04 20:07:27 -05: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];
}