import { 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, DialogContent } 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 { Building2, Bot, ListTodo, Rocket, ArrowLeft, ArrowRight, Terminal, Globe, Sparkles, Check, Loader2, FolderOpen, ChevronDown, } from "lucide-react"; type Step = 1 | 2 | 3 | 4; type AdapterType = "claude_local" | "process" | "http"; export function OnboardingWizard() { const { onboardingOpen, closeOnboarding } = useDialog(); const { setSelectedCompanyId } = useCompany(); const queryClient = useQueryClient(); const navigate = useNavigate(); const [step, setStep] = useState(1); 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(""); const [cwdPickerNotice, setCwdPickerNotice] = useState(null); // Step 3 const [taskTitle, setTaskTitle] = useState("Create your CEO HEARTBEAT.md"); const [taskDescription, setTaskDescription] = useState("You're the CEO of the company, make sure you have a file agents/ceo/HEARTBEAT.md that tells you your core loop. You MUST use the Paperclip SKILL."); // Created entity IDs const [createdCompanyId, setCreatedCompanyId] = useState(null); const [createdAgentId, setCreatedAgentId] = useState(null); 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(""); setCwdPickerNotice(null); setTaskTitle("Create your CEO HEARTBEAT.md"); setTaskDescription("You're the CEO of the company, make sure you have a file agents/ceo/HEARTBEAT.md that tells you your core loop. You MUST use the Paperclip SKILL."); setCreatedCompanyId(null); setCreatedAgentId(null); } 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); 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 { await issuesApi.create(createdCompanyId, { title: taskTitle.trim(), ...(taskDescription.trim() ? { description: taskDescription.trim() } : {}), assigneeAgentId: createdAgentId, status: "todo", }); 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(); navigate(`/agents/${createdAgentId}`); } 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(); } } const stepIcons = [Building2, Bot, ListTodo, Rocket]; return ( { if (!open) { reset(); closeOnboarding(); } }} > {/* Header */}
Get Started Step {step} of 4
{[1, 2, 3, 4].map((s) => (
))}
{/* Content */}
{step === 1 && (

Name your company

This is the organization your agents will work for.

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