refactor(cli): show gcloud project listing errors inline in the prompt

Move the gcloud failure message into the select prompt instead of writing
it to stdout separately, and highlight it with yellow ANSI coloring so
users notice the remediation steps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Luca Martial 2026-05-13 16:11:32 -07:00
parent 6962e7e4c9
commit dea2327fe1
2 changed files with 39 additions and 8 deletions

View file

@ -492,14 +492,31 @@ describe('setup Anthropic model step', () => {
expect(result.status).toBe('ready');
expect(listGcloudProjects).toHaveBeenCalledTimes(2);
expect(io.stdout()).toContain('Could not list Google Cloud projects with gcloud');
expect(io.stdout()).toContain('gcloud auth login --update-adc');
expect(prompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Which Google Cloud project should KTX use for Vertex AI?'),
message: expect.stringContaining('Could not list Google Cloud projects with gcloud'),
options: expect.arrayContaining([{ value: 'retry', label: 'Retry loading Google Cloud projects' }]),
}),
);
expect(prompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining(
`${String.fromCharCode(0x1b)}[33mCould not list Google Cloud projects with gcloud`,
),
}),
);
expect(prompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('gcloud auth login --update-adc'),
}),
);
expect(prompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining(
`${String.fromCharCode(0x1b)}[33mRun \`gcloud auth login --update-adc\``,
),
}),
);
expect(healthCheck).toHaveBeenCalledWith(
expect.objectContaining({
vertex: { project: 'other-gcp-project', location: 'us-east5' },

View file

@ -20,6 +20,12 @@ import {
type KtxSetupPromptOption,
} from './setup-prompts.js';
const ESC = String.fromCharCode(0x1b);
function yellow(text: string): string {
return `${ESC}[33m${text}${ESC}[39m`;
}
export interface KtxSetupModelArgs {
projectDir: string;
inputMode: 'auto' | 'disabled';
@ -549,9 +555,11 @@ function formatGcloudProjectListFailure(error: unknown): string {
? 'gcloud needs reauthentication before it can list projects.'
: 'gcloud returned an error while listing projects.';
return [
`│ Could not list Google Cloud projects with gcloud: ${reason}`,
'│ Run `gcloud auth login --update-adc` in another terminal, then choose Retry loading Google Cloud projects.',
].join('\n');
`Could not list Google Cloud projects with gcloud: ${reason}`,
'Run `gcloud auth login --update-adc` in another terminal, then choose Retry loading Google Cloud projects.',
]
.map((line) => yellow(line))
.join('\n');
}
async function chooseInteractiveVertexProject(
@ -563,11 +571,12 @@ async function chooseInteractiveVertexProject(
while (true) {
let projects: GcloudProjectChoice[] = [];
let listFailed = false;
let listFailureMessage: string | undefined;
try {
projects = await (deps.listGcloudProjects ?? defaultListGcloudProjects)();
} catch (error) {
listFailed = true;
io.stdout.write(`${formatGcloudProjectListFailure(error)}\n`);
listFailureMessage = formatGcloudProjectListFailure(error);
}
const orderedProjects = orderGcloudProjects(projects, currentProject);
@ -576,7 +585,12 @@ async function chooseInteractiveVertexProject(
}
const choice = await prompts.select({
message: `Which Google Cloud project should KTX use for Vertex AI?\n\n${VERTEX_PROJECT_PROMPT_CONTEXT}`,
message: `Which Google Cloud project should KTX use for Vertex AI?\n\n${[
VERTEX_PROJECT_PROMPT_CONTEXT,
listFailureMessage,
]
.filter((value): value is string => Boolean(value))
.join('\n\n')}`,
options: [
...orderedProjects.map((project) => ({
value: project.projectId,