Uh oh!
There was an error while loading. Please reload this page.
feat(setup): setup wizard with browser-based Chat key handoff - #5911
Conversation
Adds `bun run setup` and `bun run doctor` for local installs, and replaces the wizard's paste-your-Chat-key step with a browser handoff that never puts the key in a URL.
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryHigh Risk Overview Adds CLI authentication without a loopback listener: the CLI opens Auth now preserves Chat keys move from workspace/account nav to Reviewed by Cursor Bugbot for commit c1f81d8. Bugbot is set up for automated code reviews on this repo. Configure here. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Greptile SummaryAdds an interactive setup and diagnostics CLI with Docker Compose, development, and Kubernetes modes.
Confidence Score: 5/5The PR appears safe to merge. No blocking failures remain within the scope of the previous review threads.
|
| Filename | Overview |
|---|---|
| scripts/setup/lifecycle.ts | Adds installation detection and lifecycle operations while pinning Kubernetes operations to the detected context and correctly reporting failed reset uninstalls. |
| scripts/setup/modes/k8s.ts | Implements local Kubernetes setup with validated context pinning, stdin-delivered Helm values, and safely quoted diagnostic commands. |
| scripts/setup/cli-auth.ts | Implements browser launch and polling for the device-style Chat key handoff. |
| apps/sim/lib/cli-auth/approval-store.ts | Implements expiring, verifier-bound, single-use CLI approvals in Redis. |
| apps/sim/app/(auth)/verify/use-verification.ts | Carries validated post-auth destinations through email verification and consumes stored redirects after use. |
| scripts/setup/doctor.ts | Adds diagnostics and repair support for the setup wizard’s supported environment layouts. |
Sequence Diagram
sequenceDiagram
participant CLI as Setup CLI
participant Browser
participant Sim as Sim API
participant Redis
CLI->>CLI: Generate request ID and poll secret
CLI->>Browser: Open /cli/auth with request ID and challenge
Browser->>Sim: Authenticate and approve pairing
Sim->>Redis: Store approval with challenge and TTL
loop Until approved or expired
CLI->>Sim: Poll with request ID and secret
Sim->>Redis: Verify challenge and atomically claim approval
end
Sim-->>CLI: Return generated Chat API key once
Reviews (13): Last reviewed commit: "fix(setup,auth): manage explicitly-confi..." | Re-trigger Greptile
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
The browser handoff is now the only path — the wizard waits on a spinner instead of racing a paste prompt. Consent card leads with "Connect your terminal" and moves the match-the-code disclaimer into the description.
Uh oh!
There was an error while loading. Please reload this page.
…ed keys Review findings from #5911: - helm/kubectl now run against the validated context instead of the ambient one - helm values are piped on stdin rather than passed as --set arguments - ENCRYPTION_KEY/API_ENCRYPTION_KEY are checked for the 64-hex format the app requires, not just length, so an unusable key is replaced rather than kept - the managed Redis container's published port is read back instead of assumed
TheodoreSpeaks
commented
Jul 24, 2026
TheodoreSpeaks
commented
Jul 24, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
list/generate/delete each repeated the same /api/validate-key envelope in their route. They now share callValidateKey in lib/copilot/server/api-keys.ts, which also keeps the display masking server-side so the full key can only ever leave at creation.
…ad code - PKCE verifier/state/pairing code now use generateSecureToken, generateRandomHex and generateShortId instead of hand-rolled randomBytes; the pairing loop's modulo was unbiased only because 256 % 32 == 0 - new sha256Base64Url in @sim/security/hash so both sides of the PKCE exchange derive the challenge from one implementation - isUsableSecret moved beside SECRET_KEYS so setup and doctor apply the same rule; doctor previously passed a key setup would replace - isTruthy narrowed to true/1, matching the app it claims to mirror — it accepted yes/on, so a flag could read on in doctor and off in the app - checkLive runs its five probes concurrently (~17s serial worst case) - detection overlaps the banner animation instead of queueing behind it - glyph.fail/glyph.warn at 13 sites that bypassed the constant; removed unused prompter exports, a dead ENV_PATHS re-export, and an unused export keyword
Compose writes a single root .env (what docker-compose reads via env_file) but the checks required the three per-app files, so a successful compose install was followed by doctor printing three failures and exiting 1 — and the whole coherence catalog was skipped because it keyed off apps/sim/.env existing. Layout is now derived from what's on disk and every check consults it: file and schema checks iterate the layout's targets, consistency reports skip when there's only one file to mirror, and coherence/live read the layout's primary file. The wizard's existing-config detection counts root for the same reason — a compose install used to read as unconfigured and re-run from scratch.
…tener The CLI no longer binds a local port. It generates a request id + poll secret, opens /cli/auth, and polls /api/cli/auth/poll over TLS while the user approves in the browser — so the flow works over SSH and inside containers, where the browser and terminal don't share a machine. - approve stores the approval keyed by request id (session-authed, userId from the session only); poll verifies the secret before an atomic claim, so an observer of the semi-public request id can neither mint nor cancel it - pairing code stays as the anti-phishing compare; no key ever crosses the browser; done page just confirms - removes the loopback listener, /token exchange, buildCliHandoffUrl, and validateCliCallbackUrl (+ its tests) — nothing hands a key to a URL anymore
…olliding A running sim-postgres fell through to `docker run --name sim-postgres` and died on the name conflict; a stopped one failed with "no DATABASE_URL to reach it" because the generated password only lived in the env files a fresh clone lacks. Both facts are recoverable from Docker: the ladder now reads the published port and password back via `docker inspect` and reuses the container (starting it if stopped). A container that won't answer prompts before recreating, and never drops the data volume silently.
Each run mode now names who it's for — compose for self-hosting/evaluating, dev for contributing to Sim, k8s for rehearsing a production deploy — with the live detection state (Docker/kube/VM) appended.
Uh oh!
There was an error while loading. Please reload this page.
# Conflicts: # package.json # scripts/check-api-validation-contracts.ts
TheodoreSpeaks
commented
Jul 24, 2026
TheodoreSpeaks
commented
Jul 24, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
TheodoreSpeaks
commented
Jul 25, 2026
TheodoreSpeaks
commented
Jul 25, 2026
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a25efdc. Configure here.
…try-After The poll route used the default public-IP bucket (10 burst, 5/min) but the CLI polls every 2s (30/min), so it 429'd within ~20s — worse behind a slow dev cold-compile. Give the endpoint a bucket matched to its cadence (60 burst, 60/min); it's not a brute-force surface (unknown request id returns pending, minting needs the 256-bit verifier). Also make the CLI honor Retry-After and back off on 429 so a shared-NAT per-IP limit degrades gracefully instead of hammering.
Local dev auto-start spawned bun run dev:full with no port check, so it silently started a server that couldn't bind when 3000/3002 were already taken (e.g. another worktree's dev server). Extract compose's port-conflict resolver into a shared ensurePortsFree(ports) and run it before the dev start too — kill/recheck/leave, same as compose. Leaving the ports skips the auto-start with guidance instead of failing; compose still treats it as fatal.
A kubeconfig context can outlive its cluster — a kind cluster gets deleted or its Docker container stops (Docker/machine restart), but the context entry remains, pointing at a dead API-server port. The wizard checked the context looked local and handed it to helm, which failed with 'cluster unreachable'. Add a clusterReachable() liveness probe: only offer the current context when it actually answers; if a local context is dead, fall through to the kind path. There, if kind still knows 'sim' but it's stopped, start its node containers and wait for the API; if it's gone, create fresh. Either way the user gets a working cluster instead of a cryptic helm failure.
…ent)
The chart's appVersion was "0.6.73", but CI publishes GHCR tags with a v prefix (its release-commit regex captures v0.7.45). Since sim.image defaults every image tag to Chart.AppVersion, a default helm install requested ghcr.io/simstudioai/{simstudio,realtime,migrations}:0.6.73 — a tag that has never existed — so app and realtime sat in ImagePullBackOff and helm --wait failed with 'progress deadline exceeded'. Any self-hoster installing with default values hit this, not just the setup wizard.
Set appVersion to v0.7.45 (latest release on main; all three images verified present on ghcr) and bump the chart version to 1.1.1. Verified with helm lint, helm template (all images render as v0.7.45), and a live helm upgrade on a kind cluster where the new pods pull successfully while the old 0.6.73 pods remain in ImagePullBackOff.Uh oh!
There was an error while loading. Please reload this page.
…ed, current)" This reverts commit 28b6047.
Staging moved the baseline to 975; this branch's two CLI-auth routes (approve, poll) make 977. The clean merge absorbed the earlier +2 adjustment.
TheodoreSpeaks
commented
Jul 25, 2026
TheodoreSpeaks
commented
Jul 25, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
1 issue from previous review remains unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 108f777. Configure here.
…pages
/account/settings/chat-keys is a real page but deliberately not a nav item, so the sidebar's parseSettingsPathSection fell through to defaultSection ('general') and highlighted General — the page read as though it lived inside General.
Resolve the sidebar's active item with a null default so an unmatched nested route highlights nothing, and widen SettingsSidebar's activeSection to string | null. The section feeding the title/description provider keeps its default (pages override title/description anyway), and /account/settings/billing/credit-usage still correctly highlights Billing.…y on reset, clear stale post-auth redirect - lifecycle: detection is now factual — a sim-dev release either exists on the current context or it doesn't. Gating on locality stranded a release the user explicitly confirmed during setup (status/start/stop/down/reset all claimed no k8s install). Locality is recorded instead and surfaced through describeInstall, which every destructive confirm renders, so acting on a non-local cluster is named and defaulted to no rather than silently blocked or silently allowed. - lifecycle: reset no longer discards helm uninstall's exit status. Env files are archived by that point, so claiming 'Reset complete' while the release still runs is the worst outcome — it now throws with retry/inspect commands. - auth: signup clears POST_AUTH_REDIRECT_STORAGE_KEY when it has no callbackUrl, and the verification-disabled path consumes it, so a stale CLI/invite destination can't leak into a later flow in the same tab.
TheodoreSpeaks
commented
Jul 25, 2026
TheodoreSpeaks
commented
Jul 25, 2026
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c1f81d8. Configure here.

Summary
bun run setup— interactive first-run wizard (--quick,--mode compose|dev|k8s);bun run doctor [--fix] [--json]validates an existing install. Run-mode picker names who each mode is for: compose (self-host/evaluate), dev (contribute), k8s (rehearse a prod deploy)/cli/auth, and polls/api/cli/auth/pollover TLS while the user approves in the browser. No loopback listener, so it works over SSH and inside containers. No key ever crosses the browser.env; dev writes the three per-app files) — a healthy compose install no longer reports false failurescallbackUrllike signin, so a new account returns to the CLI flowType of Change
Testing
Device-flow poll verified end-to-end against a local server + Redis (pending → approve →
consume-once; wrong-secret returns pending and does not delete the approval). Unit tests cover
the approval store and both routes; the container-reuse recovery and doctor layout were run
against a live Docker/env.
bun run lint,check:api-validation:strict,check:react-query,check:client-boundary,type-check, and the unit suites pass. The wizard's mode flows(compose/dev/k8s) aren't exercised by automated checks.
Checklist