Skip to content

Commit ae25113

Browse files
author
Test User
committed
fix(init): allow guided provider override
1 parent a147c77 commit ae25113

2 files changed

Lines changed: 155 additions & 0 deletions

File tree

packages/cli/src/__tests__/commands/init.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import os from 'os';
66
import { fileURLToPath } from 'url';
77
import { getDefaultConfig } from '@night-watch/core';
88
import {
9+
buildProviderChoices,
910
buildProviderSummary,
1011
buildInitConfig,
1112
chooseProviderByPrecedence,
@@ -14,6 +15,8 @@ import {
1415
getGitHubRemoteStatus,
1516
isInteractiveInitSession,
1617
resolveTemplatePath,
18+
selectProviderOverrideByIndex,
19+
shouldPromptProviderOverride,
1720
} from '../../commands/init.js';
1821

1922
// Get project root directory (4 levels up from this test file)
@@ -604,6 +607,38 @@ describe('init command', () => {
604607
expect(summary).toContain('Auto-selected Codex');
605608
expect(summary).toContain('Also available: Claude');
606609
});
610+
611+
it('should only prompt for provider override in interactive sessions with multiple presets', () => {
612+
expect(shouldPromptProviderOverride(true, ['codex', 'claude'])).toBe(true);
613+
expect(shouldPromptProviderOverride(true, ['codex'])).toBe(false);
614+
expect(shouldPromptProviderOverride(false, ['codex', 'claude'])).toBe(false);
615+
});
616+
617+
it('should build detected provider choices with a custom provider command option', () => {
618+
const choices = buildProviderChoices(['codex', 'claude']);
619+
620+
expect(choices).toEqual([
621+
{ label: 'Codex', provider: 'codex', custom: false },
622+
{ label: 'Claude', provider: 'claude', custom: false },
623+
{ label: 'Custom provider command', custom: true },
624+
]);
625+
});
626+
627+
it('should select provider override choices by one-based index', () => {
628+
const choices = buildProviderChoices(['codex', 'claude']);
629+
630+
expect(selectProviderOverrideByIndex(choices, '2')).toEqual({
631+
label: 'Claude',
632+
provider: 'claude',
633+
custom: false,
634+
});
635+
expect(selectProviderOverrideByIndex(choices, '3')).toEqual({
636+
label: 'Custom provider command',
637+
custom: true,
638+
});
639+
expect(selectProviderOverrideByIndex(choices, '0')).toBeNull();
640+
expect(selectProviderOverrideByIndex(choices, 'nope')).toBeNull();
641+
});
607642
});
608643

