import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useDialog } from "../context/DialogContext"; import { useCompany } from "../context/CompanyContext"; import { companiesApi } from "../api/companies"; import { goalsApi } from "../api/goals"; import { agentsApi } from "../api/agents"; import { issuesApi } from "../api/issues"; import { queryKeys } from "../lib/queryKeys"; import { Dialog, DialogOverlay, DialogPortal } from "@/components/ui/dialog"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; import { cn } from "../lib/utils"; import { getUIAdapter } from "../adapters"; import { defaultCreateValues } from "./agent-config-defaults"; import { AsciiArtAnimation } from "./AsciiArtAnimation"; import { ChoosePathButton } from "./PathInstructionsModal"; import { HintIcon } from "./agent-config-primitives"; import { Building2, Bot, Code, ListTodo, Rocket, ArrowLeft, ArrowRight, Terminal, Globe, Sparkles, MousePointer2, Check, Loader2, FolderOpen, ChevronDown, X, } from "lucide-react"; type Step = 1 | 2 | 3 | 4; type AdapterType = "claude_local" | "codex_local" | "process" | "http" | "openclaw"; const DEFAULT_TASK_DESCRIPTION = `Setup yourself as the CEO. Use the ceo persona found here: [https://github.com/paperclipai/companies/blob/main/default/ceo/AGENTS.md](https://github.com/paperclipai/companies/blob/main/default/ceo/AGENTS.md) Ensure you have a folder agents/ceo and then download this AGENTS.md as well as the sibling HEARTBEAT.md, SOUL.md, and TOOLS.md. and set that AGENTS.md as the path to your agents instruction file`; export function OnboardingWizard() { const { onboardingOpen, onboardingOptions, closeOnboarding } = useDialog(); const { selectedCompanyId, companies, setSelectedCompanyId } = useCompany(); const queryClient = useQueryClient(); const navigate = useNavigate(); const initialStep = onboardingOptions.initialStep ?? 1; const existingCompanyId = onboardingOptions.companyId; const [step, setStep] = useState(initialStep); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [modelOpen, setModelOpen] = useState(false); // Step 1 const [companyName, setCompanyName] = useState(""); const [companyGoal, setCompanyGoal] = useState(""); // Step 2 const [agentName, setAgentName] = useState("CEO"); const [adapterType, setAdapterType] = useState("claude_local"); const [cwd, setCwd] = useState(""); const [model, setModel] = useState(""); const [command, setCommand] = useState(""); const [args, setArgs] = useState(""); const [url, setUrl] = useState(""); // Step 3 const [taskTitle, setTaskTitle] = useState("Create your CEO HEARTBEAT.md"); const [taskDescription, setTaskDescription] = useState(DEFAULT_TASK_DESCRIPTION); // Created entity IDs — pre-populate from existing company when skipping step 1 const [createdCompanyId, setCreatedCompanyId] = useState(existingCompanyId ?? null); const [createdCompanyPrefix, setCreatedCompanyPrefix] = useState(null); const [createdAgentId, setCreatedAgentId] = useState(null); const [createdIssueRef, setCreatedIssueRef] = useState(null); // Sync step and company when onboarding opens with options. // Keep this independent from company-list refreshes so Step 1 completion // doesn't get reset after creating a company. useEffect(() => { if (!onboardingOpen) return; const cId = onboardingOptions.companyId ?? null; setStep(onboardingOptions.initialStep ?? 1); setCreatedCompanyId(cId); setCreatedCompanyPrefix(null); }, [onboardingOpen, onboardingOptions.companyId, onboardingOptions.initialStep]); // Backfill issue prefix for an existing company once companies are loaded. useEffect(() => { if (!onboardingOpen || !createdCompanyId || createdCompanyPrefix) return; const company = companies.find((c) => c.id === createdCompanyId); if (company) setCreatedCompanyPrefix(company.issuePrefix); }, [onboardingOpen, createdCompanyId, createdCompanyPrefix, companies]); const { data: adapterModels } = useQuery({ queryKey: ["adapter-models", adapterType], queryFn: () => agentsApi.adapterModels(adapterType), enabled: onboardingOpen && step === 2, }); const selectedModel = (adapterModels ?? []).find((m) => m.id === model); function reset() { setStep(1); setLoading(false); setError(null); setCompanyName(""); setCompanyGoal(""); setAgentName("CEO"); setAdapterType("claude_local"); setCwd(""); setModel(""); setCommand(""); setArgs(""); setUrl(""); setTaskTitle("Create your CEO HEARTBEAT.md"); setTaskDescription(DEFAULT_TASK_DESCRIPTION); setCreatedCompanyId(null); setCreatedCompanyPrefix(null); setCreatedAgentId(null); setCreatedIssueRef(null); } function handleClose() { reset(); closeOnboarding(); } function buildAdapterConfig(): Record { const adapter = getUIAdapter(adapterType); return adapter.buildAdapterConfig({ ...defaultCreateValues, adapterType, cwd, model, command, args, url, dangerouslySkipPermissions: adapterType === "claude_local", }); } async function handleStep1Next() { setLoading(true); setError(null); try { const company = await companiesApi.create({ name: companyName.trim() }); setCreatedCompanyId(company.id); setCreatedCompanyPrefix(company.issuePrefix); setSelectedCompanyId(company.id); queryClient.invalidateQueries({ queryKey: queryKeys.companies.all }); if (companyGoal.trim()) { await goalsApi.create(company.id, { title: companyGoal.trim(), level: "company", status: "active", }); queryClient.invalidateQueries({ queryKey: queryKeys.goals.list(company.id) }); } setStep(2); } catch (err) { setError(err instanceof Error ? err.message : "Failed to create company"); } finally { setLoading(false); } } async function handleStep2Next() { if (!createdCompanyId) return; setLoading(true); setError(null); try { const agent = await agentsApi.create(createdCompanyId, { name: agentName.trim(), role: "ceo", adapterType, adapterConfig: buildAdapterConfig(), runtimeConfig: { heartbeat: { enabled: true, intervalSec: 300, wakeOnDemand: true, cooldownSec: 10, maxConcurrentRuns: 1, }, }, }); setCreatedAgentId(agent.id); queryClient.invalidateQueries({ queryKey: queryKeys.agents.list(createdCompanyId), }); setStep(3); } catch (err) { setError(err instanceof Error ? err.message : "Failed to create agent"); } finally { setLoading(false); } } async function handleStep3Next() { if (!createdCompanyId || !createdAgentId) return; setLoading(true); setError(null); try { const issue = await issuesApi.create(createdCompanyId, { title: taskTitle.trim(), ...(taskDescription.trim() ? { description: taskDescription.trim() } : {}), assigneeAgentId: createdAgentId, status: "todo", }); setCreatedIssueRef(issue.identifier ?? issue.id); queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(createdCompanyId), }); setStep(4); } catch (err) { setError(err instanceof Error ? err.message : "Failed to create task"); } finally { setLoading(false); } } async function handleLaunch() { if (!createdAgentId) return; setLoading(true); setError(null); try { await agentsApi.invoke(createdAgentId); } catch { // Agent may already be running from auto-wake — that's fine } setLoading(false); reset(); closeOnboarding(); if (createdCompanyPrefix && createdIssueRef) { navigate(`/${createdCompanyPrefix}/issues/${createdIssueRef}`); return; } if (createdCompanyPrefix) { navigate(`/${createdCompanyPrefix}/dashboard`); return; } navigate("/dashboard"); } function handleKeyDown(e: React.KeyboardEvent) { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); if (step === 1 && companyName.trim()) handleStep1Next(); else if (step === 2 && agentName.trim()) handleStep2Next(); else if (step === 3 && taskTitle.trim()) handleStep3Next(); else if (step === 4) handleLaunch(); } } if (!onboardingOpen) return null; return ( { if (!open) handleClose(); }} >
{/* Close button */} {/* Left half — form */}
{/* Progress indicators */}
Get Started Step {step} of 4
{[1, 2, 3, 4].map((s) => (
))}
{/* Step content */} {step === 1 && (

Name your company

This is the organization your agents will work for.

setCompanyName(e.target.value)} autoFocus />