Skip to content

feat(onboarding): shared engine for iOS + Android, TUI as render-only wrapper (PR 1)#2495

Merged
WcaleNieWolny merged 92 commits into
mainfrom
wolny/mcp-tui-integration
Jun 13, 2026
Merged

feat(onboarding): shared engine for iOS + Android, TUI as render-only wrapper (PR 1)#2495
WcaleNieWolny merged 92 commits into
mainfrom
wolny/mcp-tui-integration

Conversation

@WcaleNieWolny

@WcaleNieWolny WcaleNieWolny commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

What

The Builder onboarding mega-PR: both platforms' onboarding now runs on ONE shared headless engine, with the ink TUI as a render-only wrapper over the entire flow — including the post-save tail (CI secrets, env export, workflow file, build request). The MCP onboarding integration stays build-time gated OFF (__CAPGO_MCP_ONBOARDING__ = false; PR #2492 — stacked on this branch — completes and flips it).

Architecture

  • Shared engine: flow/contract.ts (PlatformFlow/StepView) + ios/flow.ts + android/flow.ts + the platform-neutral tail/flow.ts. Pure views/reducers/effects; zero ink imports.
  • TUI = thin wrapper: both wizards route every choice/input/effect through the engine; rendering stays ink. Ephemeral payloads (iOS import cert/profile/p12, CI secret entries) ride driver-held carried state and are never persisted (no secrets in progress.json).
  • Total resume: getIosResumeStep/android twin route gates (credentials-exist), the .p8 chain, verify-app, the import fork, and the marker-guarded tail.