609644
describe('getGitHubRemoteStatus', () => {

packages/cli/src/commands/init.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ interface IProviderSelectionResult {
102102
summary: string;
103103
}
104104

105+
export interface IProviderChoice {
106+
label: string;
107+
provider?: Provider;
108+
custom: boolean;
109+
}
110+
105111
interface IInitProjectReview {
106112
cwd: string;
107113
projectName: string;
@@ -270,6 +276,45 @@ export function buildProviderSummary(provider: Provider, detectedProviders: Prov
270276
return `Auto-selected ${providerName}. Also available: ${otherNames}. Use --provider to override.`;
271277
}
272278

279+
export function shouldPromptProviderOverride(
280+
interactive: boolean,
281+
detectedProviders: Provider[],
282+
): boolean {
283+
return interactive && detectedProviders.length > 1;
284+
}
285+
286+
export function buildProviderChoices(
287+
detectedProviders: Provider[],
288+
includeCustomProvider: boolean = true,
289+
): IProviderChoice[] {
290+
const choices: IProviderChoice[] = detectedProviders.map((provider) => ({
291+
label: formatProviderName(provider),
292+
provider,
293+
custom: false,
294+
}));
295+
296+
if (includeCustomProvider) {
297+
choices.push({
298+
label: 'Custom provider command',
299+
custom: true,
300+
});
301+
}
302+
303+
return choices;
304+
}
305+
306+
export function selectProviderOverrideByIndex(
307+
choices: IProviderChoice[],
308+
input: string,
309+
): IProviderChoice | null {
310+
const selectedIndex = Number.parseInt(input.trim(), 10);
311+
if (!Number.isInteger(selectedIndex) || selectedIndex < 1 || selectedIndex > choices.length) {
312+
return null;
313+
}
314+
315+
return choices[selectedIndex - 1] ?? null;
316+
}
317+
273318
function createCustomProviderSelection(params: {
274319
command?: string;
275320
name?: string;
@@ -333,6 +378,73 @@ async function promptCustomProviderSelection(): Promise<IProviderSelectionResult
333378
return createCustomProviderSelection({ command, name, id });
334379
}
335380

381+
async function promptProviderOverrideSelection(params: {
382+
selectedProvider: Provider;
383+
detectedProviders: Provider[];
384+
detectedCommands: string[];
385+
}): Promise<IProviderSelectionResult> {
386+
const { selectedProvider, detectedProviders, detectedCommands } = params;
387+
const selectedName = formatProviderName(selectedProvider);
388+
console.log(buildProviderSummary(selectedProvider, detectedProviders));
389+
390+
const useAutoSelected = await promptYesNo(`Use ${selectedName}?`, false);
391+
if (useAutoSelected) {
392+
return {
393+
provider: selectedProvider,
394+
detectedProviders,
395+
detectedCommands,
396+
summary: `Using auto-selected provider: ${selectedName}.`,
397+
};
398+
}
399+
400+
const choices = buildProviderChoices(detectedProviders);
401+
console.log('\nDetected provider presets:');
402+
choices.forEach((choice, index) => {
403+
console.log(` ${index + 1}. ${choice.label}`);
404+
});
405+
406+
const choiceInput = await promptText('Choose provider preset number');
407+
const choice = selectProviderOverrideByIndex(choices, choiceInput);
408+
if (!choice) {
409+
warn(`Invalid provider choice. Continuing with auto-selected provider: ${selectedName}.`);
410+
return {
411+
provider: selectedProvider,
412+
detectedProviders,
413+
detectedCommands,
414+
summary: `Using auto-selected provider: ${selectedName}.`,
415+
};
416+
}
417+
418+
if (choice.custom) {
419+
const command = await promptText('Custom provider command');
420+
if (!command) {
421+
warn(
422+
`No custom provider command entered. Continuing with auto-selected provider: ${selectedName}.`,
423+
);
424+
return {
425+
provider: selectedProvider,
426+
detectedProviders,
427+
detectedCommands,
428+
summary: `Using auto-selected provider: ${selectedName}.`,
429+
};
430+
}
431+
432+
const name = await promptText('Custom provider display name (optional)');
433+
const id = await promptText('Custom provider id (default: custom)');
434+
const customSelection = createCustomProviderSelection({ command, name, id });
435+
if (customSelection) {
436+
return customSelection;
437+
}
438+
}
439+
440+
return {
441+
provider: choice.provider ?? selectedProvider,
442+
detectedProviders,
443+
detectedCommands,
444+
summary: `Using provider selected during guided init: ${choice.label}.`,
445+
};
446+
}
447+
336448
async function resolveProviderSelection(
337449
options: IInitOptions,
338450
interactive: boolean,
@@ -385,6 +497,14 @@ async function resolveProviderSelection(
385497
}
386498
387499
const selectedProvider = chooseProviderByPrecedence(detectedProviders);
500+
if (shouldPromptProviderOverride(interactive, detectedProviders)) {
501+
return promptProviderOverrideSelection({
502+
selectedProvider,
503+
detectedProviders,
504+
detectedCommands,
505+
});
506+
}
507+
388508
return {
389509
provider: selectedProvider,
390510
detectedProviders,

0 commit comments

Comments
 (0)