Files
stack/packages/mosaic/src/index.ts
Jason Woltje 481b351ec8
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
feat(cli): add prdy, quality-rails, and wizard subcommands
Wires @mosaic/prdy, @mosaic/quality-rails, and @mosaic/mosaic into the
unified CLI binary. The wizard subcommand uses dynamic import to avoid
loading heavy wizard deps for other commands. Adds @types/node to cli
devDependencies and guards @mosaic/mosaic index.ts program.parse() so
the module is safely importable without auto-executing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 20:04:57 -05:00

85 lines
3.2 KiB
JavaScript

#!/usr/bin/env node
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Command } from 'commander';
import { ClackPrompter } from './prompter/clack-prompter.js';
import { HeadlessPrompter } from './prompter/headless-prompter.js';
import { createConfigService } from './config/config-service.js';
import { runWizard } from './wizard.js';
import { WizardCancelledError } from './errors.js';
import { VERSION, DEFAULT_MOSAIC_HOME } from './constants.js';
import type { CommunicationStyle } from './types.js';
export { VERSION, DEFAULT_MOSAIC_HOME };
export { runWizard } from './wizard.js';
export { ClackPrompter } from './prompter/clack-prompter.js';
export { HeadlessPrompter } from './prompter/headless-prompter.js';
export { createConfigService } from './config/config-service.js';
export { WizardCancelledError } from './errors.js';
const program = new Command()
.name('mosaic-wizard')
.description('Mosaic Installation Wizard')
.version(VERSION);
program
.option('--non-interactive', 'Run without prompts (uses defaults + flags)')
.option('--source-dir <path>', 'Source directory for framework files')
.option('--mosaic-home <path>', 'Target config directory', DEFAULT_MOSAIC_HOME)
// SOUL.md overrides
.option('--name <name>', 'Agent name')
.option('--role <description>', 'Agent role description')
.option('--style <style>', 'Communication style: direct|friendly|formal')
.option('--accessibility <prefs>', 'Accessibility preferences')
.option('--guardrails <rules>', 'Custom guardrails')
// USER.md overrides
.option('--user-name <name>', 'Your name')
.option('--pronouns <pronouns>', 'Your pronouns')
.option('--timezone <tz>', 'Your timezone')
.action(async (opts: Record<string, string | boolean | undefined>) => {
try {
const mosaicHome = (opts['mosaicHome'] as string) ?? DEFAULT_MOSAIC_HOME;
const sourceDir = (opts['sourceDir'] as string | undefined) ?? mosaicHome;
const prompter = opts['nonInteractive'] ? new HeadlessPrompter() : new ClackPrompter();
const configService = createConfigService(mosaicHome, sourceDir);
const style = opts['style'] as CommunicationStyle | undefined;
await runWizard({
mosaicHome,
sourceDir,
prompter,
configService,
cliOverrides: {
soul: {
agentName: opts['name'] as string | undefined,
roleDescription: opts['role'] as string | undefined,
communicationStyle: style,
accessibility: opts['accessibility'] as string | undefined,
customGuardrails: opts['guardrails'] as string | undefined,
},
user: {
userName: opts['userName'] as string | undefined,
pronouns: opts['pronouns'] as string | undefined,
timezone: opts['timezone'] as string | undefined,
},
},
});
} catch (err) {
if (err instanceof WizardCancelledError) {
console.log('\nWizard cancelled.');
process.exit(0);
}
console.error('Wizard failed:', err);
process.exit(1);
}
});
const entryPath = process.argv[1] ? resolve(process.argv[1]) : '';
if (entryPath.length > 0 && entryPath === fileURLToPath(import.meta.url)) {
program.parse();
}