From dea2327fe1e10510b18170515e335da378478ddc Mon Sep 17 00:00:00 2001 From: Luca Martial Date: Wed, 13 May 2026 16:11:32 -0700 Subject: [PATCH] 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) --- packages/cli/src/setup-models.test.ts | 23 ++++++++++++++++++++--- packages/cli/src/setup-models.ts | 24 +++++++++++++++++++----- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/setup-models.test.ts b/packages/cli/src/setup-models.test.ts index 71e4344b..d9fef97d 100644 --- a/packages/cli/src/setup-models.test.ts +++ b/packages/cli/src/setup-models.test.ts @@ -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' }, diff --git a/packages/cli/src/setup-models.ts b/packages/cli/src/setup-models.ts index e2640d04..784a1d18 100644 --- a/packages/cli/src/setup-models.ts +++ b/packages/cli/src/setup-models.ts @@ -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,