diff --git a/packages/cli/package.json b/packages/cli/package.json index 3414ad6..9b75bba 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@clack/prompts": "^0.9.0", + "@mosaic/config": "workspace:^", "@mosaic/mosaic": "workspace:^", "@mosaic/prdy": "workspace:^", "@mosaic/quality-rails": "workspace:^", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 5639ed1..42a60e0 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -7,6 +7,7 @@ import { registerAgentCommand } from './commands/agent.js'; import { registerMissionCommand } from './commands/mission.js'; // prdy is registered via launch.ts import { registerLaunchCommands } from './commands/launch.js'; +import { registerGatewayCommand } from './commands/gateway.js'; const _require = createRequire(import.meta.url); const CLI_VERSION: string = (_require('../package.json') as { version: string }).version; @@ -290,6 +291,10 @@ sessionsCmd } }); +// ─── gateway ────────────────────────────────────────────────────────── + +registerGatewayCommand(program); + // ─── agent ───────────────────────────────────────────────────────────── registerAgentCommand(program); diff --git a/packages/cli/src/commands/gateway.ts b/packages/cli/src/commands/gateway.ts new file mode 100644 index 0000000..5d5a8e8 --- /dev/null +++ b/packages/cli/src/commands/gateway.ts @@ -0,0 +1,78 @@ +import { createInterface } from 'node:readline'; +import { writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import type { Command } from 'commander'; +import { + DEFAULT_LOCAL_CONFIG, + DEFAULT_TEAM_CONFIG, + type MosaicConfig, + type StorageTier, +} from '@mosaic/config'; + +function ask(rl: ReturnType, question: string): Promise { + return new Promise((res) => rl.question(question, res)); +} + +async function runInit(opts: { tier?: string; output: string }): Promise { + const outputPath = resolve(opts.output); + let tier: StorageTier; + + if (opts.tier) { + if (opts.tier !== 'local' && opts.tier !== 'team') { + console.error(`Invalid tier "${opts.tier}" — expected "local" or "team"`); + process.exit(1); + } + tier = opts.tier; + } else { + const rl = createInterface({ input: process.stdin, output: process.stdout }); + const answer = await ask(rl, 'Select tier (local/team) [local]: '); + rl.close(); + const trimmed = answer.trim().toLowerCase(); + tier = trimmed === 'team' ? 'team' : 'local'; + } + + let config: MosaicConfig; + + if (tier === 'local') { + config = DEFAULT_LOCAL_CONFIG; + } else { + const rl = createInterface({ input: process.stdin, output: process.stdout }); + const dbUrl = await ask( + rl, + 'DATABASE_URL [postgresql://mosaic:mosaic@localhost:5432/mosaic]: ', + ); + const valkeyUrl = await ask(rl, 'VALKEY_URL [redis://localhost:6379]: '); + rl.close(); + + config = { + ...DEFAULT_TEAM_CONFIG, + storage: { + type: 'postgres', + url: dbUrl.trim() || 'postgresql://mosaic:mosaic@localhost:5432/mosaic', + }, + queue: { + type: 'bullmq', + url: valkeyUrl.trim() || 'redis://localhost:6379', + }, + }; + } + + writeFileSync(outputPath, JSON.stringify(config, null, 2) + '\n'); + console.log(`\nWrote ${outputPath}`); + console.log('\nNext steps:'); + console.log(' 1. Review the generated config'); + console.log(' 2. Run: pnpm --filter @mosaic/gateway exec tsx src/main.ts'); +} + +export function registerGatewayCommand(program: Command): void { + const gateway = program.command('gateway').description('Gateway management commands'); + + gateway + .command('init') + .description('Generate a mosaic.config.json for the gateway') + .option('--tier ', 'Storage tier: local or team (skips interactive prompt)') + .option('--output ', 'Output file path', './mosaic.config.json') + .action(async (opts: { tier?: string; output: string }) => { + await runInit(opts); + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e1726f..8565111 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -307,6 +307,9 @@ importers: '@clack/prompts': specifier: ^0.9.0 version: 0.9.1 + '@mosaic/config': + specifier: workspace:^ + version: link:../config '@mosaic/mosaic': specifier: workspace:^ version: link:../mosaic