From 79fae0369bf776be9d6b4da966f554c89e422cf9 Mon Sep 17 00:00:00 2001 From: WcaleNieWolny Date: Fri, 22 May 2026 10:06:38 +0200 Subject: [PATCH 01/14] feat(cli): offer AI build debug in onboarding TUI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Ink-based `build init` wizard runs `requestBuildInternal` with a custom logger so build output flows through React state. On failure today the build code still tries to drive the AI flow with `@clack/prompts` (`confirm`, `select`, `spinner`) — but Ink owns the terminal, so those clack writes/reads collide with React redraws and the AI prompt either corrupts the TUI or hangs. Net effect: users in the onboarding wizard never get a usable AI debug offer. This change adds a `caller-handled` mode so the wizard can drive the AI UX with Ink-native components. Layering (matches docs/superpowers/specs in wiki): - `cli/src/ai/analyze.ts` - `runCapgoAiAnalysis({ apiHost, apikey, jobId, appId })` reads the `/tmp/capgo-builds/.log` file, returns `too_big` over 10 MB, `error { message: 'log_unavailable' }` if missing, otherwise delegates to `postAnalyzeRequest`. - `releaseCapturedLogs(jobId)` thin wrapper around `cleanupCapturedJobFiles({ keepAiPromptFile: false })`. - `cli/src/ai/telemetry.ts` - Extend `AiAnalysisTriggeredBy` with `'onboarding'` so PostHog can segment onboarding-driven AI usage from CLI menu / CI flag. - `cli/src/schemas/build.ts` - `BuildRequestOptions.aiAnalysisMode?: 'auto-prompt' | 'caller-handled' | 'skip'` (default `'auto-prompt'` — direct CLI invocation matrix unchanged). - `BuildRequestResult.aiAnalysis?: { jobId, capturedLogPath, ready }`. - `cli/src/build/request.ts` - Branch the failure-AI block on `aiAnalysisMode`. `'caller-handled'` keeps the captured log alive (via `keepPromptFile=true`) and surfaces it on the result so callers can run `runCapgoAiAnalysis`. `'skip'` no-ops. `'auto-prompt'` keeps the existing clack flow. - Capture stays enabled in caller-handled mode regardless of TTY so the captured log is always available to the caller; explicit `'skip'` suppresses capture entirely. - `cli/src/build/onboarding/{types,android/types}.ts` - New `OnboardingStep`/`AndroidOnboardingStep`: `'ai-analysis-prompt'`, `'ai-analysis-running'`, `'ai-analysis-result'`. `STEP_PROGRESS` + `getPhaseLabel` entries. - `cli/src/build/onboarding/{ui,android/ui}/app.tsx` - Pass `aiAnalysisMode: 'caller-handled'` to `requestBuildInternal`. On failure with `aiAnalysis.ready === true`, transition to `'ai-analysis-prompt'` (Ink ` { + if (value === 'debug') { + setStep('ai-analysis-running') + } + else { + if (aiJobId) { + await trackAiAnalysisChoice({ + apikey: resolvedApiKeyRef.current ?? apikey ?? '', + orgId: resolvedOrgId ?? '', + appId, + platform: 'android', + jobId: aiJobId, + choice: 'skip', + triggeredBy: 'onboarding', + }).catch(() => { /* telemetry never breaks the wizard */ }) + } + setStep('build-complete') + } + }} + /> + + )} + + {/* AI debug — spinner while the edge function is running */} + {step === 'ai-analysis-running' && ( + + + + )} + + {/* AI debug — render the diagnosis (or fallback message), then exit */} + {step === 'ai-analysis-result' && ( + + AI analysis + + {aiAnalysisText && {aiAnalysisText}} + {aiResultMessage && {aiResultMessage}} + + ⚠ AI can make mistakes. Always verify the diagnosis against the full log before applying the suggested fix. + + { + if (value === 'debug') { + setStep('ai-analysis-running') + } + else { + if (aiJobId) { + await trackAiAnalysisChoice({ + apikey: resolvedApiKeyRef.current ?? apikey ?? '', + orgId: resolvedOrgId ?? '', + appId, + platform: 'ios', + jobId: aiJobId, + choice: 'skip', + triggeredBy: 'onboarding', + }).catch(() => { /* telemetry never breaks the wizard */ }) + } + setStep('build-complete') + } + }} + /> + + )} + + {/* AI debug — spinner while the edge function is running */} + {step === 'ai-analysis-running' && ( + + + + )} + + {/* AI debug — render the diagnosis (or fallback message), then exit */} + {step === 'ai-analysis-result' && ( + + AI analysis + + {aiAnalysisText && {aiAnalysisText}} + {aiResultMessage && {aiResultMessage}} + + ⚠ AI can make mistakes. Always verify the diagnosis against the full log before applying the suggested fix. + + { - setStep('build-complete') - }} - /> - - )} + {/* AI debug — render the diagnosis (or fallback message), then offer + retry-or-skip. Retry transitions back to 'requesting-build' so the + user can rebuild after applying the AI's fix in another terminal, + without re-running the credential wizard. Capped at MAX_AI_RETRIES. */} + {step === 'ai-analysis-result' && (() => { + const retriesLeft = MAX_AI_RETRIES - aiRetryCount + const canRetry = retriesLeft > 0 + const retryLabel = retriesLeft === 1 + ? '🔄 I fixed it, retry build (last retry)' + : `🔄 I fixed it, retry build (${retriesLeft} retries left)` + return ( + + AI analysis + + {aiAnalysisText && {aiAnalysisText}} + {aiResultMessage && {aiResultMessage}} + + ⚠ AI can make mistakes. Always verify the diagnosis against the full log before applying the suggested fix. + + {!canRetry && ( + <> + You've used all {MAX_AI_RETRIES} retries. Exit and re-run the wizard if you need another attempt. + + + )} + { - setStep('build-complete') - }} - /> - - )} + {/* AI debug — render the diagnosis (or fallback message), then offer + retry-or-skip. Retry transitions back to 'requesting-build' so the + user can rebuild after applying the AI's fix in another terminal, + without re-running the credential wizard. Capped at MAX_AI_RETRIES. */} + {step === 'ai-analysis-result' && (() => { + const retriesLeft = MAX_AI_RETRIES - aiRetryCount + const canRetry = retriesLeft > 0 + const retryLabel = retriesLeft === 1 + ? '🔄 I fixed it, retry build (last retry)' + : `🔄 I fixed it, retry build (${retriesLeft} retries left)` + return ( + + AI analysis + + {aiAnalysisText && {aiAnalysisText}} + {aiResultMessage && {aiResultMessage}} + + ⚠ AI can make mistakes. Always verify the diagnosis against the full log before applying the suggested fix. + + {!canRetry && ( + <> + You've used all {MAX_AI_RETRIES} retries. Exit and re-run the wizard if you need another attempt. + + + )} +