feat(cli): add prdy, quality-rails, and wizard subcommands (#104)
Some checks failed
ci/woodpecker/push/ci Pipeline failed

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #104.
This commit is contained in:
2026-03-15 01:05:31 +00:00
committed by jason.woltje
parent c4e52085e3
commit 9f036242fa
4 changed files with 112 additions and 2 deletions

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env node
import { Command } from 'commander';
import { buildPrdyCli } from '@mosaic/prdy';
import { createQualityRailsCli } from '@mosaic/quality-rails';
const program = new Command();
@@ -25,4 +27,85 @@ program
);
});
// prdy subcommand
// buildPrdyCli() returns a wrapper Command; extract the 'prdy' subcommand from it.
// Type cast is required because @mosaic/prdy uses commander@12 while @mosaic/cli uses commander@13.
const prdyWrapper = buildPrdyCli();
const prdyCmd = prdyWrapper.commands.find((c) => c.name() === 'prdy');
if (prdyCmd !== undefined) {
program.addCommand(prdyCmd as unknown as Command);
}
// quality-rails subcommand
// createQualityRailsCli() returns a wrapper Command; extract the 'quality-rails' subcommand.
const qrWrapper = createQualityRailsCli();
const qrCmd = qrWrapper.commands.find((c) => c.name() === 'quality-rails');
if (qrCmd !== undefined) {
program.addCommand(qrCmd as unknown as Command);
}
// wizard subcommand — wraps @mosaic/mosaic installation wizard
program
.command('wizard')
.description('Run the Mosaic installation wizard')
.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')
.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')
.option('--user-name <name>', 'Your name')
.option('--pronouns <pronouns>', 'Your pronouns')
.option('--timezone <tz>', 'Your timezone')
.action(async (opts: Record<string, string | boolean | undefined>) => {
// Dynamic import to avoid loading wizard deps for other commands
const {
runWizard,
ClackPrompter,
HeadlessPrompter,
createConfigService,
WizardCancelledError,
DEFAULT_MOSAIC_HOME,
} = await import('@mosaic/mosaic');
try {
const mosaicHome = (opts['mosaicHome'] as string | undefined) ?? DEFAULT_MOSAIC_HOME;
const sourceDir = (opts['sourceDir'] as string | undefined) ?? mosaicHome;
const prompter = opts['nonInteractive'] ? new HeadlessPrompter() : new ClackPrompter();
const configService = createConfigService(mosaicHome, sourceDir);
await runWizard({
mosaicHome,
sourceDir,
prompter,
configService,
cliOverrides: {
soul: {
agentName: opts['name'] as string | undefined,
roleDescription: opts['role'] as string | undefined,
communicationStyle: opts['style'] as 'direct' | 'friendly' | 'formal' | undefined,
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);
}
});
program.parse();