Deliberate behavior changes (3 — everything else is parity)

  1. Apple Distribution certs (8363b26): create modern DISTRIBUTION-type certs instead of deprecated IOS_DISTRIBUTION; the cert-limit revoke picker scopes to the pool that's actually full (the old mixed list defaulted the cursor onto the user's production cert).
  2. Import save loop fix (442786e): saving-credentials builds the credential shape FIRST and self-heals only when the build fails — the persisted-resume-first order looped the iOS import fork forever.
  3. Resume data-safety gate: resuming with already-saved iOS credentials now gates on backup-or-exit instead of silently overwriting (main's behavior).

An adversarial audit verified these are the ONLY behavior changes: running the pre-change private TUI suite (which characterized main) against this branch's dist goes red on exactly those — nothing else. Test diff vs main is 100% additive (23 new files, +10.6k/-0).

Tested

  • 97-segment capgo chain green (incl. main's new suites + 23 of ours: engine units, render snapshots, frame-fit, routing parity).
  • Private TUI E2E suite: 79/79 journeys green against this branch's dist (drift guard 0 missing both platforms).
  • Release bundle leak-guard: MCP onboarding tools stripped (gate off on this branch); main's test:helper-dce preserved through the merge.

Merge notes

Summary by CodeRabbit

  • New Features
    • Added comprehensive Android app onboarding flow with automated credential provisioning.
    • Enhanced iOS onboarding with improved certificate handling and guided resume workflow.
    • Introduced MCP-powered guided onboarding tools for streamlined app setup.
    • Improved post-credentials workflow with CI secret management, build configuration, and environment file generation.
    • Enhanced Apple certificate support with improved distribution certificate handling.

…ship test hooks from the bundle + CI leak guard

Proven on the real CLI build: a dev-gated branch importing a src/__dev__/ module
is fully DCE'd from dist/index.js (marker absent). Apple-API spoofing for AI tests
(PR 2) will live in src/__dev__/ behind this gate, physically absent from the NPM build.
…ndroidOnboardingProgress

Brings the platform-agnostic onboarding engine (mcp/*, flow.ts) + its additive shared
deps (keystore/oauth-google/oauth-scopes/output-record/schemas) onto main. Unifies the
data model: main's step set (incl. the 4 TUI-only ai-analysis-* + resume-prompt) plus
the 3 MCP markers (activePlatform, keystorePasswordGenerated, keystorePasswordManual);
flow.ts KIND_TABLE covers the TUI-only steps. main's android/ui/app.tsx kept verbatim
(TUI render-only rewrite is a later plan). Behavior is moved/renamed only — NOT assumed
correct; the android audit is Plan 2.

Verified: typecheck 0 errors; engine 87 unit + e2e hermetic gate PASS; main's onboarding
tests still pass. Covers plan Tasks 2-3 (one green commit; they don't split cleanly).
…r-cancel gate (credentials-exist + backing-up) to match main — closes a data-safety gap
… engine (persistAndStep derives next via getAndroidResumeStep; behavior-preserving + dev mismatch guard) + sequencing smoke test
…rough the shared runAndroidEffect (behavior-preserving; logs kept)
…source-of-truth (remove redundant state mirrors; conservative)
saving-credentials now stashes the CI-secret entries (createCiSecretEntries)
in transient before routing to ask-build, mirroring the Ink TUI's
doSaveCredentials. Adds the additive post-save tail transient fields to
AndroidStepCtx and the rebuildTailCredentials/tailCiSecretEntries helpers the
later tail steps reuse to re-derive the same entries from progress.
detecting-ci-secrets discovers CI destinations and persists the single chosen
target (GitHub → ask-github-actions-setup, GitLab → ask-ci-secrets, many →
target-select). checking-ci-secrets resolves the GitHub repo label + existing
keys and gates on confirm-secrets-push / confirm-ci-secret-overwrite.
uploading-ci-secrets pushes the entries and branches with-workflow →
pick-package-manager vs build-complete. Routing mirrors the Ink TUI tail.
…ep(s)

exporting-env writes the 0o600 .env (defaultExportPath fallback) and routes an
existing file to confirm-env-export-overwrite (persisting the resolved path);
overwrite-and-export-env re-exports with overwrite=true. writing-workflow-file
generates + writes .github/workflows/capgo-build.yml (overwrite) using the
recorded build-script choice + secret keys. All finish at build-complete,
mirroring the Ink TUI tail.
requesting-build fires capgo build request through the pre-bound
requestBuildInternal dep (driver owns apikey/logger/silent). A successful build
with pending CI entries routes to detecting-ci-secrets to offer the secret
push; with no entries it finishes at build-complete. A failed build with a
captured log surfaces the AI job id and routes to the TUI-only
ai-analysis-prompt; otherwise build-complete. Mirrors the Ink TUI tail.
… transient (parity with TUI; no rebuild, no secrets on disk)
…ient/deps surface + additive persisted fields
…ning/mobileprovision types (not placeholders)
… (caught by the private E2E suite)

1. backing-up: the engine's success log dropped the backup destination path
   — main's bespoke logged '✔ Backup saved · <path>' (app.tsx:1473) so the
   user can find the backup. Restored the path detail.

2. verifying-key: the engine's catch echoed the multi-line verification-
   failure message into the log pane AND returned the error route — the
   error screen renders the same message, so the advice list painted twice
   on one frame. Main routes to handleError only; dropped the onLog echo
   (the onInternalLog breadcrumb stays).

Both pinned by the private e2e-tui goldens (setup-method-select.txt /
verifying-key__invalid.txt): 79/79 journeys green after this.
Suite-side counterpart of this branch: hermetic CAPGO_SKIP_UPDATE_PROMPT
(npm published 8.1.9 and the self-update prompt blocked every journey's
first frame, on main too), the documented resume-overwrite journey flip
(the credentials-exist gate this branch adds), and the pre-authorized
1-line cert-limit-prompt golden re-record ('iOS distribution' → 'Apple
Distribution', user-approved). 79/79 journeys + drift guard green against
this branch's dist.
The merged ErrorStep now delegates its options to buildHelpMenuOptions
(support first, 'Try again', 'Exit') — the old bare 'Retry' label this
assertion pinned no longer exists on either branch. Assert the new menu
shape (support entry + renamed retry) per the TUI-is-main's-reference
rule. 25/25.
Brings in 20 commits from main, most notably PR #2458 (precompiled, signed &
notarized macOS keychain helper packages) and PR #2489 (multi-channel bundle
upload), plus release chores (cli 8.3.0 / app 12.164.0) and frontend/backend
changes that do not touch onboarding.

Conflict resolutions (playbook: engine-driven structure is the base; main's
new features are ported in):

- cli/package.json: UNION. Kept our script entries (android-tail-*, ios-*,
  dev-gate-stripped, platform-flow-contract, tail-engine-shared, ...) plus
  main's `test:helper-dce` entry, and merged the `test` chain as the union of
  both sides' segments — main's `bun run test:helper-dce` is inserted right
  after `bun run build`, matching main's position. The chain otherwise keeps
  every segment from both sides. Version stays at main's 8.3.0.

- cli/build.mjs: UNION of both sides' `define` blocks (CLI + SDK builds):
  kept our `globalThis.__CAPGO_DEV__` / `globalThis.__CAPGO_MCP_ONBOARDING__`
  release gates AND main's `__CAPGO_ALLOW_HELPER_ENV_OVERRIDE__` DCE gate for
  the CAPGO_KEYCHAIN_HELPER_PATH dev override (PR #2458).

- cli/src/build/onboarding/ui/app.tsx: ours is the engine-driven thin wrapper;
  main's bespoke import-effect bodies (deleted on our branch, modified by
  PR #2458 on main) were dropped, and main's actual change — the removal of
  the import-compiling-helper step — was ported into our engine-driven
  structure instead (see below).

- bun.lock: took main's cli version stamp; `bun install` then reconciled it
  to 8.3.0.

- private/cli-mcp-tests (submodule): fast-forwarded to main's pointer
  71db228 ("test(compat): pin cert golden + resume journey to capgo main") —
  our pointer 8a615f2 is its direct ancestor, and 71db228 matches the merged
  code's helper contract. The local frozen suite reconciliation stays with
  the orchestrator.

Port of PR #2458's import-compiling-helper removal (main is the reference for
its feature; our engine consumes main's new precompiled-helper mechanism):

- macos-signing.ts took main's version wholesale (resolveHelperBinary +
  signature-verified precompiled helper; precompileSwiftHelper/isHelperCached
  deleted). types.ts / error-categories.ts / ui/steps/ios-import.tsx merged
  clean to main's contract (step union, STEP_PROGRESS, error category, and
  ImportCompilingHelperStep component all gone).

- ios/flow.ts (ours-only engine file): removed the import-compiling-helper
  effect case, the precompileSwiftHelper/isHelperCached deps, the
  helperCompiled transient + carried idempotency guard, and rerouted the
  import-export-warning resolver's 'go' branch straight to import-exporting.
  Docs updated to record the PR #2458 contract.

- ui/app.tsx: dropped the removed imports, the engine-deps wiring for the
  two deleted callbacks, the step from IOS_ENGINE_IMPORT_EFFECT_STEPS, and
  the isHelperCached arg on the export-warning runIosEffect call.

- Tests updated to main's contract (each pinned the removed step):
  - test-ios-import-export.mjs: collapsed the three 'go' resolver variants
    into one unconditional 'go' -> import-exporting case, deleted the
    import-compiling-helper effect section and the "NOT cached" driver
    journey, dropped the two deps from makeDeps.
  - test-ios-e2e.mjs: journey (b) no longer detours through the compile
    step; dropped the deps mocks, the isHelperCached overrides, and the
    step's error-route case in (e).
  - test-ios-tui-routing.mjs: replaced the cached/NOT-cached parity pair
    with a single unconditional 'go' MATCH case.
  - test-ios-tui-render.mjs: removed the ImportCompilingHelperStep render
    test + its import (component deleted by #2458).
  - test-ios-resume.mjs: dropped the step from EPHEMERAL_PICKER_STEPS.
  - test-ios-tail-handoff.mjs: comment-only update.

Gates: tsc --noEmit clean; bun run build clean; test:helper-dce green
(entry + chain segment + cli/scripts/check-helper-dce.sh all survive);
test-dev-gate-stripped green (MCP onboarding tools still ABSENT — the PR-1
gate stays off); full `bun run test` chain exit 0.

TUI E2E (frozen private suite @ 68a181d vs this dist): 79 journeys — 78 pass,
1 flaky (support flow → view logs → cancel; pass-on-retry). Runtime drift
guard: ios 40/65 covered / 25 excluded / 0 missing; android 42/65 covered /
23 excluded / 0 missing. Known follow-up (private repo, orchestrator-owned):
test:e2e-tui:harness fails at harness-tests/test-drift.mjs hygiene —
"UNCOVERED key 'import-compiling-helper' is not a STEP_PROGRESS step id" —
because the suite's restored exclusion (private PR #4) is orphaned now that
this merge ports the step's removal.
Drift-guard bookkeeping for the main merge: this branch now ports main's
import-compiling-helper step removal (PR #2458), so the private suite's
restored exclusion (#4) became orphaned — 1044e70 re-applies #3 exactly.
Harness self-tests + the 79-journey suite green against this branch's
dist (78 pass + 1 pty flake, pass-on-retry; drift 0 missing both
platforms).
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@WcaleNieWolny, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 3 hours, 34 minutes, and 53 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a1ade4c8-7f60-434a-aaf5-2c55533916eb

📥 Commits

Reviewing files that changed from the base of the PR and between dd94506 and aa14642.

📒 Files selected for processing (5)
  • cli/src/build/onboarding/env-export.ts
  • cli/src/build/onboarding/mcp/app-id-validation.ts
  • cli/src/build/onboarding/tail/flow.ts
  • cli/test/test-ios-import-discovery.mjs
  • cli/test/test-ios-import-recovery.mjs
📝 Walkthrough

Walkthrough

The CLI now uses build-time globals to gate dev and MCP onboarding behavior, adds shared onboarding contracts and tail-state types, refactors Android and iOS onboarding into engine-driven flows, and expands MCP onboarding tooling, validation, and CLI test coverage.

Changes

CLI onboarding runtime

Layer / File(s) Summary
Build flags and test entrypoints
cli/build.mjs, cli/src/dev-flag.d.ts, cli/package.json, cli/src/__dev__/README.md, cli/test/dev-preload.mjs, cli/test/test-dev-gate-stripped.mjs
Build defines __CAPGO_DEV__ and __CAPGO_MCP_ONBOARDING__, the CLI declares those globals, dev-only usage is documented, and test scripts plus a release-bundle guard are added.
Shared flow and output contracts
cli/src/build/mobileprovision-parser.ts, cli/src/build/onboarding/flow/*, cli/src/build/onboarding/tail-types.ts, cli/src/build/onboarding/types.ts, cli/src/build/output-record.ts, cli/src/build/onboarding/ui/p8-error.ts, cli/src/build/onboarding/env-export.ts
Shared onboarding flow/view contracts, tail progress types, output-record parsing, p8 error classification, and env-export docs are added or updated for the new flow model.
Android flow core
cli/src/build/onboarding/android/*
Android onboarding adds a headless flow core, OAuth/session helpers, resume routing, tail progress markers, and engine-driven auto/effect handling.
Android UI and tests
cli/src/build/onboarding/android/ui/app.tsx, cli/test/test-android-*, cli/test/test-tail-engine-shared.mjs, cli/test/test-platform-flow-contract.mjs, cli/test/test-p8-error.mjs
The Android TUI is rewired to engine-driven auto/tail routing, and the new tests cover tail rendering, routing parity, engine behavior, and platform-flow contracts.
iOS flow core
cli/src/build/onboarding/ios/progress.ts, cli/src/build/onboarding/apple-api.ts, cli/src/build/mobileprovision-parser.ts, cli/src/build/onboarding/ui/steps/ios-credentials.tsx
iOS resume routing adds tail markers and a credentials-exist gate, Apple Distribution certificate handling changes, and iOS credential UI copy is updated.
iOS UI and tests
cli/test/test-ios-*
The iOS test suite expands across create-new, import, recovery, resume, tail handoff, verify-app, flow-contract, routing, and render coverage.

MCP onboarding tools

Layer / File(s) Summary
Contracts and session helpers
cli/src/build/onboarding/mcp/*
The MCP onboarding contract, explanation map, OAuth session registry, input validation, terminal launcher, and schema definitions are added for the guided onboarding tools.
Engine and server wiring
cli/src/build/onboarding/mcp/engine.ts, cli/src/build/onboarding/mcp/onboarding-tools.ts, cli/src/mcp/server.ts
The MCP onboarding engine resolves start/advance/explain flows, builds the dependency graph, and registers the tools behind the onboarding flag.

Sequence Diagram(s)

sequenceDiagram
  participant Server
  participant OnboardingTools
  participant Engine
  participant OAuthSession
  participant TerminalLaunch
  Server->>OnboardingTools: registerOnboardingTools()
  OnboardingTools->>Engine: gatherFacts()
  OnboardingTools->>Engine: runStart() / runAdvance()
  Engine->>OAuthSession: beginOAuthSession()
  Engine->>TerminalLaunch: launchBuildInTerminal()
  Engine-->>OnboardingTools: NextStepResult
  OnboardingTools-->>Server: renderResult()
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Cap-go/capgo#2064: Builds on the Android OAuth and keystore onboarding paths already reshaped there.
  • Cap-go/capgo#2376: Touches the same Android onboarding TUI surface in cli/src/build/onboarding/android/ui/app.tsx.
  • Cap-go/capgo#2317: Also changes Android onboarding resume routing in cli/src/build/onboarding/android/progress.ts.

Suggested labels

codex

Suggested reviewers

  • zinc-builds

@codspeed-hq

codspeed-hq Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing wolny/mcp-tui-integration (aa14642) with main (0b9a409)2

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (dc587c1) during the generation of this report, so 0b9a409 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Comment thread cli/src/build/onboarding/mcp/engine.ts Fixed
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

🧪 Builder onboarding TUI preview — ✅ passed

▶ Open the interactive HTML report (zoomable journey tree + cast playback)

Commit: aa14642 · Job summary with the result table

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 73fe414168

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/src/build/onboarding/mcp/engine.ts
…read, bundle-id resolution, error truthfulness

BLOCKER: buildIosSavedCredentials re-reads the .p8 from the persisted
progress.p8Path via the injected deps.readFile when the carried key content
was lost (crash/restart), and REFUSES to save an app_store credential map
without the ASC key — pre-fix the resume silently wrote credentials missing
APPLE_KEY_CONTENT/APPLE_KEY_ID/APPLE_ISSUER_ID and declared success
(3-engine hostile-review consensus; regression vs main's guards).
buildSavedCredentials may now be async (tail/flow.ts awaits it).

Engine findings:
- resolveIosBundleId: override → detected Release bundle id → appId, threaded
  into the provisioning-map key, the tail rebuild, and all six runIosEffect
  call sites (bundle id ≠ Capgo appId)
- resolveP8Content wraps readFile: a stale p8Path surfaces the NeedP8-style
  re-provide error instead of a raw ENOENT
- import-portal open-anyway: success log carries the URL; openExternal
  failure logs the visit-manually fallback instead of fabricating success
- tail saving-credentials self-heal divert surfaces the underlying builder
  error (guidance line + internal log)
- requesting-build honors deps.signal (pre/post/catch abort bails; limitation
  vs request.ts internals documented)
- android backing-up: only ENOENT keeps the benign note; real copy failures
  log a truthful warning
- gcp-setup-running strips _oauthRefreshToken from persisted progress after a
  successful revoke
- createCertificate/createProfile rethrow the ORIGINAL Apple error when the
  follow-up list call also fails
- rebuildIosTailCredentials restores APPLE_KEY_ID/APPLE_ISSUER_ID from
  progress (keyId/issuerId ?? apiKeyVerified)
- preloadWorkflowScripts failures route through onInternalLog
- test-ios-import-discovery: prefetch fixture uses the RAW AscProfileSummary
  shape so the synthesizeProfileFromAscSummary mapping is actually exercised
…tale closures, tail readFile wire

- .p8 submit handlers no longer rewrite every failure to 'File not found':
  classifyP8SubmitError (new ui/p8-error.ts) keeps the exact copy for ENOENT
  only; other errors reach handleError with the real message, and every
  failure lands in the internal log
- the three [step]-keyed drivers (create-new / import / tail) call
  handleError through a ref (handleErrorRef), eliminating stale-closure
  routing on error
- NeedP8Error hoisted to module scope (stable instanceof across renders)
- tail driver carried.savedCredentials built via an explicit filtered
  conversion instead of a lying Record<string,string> cast
- tail driver deps now thread readFile (mirrors the create driver) so the
  engine's crash-recovery .p8 re-read works through the TUI — verified by the
  private e2e crash-recovery journeys (xfail pin promoted)
- error sinks: support-flow and restart deleteProgress catches log instead of
  swallowing; cert-limit/duplicate-profile Select onChange guarded against
  the @inkjs/ui re-fire with one-shot refs
Comment thread cli/test/test-ios-import-recovery.mjs Fixed
Comment thread cli/test/test-ios-import-recovery.mjs Fixed

@jinhongliang991013 jinhongliang991013 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I traced the post-save resume markers through both platform drivers. The resume router is implemented, but the production write path never creates the state it consumes.

}

await deps.updateSavedCredentials(progress.appId, deps.platform, credentials)
await deps.deleteProgress(progress.appId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Persist the tail milestones before deleting progress

This deletes progress.json immediately after credentials are written, but no production path persists completedSteps.credentialsSaved (or the later �uildRequested / ciSecretsUploaded markers). The only marker values under cli/src are the synthetic object in Android's ailEngineNext; iOS has no writer at all. The resume tests pass because they construct these markers directly in fixtures rather than exercising the real save/restart path.

As a result, exiting on �sk-build or any later tail screen leaves no progress file. The next �uild init cannot enter ailResumeStep, so the advertised total resume is lost and the user is sent through credential setup again; after a queued build or secret upload this can also repeat the remote side effect. Persist credentialsSaved plus the tail fields before leaving this step, persist �uildRequested/ciSecretsUploaded only after their side effects succeed, and defer deleting progress until �uild-complete.

- ui/app.tsx + android/ui/app.tsx: remove unused imports (BuildLogger,
  homedir, CertificateLimitError, DuplicateProfileError, bundleIdMatches)
  and _-prefix two set-but-never-read state values (pendingRecoveryAction,
  duplicateProfileOrigin) — oxlint no-unused-vars, zero behavior change
- android/flow.ts: drop the unused androidStepView export (knip dead-code)
- mcp/engine.ts: remove the dead keystoreFileWritten=true assignment in the
  first write-block (that block returns at mapAndroidView before the loop can
  re-read it; the live write-once guard is the second site) — code-quality nit
- test-ios-import-recovery.mjs: assert the opened/warned portal URL via
  RegExp.test() instead of String.includes(<url-literal>) so it is no longer a
  CodeQL incomplete-URL-substring-sanitization sink (it is a log-message check,
  not URL validation)
- typos: reword MATCH* comment false-positives + the SER1 cert fixture value
Comment thread cli/test/test-ios-import-recovery.mjs Fixed
@coderabbitai coderabbitai Bot added the codex label Jun 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/src/build/onboarding/android/oauth-google.ts (1)

444-462: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Close the loopback timer/server on a pre-aborted signal.

If signal.aborted is already true, Line 458 returns before close() and codePromise.then(close, close) are wired. That leaves the 5-minute timer armed and the loopback server unclosed, so cancelling the flow can keep the CLI process alive until timeout.

Suggested fix
     const timer = setTimeout(() => {
       codeReject(new Error(`Timed out after ${Math.round(args.timeoutMs / 1000)}s waiting for browser sign-in`))
     }, args.timeoutMs)
 
+    function close() {
+      clearTimeout(timer)
+      if (args.signal)
+        args.signal.removeEventListener('abort', onAbort)
+      server.close()
+    }
+
     function onAbort() {
       const err = new Error('OAuth flow aborted')
+      close()
       codeReject(err)
       // Also reject the outer Promise. Before `server.listen` resolves this is
       // the only path to surface the abort to the caller; afterwards
       // `rejectHandle` is a no-op on an already-resolved promise.
       rejectHandle(err)
@@
-    function close() {
-      clearTimeout(timer)
-      if (args.signal)
-        args.signal.removeEventListener('abort', onAbort)
-      server.close()
-    }
-
     // Always clean up on either outcome.
     codePromise.then(close, close)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/build/onboarding/android/oauth-google.ts` around lines 444 - 462, If
args.signal.aborted is true the function returns before the loopback server and
timeout are cleaned up; ensure you close the server and cancel the timer when
pre-aborted by calling the existing cleanup helpers before returning.
Concretely, after calling onAbort() in the pre-abort branch, also invoke the
cleanup/close function used later (close) and clear the timeout
(clearTimeout(timer)) so the loopback server is closed and the timer cleared (or
alternatively move the wiring of codePromise.then(close, close) and timeout
setup to before the aborted check so the same cleanup path runs); reference the
symbols timer, onAbort, close, codePromise.then(close, close), and
args.signal.aborted to locate and apply the change.
♻️ Duplicate comments (1)
cli/src/build/onboarding/tail/flow.ts (1)

786-818: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Persist the tail milestones before deleting progress.json.

This still writes credentials and immediately deletes progress, but none of the shared tail effects record the milestone markers that resume logic consumes (completedSteps.credentialsSaved, buildRequested, ciSecretsUploaded). cli/src/build/onboarding/ios/progress.ts resumes the post-save flow entirely from those markers, so exiting on ask-build or any later tail screen drops the user back out of the tail and reopens the double-build / double-secret-upload risk on retry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/build/onboarding/tail/flow.ts` around lines 786 - 818, The code
deletes progress.json before persisting the tail milestone markers the resume
logic needs; fix by setting the resume markers on the progress object (e.g.
ensure progress.completedSteps = progress.completedSteps || {}; set
progress.completedSteps.credentialsSaved = true; set progress.buildRequested and
progress.ciSecretsUploaded as appropriate) and persist that updated progress
before calling deps.deleteProgress; call the repository's persistence helper
(e.g. deps.updateProgress(progress) or deps.saveProgress(progress) — or, if the
codebase only exposes a specific persister, include those markers in the call
that writes progress/credentials (ensure you persist them after
deps.updateSavedCredentials(...) and before deps.deleteProgress), then continue
to createCiSecretEntries(...) and return the same next/transient payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/src/build/onboarding/android/oauth-scopes.ts`:
- Around line 7-10: There’s a duplicate local OAUTH_SCOPES_FOR_ONBOARDING
defined in the Android onboarding UI; remove that local definition and import
the exported OAUTH_SCOPES_FOR_ONBOARDING (the constant exported alongside
GOOGLE_OAUTH_SCOPES_ANDROIDPUBLISHER) and use it in place of the local variable.
Add the import at the top of the module, delete the local constant (or replace
its references), and ensure all usages in the onboarding UI now reference the
imported OAUTH_SCOPES_FOR_ONBOARDING to prevent scope drift.

In `@cli/src/build/onboarding/android/ui/app.tsx`:
- Around line 841-848: The retry target on persistence failure is incorrectly
using nextStep (which may advance past the failed save) — change the error
routing so save/write failures always retry the current step: in the block that
calls handleErrorRef.current?.(err, nextStep ?? stepRef.current), pass
stepRef.current explicitly (handleErrorRef.current?.(err, stepRef.current)) so
the retry/UX returns to the step that failed to persist; leave
getAndroidResumeStep behavior unchanged.

In `@cli/src/build/onboarding/env-export.ts`:
- Around line 9-14: The comment claiming the `.env.capgo.<appId>.<platform>`
file is to be "committed manually" is incorrect; update the v1 contract comment
that contains the literal `.env.capgo.<appId>.<platform>` to state this file is
a local secret that MUST NOT be committed, that the code performs NO git
operations, and advise adding it to `.gitignore` (remove the phrase "that the
USER commits manually"); make the same change to the other identical comment
instance referenced (the one around lines 29-34) so both places consistently
indicate the file must remain local and uncommitted.

In `@cli/src/build/onboarding/mcp/app-id-validation.ts`:
- Around line 25-35: The regex SAFE_APP_ID used by isSafeAppIdForCommand
currently allows identifiers without a dot (e.g., "my-app"), which violates the
reverse-domain requirement; update SAFE_APP_ID to enforce at least one '.' by
adding a positive lookahead that requires a dot before the rest of the pattern
(so the pattern still enforces the same token rules but must contain a dot),
then keep isSafeAppIdForCommand unchanged to use the new SAFE_APP_ID.

In `@cli/src/build/onboarding/mcp/engine.ts`:
- Around line 1533-1538: resolveCurrentState currently only checks for a single
detected platform and otherwise returns 'platform-select', which misreports when
facts.platformsDetected is empty; update resolveCurrentState to explicitly
handle the no-platform case by returning 'no-platform' when
facts.platformsDetected.length === 0 (before the existing single-platform
checks), leaving the getAndroidResumeStep(androidProgress) and 'ios-api-key'
branches intact so behavior matches decideStart.

In `@cli/src/build/onboarding/tail/flow.ts`:
- Around line 423-427: The TAIL_KIND mapping incorrectly marks the
'ci-secrets-setup' TailStep as 'auto' while tailViewForStep() expects it to be a
'choice' (so retry/skip options render correctly); update the TAIL_KIND constant
to set 'ci-secrets-setup' => 'choice' (i.e., change the value for the
'ci-secrets-setup' key in the TAIL_KIND Record<TailStep, TailStepKind>).
- Around line 384-388: The options array OPTIONS_ASK_GITHUB_ACTIONS_SETUP emits
a value 'no' for the decline branch which conflicts with the expected 'declined'
used by TailEffectProgress.setupMode and applyTailInput(); change the third
entry's value from 'no' to 'declined' so the menu contract matches the shared
decline token and avoids ad-hoc remapping in drivers.

In `@cli/src/build/output-record.ts`:
- Around line 107-119: The function readBuildOutputRecord currently only
validates jobId, status and outputUrl before casting; extend its runtime
validation to verify the full BuildOutputRecord shape (at minimum ensure fields
platform and buildMode are strings and finishedAt is present with an appropriate
primitive type such as number or string) and only return parsed as
BuildOutputRecord when all required fields pass type checks; if validation
fails, return null. Update the checks inside readBuildOutputRecord to explicitly
test these additional properties rather than relying on a partial cast so
downstream consumers don’t receive malformed objects.

In `@cli/test/test-ios-import-discovery.mjs`:
- Around line 15-16: The test's routing expectation is inconsistent: the
'__cancel__' routing intent is documented to route to 'api-key-instructions' but
the assertion for getImportEntryStep(...) expects 'import-distribution-mode';
update the assertion(s) so they expect 'api-key-instructions' (or alternately
update the documented intent to match if that is the true contract) and make the
same change for the duplicate occurrences around lines 273-282; specifically,
find the test(s) that call getImportEntryStep(...) and replace the
'import-distribution-mode' expectation with 'api-key-instructions' so the spec
and assertion align with the router under test.

---

Outside diff comments:
In `@cli/src/build/onboarding/android/oauth-google.ts`:
- Around line 444-462: If args.signal.aborted is true the function returns
before the loopback server and timeout are cleaned up; ensure you close the
server and cancel the timer when pre-aborted by calling the existing cleanup
helpers before returning. Concretely, after calling onAbort() in the pre-abort
branch, also invoke the cleanup/close function used later (close) and clear the
timeout (clearTimeout(timer)) so the loopback server is closed and the timer
cleared (or alternatively move the wiring of codePromise.then(close, close) and
timeout setup to before the aborted check so the same cleanup path runs);
reference the symbols timer, onAbort, close, codePromise.then(close, close), and
args.signal.aborted to locate and apply the change.

---

Duplicate comments:
In `@cli/src/build/onboarding/tail/flow.ts`:
- Around line 786-818: The code deletes progress.json before persisting the tail
milestone markers the resume logic needs; fix by setting the resume markers on
the progress object (e.g. ensure progress.completedSteps =
progress.completedSteps || {}; set progress.completedSteps.credentialsSaved =
true; set progress.buildRequested and progress.ciSecretsUploaded as appropriate)
and persist that updated progress before calling deps.deleteProgress; call the
repository's persistence helper (e.g. deps.updateProgress(progress) or
deps.saveProgress(progress) — or, if the codebase only exposes a specific
persister, include those markers in the call that writes progress/credentials
(ensure you persist them after deps.updateSavedCredentials(...) and before
deps.deleteProgress), then continue to createCiSecretEntries(...) and return the
same next/transient payload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 13ff12bf-1780-4ef7-8c8c-d2c35d478593

📥 Commits

Reviewing files that changed from the base of the PR and between 6096607 and dd94506.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (61)
  • cli/build.mjs
  • cli/package.json
  • cli/src/__dev__/README.md
  • cli/src/build/mobileprovision-parser.ts
  • cli/src/build/onboarding/android/flow.ts
  • cli/src/build/onboarding/android/keystore.ts
  • cli/src/build/onboarding/android/oauth-google.ts
  • cli/src/build/onboarding/android/oauth-scopes.ts
  • cli/src/build/onboarding/android/progress.ts
  • cli/src/build/onboarding/android/types.ts
  • cli/src/build/onboarding/android/ui/app.tsx
  • cli/src/build/onboarding/apple-api.ts
  • cli/src/build/onboarding/env-export.ts
  • cli/src/build/onboarding/flow/android-flow.ts
  • cli/src/build/onboarding/flow/contract.ts
  • cli/src/build/onboarding/flow/ios-flow.ts
  • cli/src/build/onboarding/ios/flow.ts
  • cli/src/build/onboarding/ios/progress.ts
  • cli/src/build/onboarding/mcp/app-id-validation.ts
  • cli/src/build/onboarding/mcp/contract.ts
  • cli/src/build/onboarding/mcp/engine.ts
  • cli/src/build/onboarding/mcp/explanations.ts
  • cli/src/build/onboarding/mcp/oauth-session.ts
  • cli/src/build/onboarding/mcp/onboarding-tools.ts
  • cli/src/build/onboarding/mcp/step-input.ts
  • cli/src/build/onboarding/mcp/terminal-launch.ts
  • cli/src/build/onboarding/tail-types.ts
  • cli/src/build/onboarding/tail/flow.ts
  • cli/src/build/onboarding/types.ts
  • cli/src/build/onboarding/ui/app.tsx
  • cli/src/build/onboarding/ui/p8-error.ts
  • cli/src/build/onboarding/ui/steps/ios-credentials.tsx
  • cli/src/build/output-record.ts
  • cli/src/dev-flag.d.ts
  • cli/src/mcp/server.ts
  • cli/src/schemas/onboarding.ts
  • cli/test/dev-preload.mjs
  • cli/test/test-android-tail-engine.mjs
  • cli/test/test-android-tail-render.mjs
  • cli/test/test-android-tail-routing.mjs
  • cli/test/test-android-tui-sequencing.mjs
  • cli/test/test-apple-api-cert-create.mjs
  • cli/test/test-dev-gate-stripped.mjs
  • cli/test/test-ios-confirm-app-id.mjs
  • cli/test/test-ios-create-new.mjs
  • cli/test/test-ios-e2e.mjs
  • cli/test/test-ios-flow-contract.mjs
  • cli/test/test-ios-import-discovery.mjs
  • cli/test/test-ios-import-export.mjs
  • cli/test/test-ios-import-pickers.mjs
  • cli/test/test-ios-import-recovery.mjs
  • cli/test/test-ios-recovery.mjs
  • cli/test/test-ios-resume.mjs
  • cli/test/test-ios-tail-handoff.mjs
  • cli/test/test-ios-tui-render.mjs
  • cli/test/test-ios-tui-routing.mjs
  • cli/test/test-ios-verify-app.mjs
  • cli/test/test-p8-error.mjs
  • cli/test/test-platform-flow-contract.mjs
  • cli/test/test-tail-engine-shared.mjs
  • private/cli-mcp-tests
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • Cap-go/capacitor-updater (manual)

Comment thread cli/src/build/onboarding/android/oauth-scopes.ts
Comment thread cli/src/build/onboarding/android/ui/app.tsx
Comment thread cli/src/build/onboarding/env-export.ts
Comment thread cli/src/build/onboarding/mcp/app-id-validation.ts Outdated
Comment thread cli/src/build/onboarding/mcp/engine.ts
Comment thread cli/src/build/onboarding/mcp/engine.ts
Comment thread cli/src/build/onboarding/tail/flow.ts
Comment thread cli/src/build/onboarding/tail/flow.ts
Comment thread cli/src/build/output-record.ts
Comment thread cli/test/test-ios-import-discovery.mjs Outdated
…ule, tail decline mapping

- env-export.ts: the .env.capgo.* file holds credentials; reword the docs so
  they no longer imply the user commits it — keep it out of version control
- mcp/app-id-validation.ts: enforce the documented reverse-domain rule in code
  (require a dot) so single-label ids like `my-app` no longer pass the gate
- tail/flow.ts: applyTailInput now maps the user-facing 'no' option to the
  canonical persisted setupMode 'declined' (mirrors the TUI drivers), so the
  shared-engine path's resume routing (setupMode === 'declined') matches; the
  ask-github-actions-setup input type now reflects the real 'no' value
- test-ios-import-recovery.mjs: drop the now-unused PORTAL_PROFILES_URL const
- test-ios-import-discovery.mjs: fix a stale __cancel__ doc/name that claimed
  api-key-instructions; the assertion verifies the distribution fork
@sonarqubecloud

Copy link
Copy Markdown

@WcaleNieWolny WcaleNieWolny merged commit 88bba30 into main Jun 13, 2026
47 checks passed
@WcaleNieWolny WcaleNieWolny deleted the wolny/mcp-tui-integration branch June 13, 2026 11:18
WcaleNieWolny added a commit that referenced this pull request Jun 15, 2026
… squash-merge)

PR-1 (#2495) was squash-merged into main, breaking shared-commit ancestry, so this merge surfaced ~20 add/add conflicts on the MCP/onboarding files even though PR-2 is a content superset of PR-1. Resolved by taking PR-2's version for all onboarding/MCP source + tests (verified: the only main-only commits touching them were the PR-1 squash + release bumps — no separate fix to fold in), keeping main's auto-merged package.json version (8.4.0) plus PR-2's test scripts, and pinning the cli-mcp-tests submodule to PR-2's c511d08.

Also folds in the pre-existing dead-code gate fix (PR-2's own exports, never in main): delete unused clearTailParked; knip-ignore the test-only exports clearAllSessions / MCP_ONLY_STATES / MCP_UNREACHABLE_STEPS (consumed only by the private cli-mcp-tests suite knip can't see).

Gates green post-merge: typecheck, lint, knip, build, instructions test, + 6 targeted onboarding/MCP tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants