fix(wizard): avoid rerunning completed setup steps (#692)
This commit was merged in pull request #692.
This commit is contained in:
@@ -167,6 +167,45 @@ describe('gatewayConfigStage', () => {
|
||||
expect(state.gateway?.regeneratedConfig).toBe(true);
|
||||
});
|
||||
|
||||
it('does not ask for a gateway API key when provider setup was completed with no key', async () => {
|
||||
delete process.env['MOSAIC_ASSUME_YES'];
|
||||
const originalIsTTY = process.stdin.isTTY;
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
|
||||
|
||||
try {
|
||||
const textFn = vi.fn(async (opts: { message: string; initialValue?: string }) => {
|
||||
if (opts.message === 'Gateway port') return opts.initialValue ?? '14242';
|
||||
if (opts.message === 'Web UI hostname (for browser access)') return 'localhost';
|
||||
if (opts.message.includes('API_KEY')) {
|
||||
throw new Error('gateway API key prompt should be skipped');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const p = buildPrompter({ text: textFn, select: vi.fn().mockResolvedValue('local') });
|
||||
const state = makeState('/home/user/.config/mosaic');
|
||||
|
||||
const result = await gatewayConfigStage(p, state, {
|
||||
host: 'localhost',
|
||||
defaultPort: 14242,
|
||||
skipInstall: true,
|
||||
providerType: 'none',
|
||||
});
|
||||
|
||||
expect(result.ready).toBe(true);
|
||||
expect(textFn).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: expect.stringContaining('API_KEY') }),
|
||||
);
|
||||
const envContents = readFileSync(daemonState.envFile, 'utf-8');
|
||||
expect(envContents).not.toContain('ANTHROPIC_API_KEY=');
|
||||
expect(envContents).not.toContain('OPENAI_API_KEY=');
|
||||
} finally {
|
||||
Object.defineProperty(process.stdin, 'isTTY', {
|
||||
value: originalIsTTY,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('short-circuits when gateway is already fully installed and user declines rerun', async () => {
|
||||
// Pre-populate both files + running daemon + meta with token
|
||||
const fs = require('node:fs');
|
||||
|
||||
@@ -506,6 +506,9 @@ async function collectAndWriteConfig(
|
||||
if (opts.providerKey) {
|
||||
anthropicKey = opts.providerKey;
|
||||
p.log(`Using API key from provider setup (${opts.providerType ?? 'unknown'}).`);
|
||||
} else if (opts.providerType === 'none') {
|
||||
anthropicKey = '';
|
||||
p.log('No API key provided during provider setup; skipping gateway API key prompt.');
|
||||
} else {
|
||||
anthropicKey = await p.text({
|
||||
message: 'ANTHROPIC_API_KEY (optional, press Enter to skip)',
|
||||
|
||||
@@ -37,6 +37,7 @@ export async function quickStartPath(
|
||||
|
||||
// 1. Provider setup (first question)
|
||||
await providerSetupStage(prompter, state);
|
||||
state.completedSections?.add('providers');
|
||||
|
||||
// Apply sensible defaults for everything else
|
||||
state.soul.agentName ??= 'Mosaic';
|
||||
@@ -57,6 +58,7 @@ export async function quickStartPath(
|
||||
|
||||
// Skills (recommended set, no user input in quick mode)
|
||||
await skillsSelectStage(prompter, state);
|
||||
state.completedSections?.add('skills');
|
||||
|
||||
// Finalize writes configs/assets/skills, but defer the success summary until
|
||||
// after the gateway health/bootstrap gates complete.
|
||||
@@ -75,7 +77,7 @@ export async function quickStartPath(
|
||||
portOverride: options.gatewayPortOverride,
|
||||
skipInstall: options.skipGatewayNpmInstall,
|
||||
providerKey: state.providerKey,
|
||||
providerType: state.providerType ?? 'none',
|
||||
providerType: state.providerType,
|
||||
});
|
||||
|
||||
if (!configResult.ready || !configResult.host || !configResult.port) {
|
||||
|
||||
@@ -126,6 +126,11 @@ type MenuChoice =
|
||||
| 'advanced'
|
||||
| 'finish';
|
||||
|
||||
function menuSectionKey(section: MenuChoice): MenuSection | null {
|
||||
if (section === 'quick-start' || section === 'finish') return null;
|
||||
return section === 'gateway-config' ? 'gateway' : section;
|
||||
}
|
||||
|
||||
function menuLabel(section: MenuChoice, completed: Set<MenuSection>): string {
|
||||
const labels: Record<MenuChoice, string> = {
|
||||
'quick-start': 'Quick Start',
|
||||
@@ -137,14 +142,24 @@ function menuLabel(section: MenuChoice, completed: Set<MenuSection>): string {
|
||||
finish: 'Finish & Apply',
|
||||
};
|
||||
const base = labels[section];
|
||||
const sectionKey: MenuSection =
|
||||
section === 'gateway-config' ? 'gateway' : (section as MenuSection);
|
||||
if (completed.has(sectionKey)) {
|
||||
const sectionKey = menuSectionKey(section);
|
||||
if (sectionKey && completed.has(sectionKey)) {
|
||||
return `${base} [done]`;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function skipCompletedMenuChoice(
|
||||
prompter: WizardPrompter,
|
||||
completed: Set<MenuSection>,
|
||||
choice: MenuChoice,
|
||||
): boolean {
|
||||
const sectionKey = menuSectionKey(choice);
|
||||
if (!sectionKey || !completed.has(sectionKey)) return false;
|
||||
prompter.log(`${menuLabel(choice, completed)} is already complete; skipping.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function runMenuLoop(
|
||||
prompter: WizardPrompter,
|
||||
state: WizardState,
|
||||
@@ -201,21 +216,25 @@ async function runMenuLoop(
|
||||
return; // Quick start is a complete flow — exit menu
|
||||
|
||||
case 'providers':
|
||||
if (skipCompletedMenuChoice(prompter, completed, choice)) break;
|
||||
await providerSetupStage(prompter, state);
|
||||
completed.add('providers');
|
||||
break;
|
||||
|
||||
case 'identity':
|
||||
if (skipCompletedMenuChoice(prompter, completed, choice)) break;
|
||||
await agentIntentStage(prompter, state);
|
||||
completed.add('identity');
|
||||
break;
|
||||
|
||||
case 'skills':
|
||||
if (skipCompletedMenuChoice(prompter, completed, choice)) break;
|
||||
await skillsSelectStage(prompter, state);
|
||||
completed.add('skills');
|
||||
break;
|
||||
|
||||
case 'gateway-config':
|
||||
if (skipCompletedMenuChoice(prompter, completed, choice)) break;
|
||||
// Gateway config is handled during Finish — mark as "configured"
|
||||
// after user reviews settings.
|
||||
await runGatewaySubMenu(prompter, state, options);
|
||||
@@ -223,6 +242,7 @@ async function runMenuLoop(
|
||||
break;
|
||||
|
||||
case 'advanced':
|
||||
if (skipCompletedMenuChoice(prompter, completed, choice)) break;
|
||||
await runAdvancedSubMenu(prompter, state);
|
||||
completed.add('advanced');
|
||||
break;
|
||||
@@ -325,7 +345,7 @@ async function runFinishPath(
|
||||
portOverride: options.gatewayPortOverride,
|
||||
skipInstall: options.skipGatewayNpmInstall,
|
||||
providerKey: state.providerKey,
|
||||
providerType: state.providerType ?? 'none',
|
||||
providerType: state.providerType,
|
||||
});
|
||||
|
||||
if (configResult.ready && configResult.host && configResult.port) {
|
||||
@@ -396,7 +416,7 @@ async function runHeadlessPath(
|
||||
portOverride: options.gatewayPortOverride,
|
||||
skipInstall: options.skipGatewayNpmInstall,
|
||||
providerKey: state.providerKey,
|
||||
providerType: state.providerType ?? 'none',
|
||||
providerType: state.providerType,
|
||||
});
|
||||
|
||||
if (!configResult.ready || !configResult.host || !configResult.port) {
|
||||
|
||||
Reference in New Issue
Block a user