---
phase: 31-puter.js-zero-config-cloud
plan: "03"
type: execute
wave: 2
depends_on: ["31-01", "31-02"]
files_modified:
- ui/src/components/onboarding/ProviderSelectionStep.tsx
- ui/src/components/onboarding/PuterAuthButton.tsx
- ui/src/components/onboarding/GoogleOAuthButton.tsx
- ui/src/components/onboarding/ApiKeyEntryForm.tsx
- ui/src/components/NexusOnboardingWizard.tsx
- ui/src/api/puter-proxy.ts
autonomous: true
requirements: [CLOUD-01, CLOUD-03, CLOUD-04, CLOUD-05]
must_haves:
truths:
- "User sees a Provider Selection step (Step 3 of 4) in the onboarding wizard with Puter, Google, and API Key options"
- "Clicking 'Continue with Puter' triggers Puter auth popup, stores token in React state for later server submission"
- "Google option shows policy-risk warning before enabling the Sign in button (3-second gate)"
- "Google OAuth opens popup, callback stores tokens by stateId, stateId captured in React state for later claim"
- "API key option shows inline form with provider dropdown and key input"
- "Skip for now button is always visible and advances to root directory step"
- "Pre-installed tools (Hermes, Claude Code, OpenClaw) show detected badges via adapter probe"
- "Step indicator shows 'Step N of 4' (updated from 3)"
- "handleSubmit posts collected credentials to server AFTER company creation (Puter token, Google claim, API key)"
artifacts:
- path: "ui/src/components/onboarding/ProviderSelectionStep.tsx"
provides: "Provider selection UI with three option cards and skip button"
exports: ["ProviderSelectionStep"]
- path: "ui/src/components/onboarding/PuterAuthButton.tsx"
provides: "Puter auth button that loads CDN script, calls signIn, captures token in state"
exports: ["PuterAuthButton"]
- path: "ui/src/components/onboarding/GoogleOAuthButton.tsx"
provides: "Google OAuth button with 3-second risk warning gate, opens popup, captures stateId"
exports: ["GoogleOAuthButton"]
- path: "ui/src/components/onboarding/ApiKeyEntryForm.tsx"
provides: "API key entry form with provider dropdown"
exports: ["ApiKeyEntryForm"]
- path: "ui/src/api/puter-proxy.ts"
provides: "API client for puter-proxy, oauth, and api-keys endpoints"
exports: ["puterProxyApi"]
- path: "ui/src/components/NexusOnboardingWizard.tsx"
provides: "4-step wizard with provider selection step inserted"
key_links:
- from: "ui/src/components/onboarding/PuterAuthButton.tsx"
to: "POST /api/puter-proxy/token"
via: "puterProxyApi.storeToken called in wizard handleSubmit after company creation"
pattern: "puter-proxy/token"
- from: "ui/src/components/onboarding/GoogleOAuthButton.tsx"
to: "POST /api/oauth/google/authorize"
via: "fetch call to get auth URL + stateId, then window.open"
pattern: "oauth/google/authorize"
- from: "ui/src/components/NexusOnboardingWizard.tsx"
to: "POST /api/oauth/google/claim"
via: "claim call in handleSubmit with stateId + companyId after company creation"
pattern: "oauth/google/claim"
- from: "ui/src/components/onboarding/ApiKeyEntryForm.tsx"
to: "POST /api/api-keys/store"
via: "puterProxyApi.storeApiKey called in wizard handleSubmit after company creation"
pattern: "api-keys/store"
- from: "ui/src/components/NexusOnboardingWizard.tsx"
to: "ui/src/components/onboarding/ProviderSelectionStep.tsx"
via: "import and render in step 3"
pattern: "ProviderSelectionStep"
- from: "ui/src/components/NexusOnboardingWizard.tsx"
to: "GET /api/adapters/:type/probe"
via: "probeAdapter calls for hermes_local, claude_local, openclaw_gateway on mount"
pattern: "probeAdapter|adapters.*probe"
---
Build the Provider Selection step UI components and wire them into the existing 3-step onboarding wizard (making it 4 steps). This includes the Puter auth button, Google OAuth button with risk warning, API key entry form, and adapter auto-detection badges.
Purpose: CLOUD-01 (Puter zero-config UI), CLOUD-03 (Google OAuth UI), CLOUD-04 (auto-detect tools), CLOUD-05 (API key entry UI). This is the user-facing surface for all cloud provider paths.
Output: ProviderSelectionStep, PuterAuthButton, GoogleOAuthButton, ApiKeyEntryForm, updated wizard
@/home/mikkel/.claude/get-shit-done/workflows/execute-plan.md
@/home/mikkel/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/31-puter.js-zero-config-cloud/31-RESEARCH.md
@.planning/phases/31-puter.js-zero-config-cloud/31-UI-SPEC.md
@.planning/phases/30-hardware-detection-mode-selection/30-02-SUMMARY.md
From ui/src/components/NexusOnboardingWizard.tsx (current state):
```typescript
// 3-step wizard: step 1 = hardware, step 2 = mode, step 3 = root directory
// Step indicator: "Step {step} of 3"
// defaultAdapter state: "claude_local" | "hermes_local"
// probing state for adapter detection
// handleSubmit creates company + agents on step 3 form submit
```
From ui/src/components/onboarding/ModeSelector.tsx (selected state pattern):
```typescript
// Cards use: border-primary bg-primary/5 when selected
// Vertical stack with gap-3
// Each card is a
Task 1: API client + PuterAuthButton + GoogleOAuthButton + ApiKeyEntryForm
ui/src/api/puter-proxy.ts,
ui/src/components/onboarding/PuterAuthButton.tsx,
ui/src/components/onboarding/GoogleOAuthButton.tsx,
ui/src/components/onboarding/ApiKeyEntryForm.tsx
ui/src/api/agents.ts,
ui/src/api/client.ts,
ui/src/components/onboarding/ModeSelector.tsx,
ui/src/components/NexusOnboardingWizard.tsx,
.planning/phases/31-puter.js-zero-config-cloud/31-UI-SPEC.md
**Create ui/src/api/puter-proxy.ts:**
Import `api` from `./client`. Export `puterProxyApi` with:
- `storeToken: (companyId: string, token: string) => api.post("/puter-proxy/token", { companyId, token })`
- `getAuthUrl: () => api.post<{ url: string; stateId: string }>("/oauth/google/authorize", {})` — no companyId needed
- `claimGoogleTokens: (stateId: string, companyId: string) => api.post("/oauth/google/claim", { stateId, companyId })`
- `storeApiKey: (companyId: string, provider: string, apiKey: string) => api.post("/api-keys/store", { companyId, provider, apiKey })`
**Create ui/src/components/onboarding/PuterAuthButton.tsx:**
Props: `{ onSuccess: (token: string) => void; onError: (msg: string) => void }`.
State: `loading` boolean, `connected` boolean.
`loadScript` helper: check if `window.puter` exists; if not, create a `