Co-authored-by: jason.woltje <jason@diversecanvas.com> Co-committed-by: jason.woltje <jason@diversecanvas.com>
621 lines
25 KiB
JavaScript
621 lines
25 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { createRequire } from 'module';
|
|
import { Command } from 'commander';
|
|
import { registerBrainCommand } from '@mosaicstack/brain';
|
|
import { registerForgeCommand } from '@mosaicstack/forge';
|
|
import { registerLogCommand } from '@mosaicstack/log';
|
|
import { registerMacpCommand } from '@mosaicstack/macp';
|
|
import { registerMemoryCommand } from '@mosaicstack/memory';
|
|
import { registerQualityRails } from '@mosaicstack/quality-rails';
|
|
import { registerQueueCommand } from '@mosaicstack/queue';
|
|
import { registerStorageCommand } from '@mosaicstack/storage';
|
|
import { registerTelemetryCommand } from './commands/telemetry.js';
|
|
import { registerAgentCommand } from './commands/agent.js';
|
|
import { registerInteractionCommand } from './commands/interaction.js';
|
|
import { registerConfigCommand } from './commands/config.js';
|
|
import { registerFleetCommand } from './commands/fleet.js';
|
|
import { registerMissionCommand } from './commands/mission.js';
|
|
import { registerUninstallCommand } from './commands/uninstall.js';
|
|
import { registerRestoreCommand } from './commands/restore.js';
|
|
import { registerSkillCommand } from './commands/skill.js';
|
|
// prdy is registered via launch.ts
|
|
import { registerLaunchCommands } from './commands/launch.js';
|
|
import { registerLeaseCapabilityProbe } from './commands/lease-activation-probe.js';
|
|
import { registerInstallOrderingGuardCommand } from './commands/install-ordering-guard.js';
|
|
import { registerAuthCommand } from './commands/auth.js';
|
|
import { registerFederationCommand } from './commands/federation.js';
|
|
import { registerGatewayCommand } from './commands/gateway.js';
|
|
import {
|
|
backgroundUpdateCheck,
|
|
checkForAllUpdates,
|
|
formatAllPackagesTable,
|
|
getInstallAllCommand,
|
|
repairFleetCommsTools,
|
|
runUpdateReseedFlow,
|
|
checkFrameworkDrift,
|
|
FRAMEWORK_RESEED_PACKAGE,
|
|
} from './runtime/update-checker.js';
|
|
import { runWizard } from './wizard.js';
|
|
import { ClackPrompter } from './prompter/clack-prompter.js';
|
|
import { HeadlessPrompter } from './prompter/headless-prompter.js';
|
|
import { createConfigService } from './config/config-service.js';
|
|
import { WizardCancelledError } from './errors.js';
|
|
import { DEFAULT_MOSAIC_HOME } from './constants.js';
|
|
|
|
const _require = createRequire(import.meta.url);
|
|
const CLI_VERSION: string = (_require('../package.json') as { version: string }).version;
|
|
|
|
// Fire-and-forget update check at startup (non-blocking, cached 1h)
|
|
try {
|
|
backgroundUpdateCheck();
|
|
} catch {
|
|
// Silently ignore — update check is best-effort
|
|
}
|
|
|
|
const program = new Command();
|
|
|
|
program
|
|
.name('mosaic')
|
|
.description('Mosaic Stack CLI')
|
|
.version(CLI_VERSION)
|
|
.configureHelp({ sortSubcommands: true })
|
|
.addHelpText(
|
|
'after',
|
|
`
|
|
Command Groups:
|
|
|
|
Runtime: tui, login, sessions
|
|
Gateway: gateway
|
|
Framework: agent, bootstrap, coord, doctor, fleet, init, launch, mission, prdy, seq, skill, sync, upgrade, wizard, yolo
|
|
Platform: update
|
|
Runtimes: claude, codex, opencode, pi
|
|
`,
|
|
);
|
|
|
|
// ─── runtime launchers + framework commands ────────────────────────────
|
|
|
|
registerLaunchCommands(program);
|
|
|
|
// ─── lease activation capability probe (hidden; #869 Point-1 C1) ────────
|
|
|
|
registerLeaseCapabilityProbe(program);
|
|
|
|
// ─── install-ordering guard (hidden; #869 Point-1 C2) ───────────────────
|
|
|
|
registerInstallOrderingGuardCommand(program);
|
|
|
|
// ─── login ──────────────────────────────────────────────────────────────
|
|
|
|
program
|
|
.command('login')
|
|
.description('Sign in to a Mosaic gateway')
|
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:14242')
|
|
.option('-e, --email <email>', 'Email address')
|
|
.option('-p, --password <password>', 'Password')
|
|
.action(async (opts: { gateway: string; email?: string; password?: string }) => {
|
|
const { signIn, saveSession } = await import('./auth.js');
|
|
|
|
let email = opts.email;
|
|
let password = opts.password;
|
|
|
|
if (!email || !password) {
|
|
const readline = await import('node:readline');
|
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
const ask = (q: string): Promise<string> => new Promise((resolve) => rl.question(q, resolve));
|
|
|
|
if (!email) email = await ask('Email: ');
|
|
if (!password) password = await ask('Password: ');
|
|
rl.close();
|
|
}
|
|
|
|
try {
|
|
const auth = await signIn(opts.gateway, email, password);
|
|
saveSession(opts.gateway, auth);
|
|
console.log(`Signed in as ${auth.email} (${opts.gateway})`);
|
|
} catch (err) {
|
|
console.error(err instanceof Error ? err.message : String(err));
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
// ─── tui ────────────────────────────────────────────────────────────────
|
|
|
|
program
|
|
.command('tui')
|
|
.description('Launch interactive TUI connected to the gateway')
|
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:14242')
|
|
.option('-c, --conversation <id>', 'Resume a conversation by ID')
|
|
.option('-m, --model <modelId>', 'Model ID to use (e.g. gpt-4o, llama3.2)')
|
|
.option('-p, --provider <provider>', 'Provider to use (e.g. openai, ollama)')
|
|
.option('--agent <idOrName>', 'Connect to a specific agent')
|
|
.option('--project <idOrName>', 'Scope session to project')
|
|
.action(
|
|
async (opts: {
|
|
gateway: string;
|
|
conversation?: string;
|
|
model?: string;
|
|
provider?: string;
|
|
agent?: string;
|
|
project?: string;
|
|
}) => {
|
|
const { loadSession, validateSession, signIn, saveSession } = await import('./auth.js');
|
|
|
|
// Try loading saved session
|
|
let session = loadSession(opts.gateway);
|
|
|
|
if (session) {
|
|
const valid = await validateSession(opts.gateway, session.cookie);
|
|
if (!valid) {
|
|
console.log('Session expired. Please sign in again.');
|
|
session = null;
|
|
}
|
|
}
|
|
|
|
// No valid session — prompt for credentials
|
|
if (!session) {
|
|
const { promptLine, promptSecret } = await import('./commands/gateway/login.js');
|
|
|
|
console.log(`Sign in to ${opts.gateway}`);
|
|
const email = await promptLine('Email: ');
|
|
const password = await promptSecret('Password: ');
|
|
|
|
try {
|
|
const auth = await signIn(opts.gateway, email, password);
|
|
saveSession(opts.gateway, auth);
|
|
session = auth;
|
|
console.log(`Signed in as ${auth.email}\n`);
|
|
} catch (err) {
|
|
console.error(err instanceof Error ? err.message : String(err));
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Resolve agent ID if --agent was passed by name
|
|
let agentId: string | undefined;
|
|
let agentName: string | undefined;
|
|
if (opts.agent) {
|
|
try {
|
|
const { fetchAgentConfigs } = await import('./tui/gateway-api.js');
|
|
const agents = await fetchAgentConfigs(opts.gateway, session.cookie);
|
|
const match = agents.find((a) => a.id === opts.agent || a.name === opts.agent);
|
|
if (match) {
|
|
agentId = match.id;
|
|
agentName = match.name;
|
|
} else {
|
|
console.error(`Agent "${opts.agent}" not found.`);
|
|
process.exit(1);
|
|
}
|
|
} catch (err) {
|
|
console.error(
|
|
`Failed to resolve agent: ${err instanceof Error ? err.message : String(err)}`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Resolve project ID if --project was passed by name
|
|
let projectId: string | undefined;
|
|
if (opts.project) {
|
|
try {
|
|
const { fetchProjects } = await import('./tui/gateway-api.js');
|
|
const projects = await fetchProjects(opts.gateway, session.cookie);
|
|
const match = projects.find((p) => p.id === opts.project || p.name === opts.project);
|
|
if (match) {
|
|
projectId = match.id;
|
|
} else {
|
|
console.error(`Project "${opts.project}" not found.`);
|
|
process.exit(1);
|
|
}
|
|
} catch (err) {
|
|
console.error(
|
|
`Failed to resolve project: ${err instanceof Error ? err.message : String(err)}`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Auto-create a conversation if none was specified
|
|
let conversationId = opts.conversation;
|
|
if (!conversationId) {
|
|
try {
|
|
const { createConversation } = await import('./tui/gateway-api.js');
|
|
const conv = await createConversation(opts.gateway, session.cookie, {
|
|
...(projectId ? { projectId } : {}),
|
|
});
|
|
conversationId = conv.id;
|
|
} catch (err) {
|
|
console.error(
|
|
`Failed to create conversation: ${err instanceof Error ? err.message : String(err)}`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Dynamic import to avoid loading React/Ink for other commands
|
|
const { render } = await import('ink');
|
|
const React = await import('react');
|
|
const { TuiApp } = await import('./tui/app.js');
|
|
|
|
render(
|
|
React.createElement(TuiApp, {
|
|
gatewayUrl: opts.gateway,
|
|
conversationId,
|
|
sessionCookie: session.cookie,
|
|
initialModel: opts.model,
|
|
initialProvider: opts.provider,
|
|
agentId,
|
|
agentName: agentName ?? undefined,
|
|
projectId,
|
|
version: CLI_VERSION,
|
|
}),
|
|
{ exitOnCtrlC: false },
|
|
);
|
|
},
|
|
);
|
|
|
|
// ─── sessions ───────────────────────────────────────────────────────────
|
|
|
|
const sessionsCmd = program
|
|
.command('sessions')
|
|
.description('Manage active agent sessions')
|
|
.configureHelp({ sortSubcommands: true });
|
|
|
|
sessionsCmd
|
|
.command('list')
|
|
.description('List active agent sessions')
|
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:14242')
|
|
.action(async (opts: { gateway: string }) => {
|
|
const { withAuth } = await import('./commands/with-auth.js');
|
|
const auth = await withAuth(opts.gateway);
|
|
const { fetchSessions } = await import('./tui/gateway-api.js');
|
|
|
|
try {
|
|
const result = await fetchSessions(auth.gateway, auth.cookie);
|
|
if (result.total === 0) {
|
|
console.log('No active sessions.');
|
|
return;
|
|
}
|
|
console.log(`Active sessions (${result.total}):\n`);
|
|
for (const s of result.sessions) {
|
|
const created = new Date(s.createdAt).toLocaleString();
|
|
const durationSec = Math.round(s.durationMs / 1000);
|
|
console.log(` ID: ${s.id}`);
|
|
console.log(` Model: ${s.provider}/${s.modelId}`);
|
|
console.log(` Created: ${created}`);
|
|
console.log(` Prompts: ${s.promptCount}`);
|
|
console.log(` Duration: ${durationSec}s`);
|
|
if (s.channels.length > 0) {
|
|
console.log(` Channels: ${s.channels.join(', ')}`);
|
|
}
|
|
console.log('');
|
|
}
|
|
} catch (err) {
|
|
console.error(err instanceof Error ? err.message : String(err));
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
sessionsCmd
|
|
.command('resume <id>')
|
|
.description('Resume an existing agent session in the TUI')
|
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:14242')
|
|
.action(async (id: string, opts: { gateway: string }) => {
|
|
const { loadSession, validateSession } = await import('./auth.js');
|
|
|
|
const session = loadSession(opts.gateway);
|
|
if (!session) {
|
|
console.error('Not signed in. Run `mosaic login` first.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const valid = await validateSession(opts.gateway, session.cookie);
|
|
if (!valid) {
|
|
console.error('Session expired. Run `mosaic login` again.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const { render } = await import('ink');
|
|
const React = await import('react');
|
|
const { TuiApp } = await import('./tui/app.js');
|
|
|
|
render(
|
|
React.createElement(TuiApp, {
|
|
gatewayUrl: opts.gateway,
|
|
conversationId: id,
|
|
sessionCookie: session.cookie,
|
|
version: CLI_VERSION,
|
|
}),
|
|
);
|
|
});
|
|
|
|
sessionsCmd
|
|
.command('destroy <id>')
|
|
.description('Terminate an active agent session')
|
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:14242')
|
|
.action(async (id: string, opts: { gateway: string }) => {
|
|
const { withAuth } = await import('./commands/with-auth.js');
|
|
const auth = await withAuth(opts.gateway);
|
|
const { deleteSession } = await import('./tui/gateway-api.js');
|
|
|
|
try {
|
|
await deleteSession(auth.gateway, auth.cookie, id);
|
|
console.log(`Session ${id} destroyed.`);
|
|
} catch (err) {
|
|
console.error(err instanceof Error ? err.message : String(err));
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
// ─── auth ────────────────────────────────────────────────────────────────
|
|
|
|
registerAuthCommand(program);
|
|
|
|
// ─── gateway ──────────────────────────────────────────────────────────
|
|
|
|
registerGatewayCommand(program);
|
|
|
|
// ─── federation ───────────────────────────────────────────────────────
|
|
|
|
registerFederationCommand(program);
|
|
|
|
// ─── agent ─────────────────────────────────────────────────────────────
|
|
|
|
registerAgentCommand(program);
|
|
|
|
// ─── interaction ───────────────────────────────────────────────────────
|
|
|
|
registerInteractionCommand(program);
|
|
|
|
// ─── fleet ─────────────────────────────────────────────────────────────
|
|
|
|
registerFleetCommand(program);
|
|
|
|
// ─── config ────────────────────────────────────────────────────────────
|
|
|
|
registerConfigCommand(program);
|
|
|
|
// ─── mission ───────────────────────────────────────────────────────────
|
|
|
|
registerMissionCommand(program);
|
|
|
|
// ─── brain ──────────────────────────────────────────────────────────────
|
|
|
|
registerBrainCommand(program);
|
|
|
|
// ─── forge ───────────────────────────────────────────────────────────────
|
|
|
|
registerForgeCommand(program);
|
|
|
|
// ─── macp ────────────────────────────────────────────────────────────────
|
|
|
|
registerMacpCommand(program);
|
|
|
|
// ─── quality-rails ──────────────────────────────────────────────────────
|
|
|
|
registerQualityRails(program);
|
|
|
|
// ─── log ─────────────────────────────────────────────────────────────────
|
|
|
|
registerLogCommand(program);
|
|
|
|
// ─── memory ──────────────────────────────────────────────────────────────
|
|
|
|
registerMemoryCommand(program);
|
|
|
|
// ─── queue ───────────────────────────────────────────────────────────────
|
|
|
|
registerQueueCommand(program);
|
|
|
|
// ─── storage ─────────────────────────────────────────────────────────────
|
|
|
|
registerStorageCommand(program);
|
|
|
|
// ─── uninstall ───────────────────────────────────────────────────────────────
|
|
|
|
registerUninstallCommand(program);
|
|
|
|
// ─── restore ─────────────────────────────────────────────────────────────────
|
|
|
|
registerRestoreCommand(program);
|
|
|
|
// ─── skill ───────────────────────────────────────────────────────────────────
|
|
|
|
registerSkillCommand(program);
|
|
|
|
// ─── telemetry ───────────────────────────────────────────────────────────────
|
|
|
|
registerTelemetryCommand(program);
|
|
|
|
// ─── update ─────────────────────────────────────────────────────────────
|
|
|
|
program
|
|
.command('update')
|
|
.description('Check for and install Mosaic CLI updates')
|
|
.option('--check', 'Check only, do not install')
|
|
.option(
|
|
'--no-reseed',
|
|
'Skip re-seeding framework files into ~/.config/mosaic after the CLI update',
|
|
)
|
|
.option('--relaunch', 'Restart durable fleet agents so the new launcher/runtime takes effect')
|
|
.option(
|
|
'--repair-tools',
|
|
'Restore the supported current-version TOOLS contract and executable fleet helper',
|
|
)
|
|
.option(
|
|
'--allow-inactive-enforcement',
|
|
'Wire lease-enforcement hooks into settings.json even when activation cannot be confirmed ' +
|
|
'(explicit, loud, non-default opt-out for the post-reseed install-ordering guard — see #869/#882)',
|
|
)
|
|
.action(
|
|
async (opts: {
|
|
check?: boolean;
|
|
reseed?: boolean;
|
|
relaunch?: boolean;
|
|
repairTools?: boolean;
|
|
allowInactiveEnforcement?: boolean;
|
|
}) => {
|
|
if (opts.repairTools) {
|
|
const repair = repairFleetCommsTools();
|
|
if (!repair.ok) {
|
|
console.error(`Fleet communications tools repair failed: ${repair.reason ?? 'unknown'}`);
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
console.log(
|
|
repair.changed
|
|
? 'Fleet communications tools repaired from the supported current framework.'
|
|
: 'Fleet communications tools already match the supported current framework.',
|
|
);
|
|
if (repair.backupPath) console.log(`Preserved previous TOOLS.md at ${repair.backupPath}.`);
|
|
console.log('No active context or session was rewritten.');
|
|
return;
|
|
}
|
|
// checkForAllUpdates imported statically above
|
|
const { execSync } = await import('node:child_process');
|
|
|
|
// Re-seed the framework from the freshly-installed package, re-apply the
|
|
// install-ordering guard to settings.json (#882 (b) — closes the
|
|
// `--sync-only` bypass so `mosaic update` never leaves enforcement-hook
|
|
// wiring stale/unguarded), propagate shipped systemd unit fixes to the
|
|
// active units, and (opt-in) relaunch durable agents. Shared by the
|
|
// "packages updated" and the "framework drift" paths. Extracted to
|
|
// update-checker.ts (`runUpdateReseedFlow`) for direct unit testability.
|
|
const reseedFramework = (reason: string): void => {
|
|
const flow = runUpdateReseedFlow(reason, {
|
|
reseed: opts.reseed,
|
|
relaunch: opts.relaunch,
|
|
allowInactiveEnforcement: opts.allowInactiveEnforcement === true,
|
|
});
|
|
if (flow.settingsGuard?.ran && flow.settingsGuard.result?.exitCode === 1) {
|
|
// Fail-loud: enforcement hooks were refused/stripped. Surface this
|
|
// in the command's own exit status without aborting the rest of
|
|
// the update (mirrors mosaic-link-runtime-assets' guard_degraded).
|
|
process.exitCode = 1;
|
|
}
|
|
};
|
|
|
|
console.log('Checking for updates…');
|
|
const results = checkForAllUpdates({ skipCache: true });
|
|
|
|
console.log('');
|
|
console.log(formatAllPackagesTable(results));
|
|
|
|
const outdated = results.filter((r: { updateAvailable: boolean }) => r.updateAvailable);
|
|
if (outdated.length === 0) {
|
|
const anyInstalled = results.some((r: { current: string }) => r.current);
|
|
if (!anyInstalled) {
|
|
console.error('No @mosaicstack/* packages are installed.');
|
|
process.exit(1);
|
|
}
|
|
console.log('\n✔ All packages up to date.');
|
|
// #642: the CLI may have been upgraded outside `mosaic update` (e.g. a
|
|
// direct `npm i -g`), leaving the framework files stale even though no
|
|
// package is reported outdated. Detect that via the framework version and
|
|
// re-seed so shipped launcher/runtime fixes still activate.
|
|
const drift = checkFrameworkDrift();
|
|
if (drift.drifted) {
|
|
reseedFramework(
|
|
`\nFramework drift detected (on-disk v${drift.installed} < bundled v${drift.bundled}) — ` +
|
|
'the CLI was updated outside `mosaic update`. Re-seeding framework files into ' +
|
|
'~/.config/mosaic (data-safe; keeps your edits)…',
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (opts.check) {
|
|
process.exit(2); // Signal to callers that an update exists
|
|
}
|
|
|
|
console.log(`\nInstalling ${outdated.length} update(s)…`);
|
|
try {
|
|
// Relies on @mosaicstack:registry in ~/.npmrc
|
|
const cmd = getInstallAllCommand(outdated);
|
|
execSync(cmd, {
|
|
stdio: 'inherit',
|
|
timeout: 60_000,
|
|
});
|
|
console.log('\n✔ Updated successfully.');
|
|
} catch {
|
|
console.error('\nUpdate failed. Try manually: bash tools/install.sh');
|
|
process.exit(1);
|
|
}
|
|
|
|
// F3-m3 / R13: the CLI is updated, but the framework files in
|
|
// ~/.config/mosaic/ are still the previous version. Re-seed them from the
|
|
// freshly-installed package so shipped launcher/runtime changes ACTIVATE.
|
|
// Re-seed when the framework-bearing package itself updated OR the on-disk
|
|
// framework is older than the freshly-installed one (#642 — e.g. only
|
|
// sibling packages were outdated but the CLI was already ahead).
|
|
const mosaicUpdated = outdated.some(
|
|
(r: { package: string }) => r.package === FRAMEWORK_RESEED_PACKAGE,
|
|
);
|
|
const drift = checkFrameworkDrift();
|
|
if (mosaicUpdated || drift.drifted) {
|
|
reseedFramework(
|
|
'\nRe-seeding framework files into ~/.config/mosaic (data-safe; keeps your edits)…',
|
|
);
|
|
}
|
|
},
|
|
);
|
|
|
|
// ─── 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>) => {
|
|
// All wizard imports are now static (see top of file)
|
|
|
|
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();
|