diff --git a/apps/x/apps/renderer/src/components/onboarding/index.tsx b/apps/x/apps/renderer/src/components/onboarding/index.tsx
index d37cdf0f..2fb7426e 100644
--- a/apps/x/apps/renderer/src/components/onboarding/index.tsx
+++ b/apps/x/apps/renderer/src/components/onboarding/index.tsx
@@ -14,6 +14,7 @@ import { StepIndicator } from "./step-indicator"
import { WelcomeStep } from "./steps/welcome-step"
import { LlmSetupStep } from "./steps/llm-setup-step"
import { ConnectAccountsStep } from "./steps/connect-accounts-step"
+import { CodeModeStep } from "./steps/code-mode-step"
import { CompletionStep } from "./steps/completion-step"
interface OnboardingModalProps {
@@ -33,6 +34,8 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
case 2:
return
case 3:
+ return
+ case 4:
return
}
}, [state.currentStep, state])
diff --git a/apps/x/apps/renderer/src/components/onboarding/step-indicator.tsx b/apps/x/apps/renderer/src/components/onboarding/step-indicator.tsx
index d0b41991..661bdf8b 100644
--- a/apps/x/apps/renderer/src/components/onboarding/step-indicator.tsx
+++ b/apps/x/apps/renderer/src/components/onboarding/step-indicator.tsx
@@ -6,14 +6,16 @@ import type { Step, OnboardingPath } from "./use-onboarding-state"
const ROWBOAT_STEPS = [
{ step: 0 as Step, label: "Welcome" },
{ step: 2 as Step, label: "Connect" },
- { step: 3 as Step, label: "Done" },
+ { step: 3 as Step, label: "Code" },
+ { step: 4 as Step, label: "Done" },
]
const BYOK_STEPS = [
{ step: 0 as Step, label: "Welcome" },
{ step: 1 as Step, label: "Model" },
{ step: 2 as Step, label: "Connect" },
- { step: 3 as Step, label: "Done" },
+ { step: 3 as Step, label: "Code" },
+ { step: 4 as Step, label: "Done" },
]
interface StepIndicatorProps {
diff --git a/apps/x/apps/renderer/src/components/onboarding/steps/code-mode-step.tsx b/apps/x/apps/renderer/src/components/onboarding/steps/code-mode-step.tsx
new file mode 100644
index 00000000..28b1f6a2
--- /dev/null
+++ b/apps/x/apps/renderer/src/components/onboarding/steps/code-mode-step.tsx
@@ -0,0 +1,147 @@
+import { useState, useEffect, useCallback } from "react"
+import { Loader2, ArrowLeft, Terminal, CheckCircle2 } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { Switch } from "@/components/ui/switch"
+import { startProvisioning, type CodeModeAgentStatus } from "@/lib/code-mode-provisioning"
+import type { OnboardingState } from "../use-onboarding-state"
+
+interface CodeModeStepProps {
+ state: OnboardingState
+}
+
+const AGENTS = [
+ { key: "claude" as const, name: "Claude Code" },
+ { key: "codex" as const, name: "Codex" },
+]
+
+export function CodeModeStep({ state }: CodeModeStepProps) {
+ const { handleNext, handleBack } = state
+
+ const [enabled, setEnabled] = useState(false)
+ const [selected, setSelected] = useState>({ claude: false, codex: false })
+ const [status, setStatus] = useState(null)
+ const [statusLoading, setStatusLoading] = useState(true)
+ const [saving, setSaving] = useState(false)
+
+ // Reflect what's already set up: pre-select installed agents and turn the master
+ // switch on if any agent is already there, so returning users don't start from off.
+ useEffect(() => {
+ let cancelled = false
+ ;(async () => {
+ setStatusLoading(true)
+ try {
+ const result = await window.ipc.invoke("codeMode:checkAgentStatus", null)
+ if (cancelled) return
+ setStatus(result)
+ const claudeInstalled = result.claude.installed
+ const codexInstalled = result.codex.installed
+ if (claudeInstalled || codexInstalled) {
+ setEnabled(true)
+ setSelected({ claude: claudeInstalled, codex: codexInstalled })
+ }
+ } catch {
+ if (!cancelled) setStatus(null)
+ } finally {
+ if (!cancelled) setStatusLoading(false)
+ }
+ })()
+ return () => { cancelled = true }
+ }, [])
+
+ const onContinue = useCallback(async () => {
+ if (enabled) {
+ setSaving(true)
+ try {
+ await window.ipc.invoke("codeMode:setConfig", { enabled: true, approvalPolicy: "ask" })
+ window.dispatchEvent(new Event("code-mode-config-changed"))
+ } catch {
+ // Non-fatal — the user can still enable code mode later from Settings.
+ }
+ setSaving(false)
+ // Kick off engine downloads in the BACKGROUND for selected agents that aren't
+ // installed yet. We deliberately don't block onboarding on the ~200 MB download —
+ // it keeps running in the main process and its progress shows in Settings → Code Mode.
+ for (const a of AGENTS) {
+ if (selected[a.key] && !status?.[a.key].installed) {
+ startProvisioning(a.key, () => {})
+ }
+ }
+ }
+ handleNext()
+ }, [enabled, selected, status, handleNext])
+
+ return (
+
+ {/* Title */}
+
+ Set Up Code Mode
+
+
+ Use your existing Claude Code or Codex subscription inside Rowboat to tackle coding
+ tasks and unlock far more workflows, all without leaving the app. Make sure Claude Code
+ and Codex are signed in locally with{" "}
+ claude login or{" "}
+ codex login in your terminal.
+
+
+ {statusLoading ? (
+
+
+
+ ) : (
+
+ {/* Master enable */}
+
+
+
Enable code mode
+
+ Shows the code mode chip in the composer and lets the assistant delegate to your agents.
+