// [nexus] Replacement onboarding wizard — single-step root directory flow // Exports `OnboardingWizard` to match the named import in App.tsx. // Wired via Vite alias: all imports of ./components/OnboardingWizard are // redirected here at build time; the original file is preserved for upstream rebase. import { useState, useEffect } from "react"; import { useLocation, useNavigate, useParams } from "@/lib/router"; import { VOCAB } from "@paperclipai/branding"; import { useQueryClient } from "@tanstack/react-query"; import { useCompany } from "../context/CompanyContext"; import { useDialog } from "../context/DialogContext"; import { companiesApi } from "../api/companies"; import { agentsApi } from "../api/agents"; import { queryKeys } from "../lib/queryKeys"; import { resolveRouteOnboardingOptions } from "../lib/onboarding-route"; import { Dialog, DialogPortal } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { cn } from "../lib/utils"; // [nexus] Single-step onboarding wizard: root directory → workspace + PM + Engineer export function OnboardingWizard() { const { onboardingOpen, onboardingOptions, closeOnboarding } = useDialog(); const { companies, setSelectedCompanyId, loading: companiesLoading } = useCompany(); const queryClient = useQueryClient(); const navigate = useNavigate(); const location = useLocation(); const { companyPrefix } = useParams<{ companyPrefix?: string }>(); const [routeDismissed, setRouteDismissed] = useState(false); // Preserve wizard-show detection logic from the original OnboardingWizard const routeOnboardingOptions = companyPrefix && companiesLoading ? null : resolveRouteOnboardingOptions({ pathname: location.pathname, companyPrefix, companies, }); const effectiveOnboardingOpen = onboardingOpen || (routeOnboardingOptions !== null && !routeDismissed); useEffect(() => { setRouteDismissed(false); }, [location.pathname]); // Form state const [rootDir, setRootDir] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // [nexus] Adapter detection state — probe for Hermes when wizard opens const [defaultAdapter, setDefaultAdapter] = useState<"claude_local" | "hermes_local">("claude_local"); const [probing, setProbing] = useState(false); // Reset form when wizard closes useEffect(() => { if (!effectiveOnboardingOpen) { setRootDir(""); setError(null); setLoading(false); } }, [effectiveOnboardingOpen]); // [nexus] Probe for Hermes availability when wizard opens useEffect(() => { if (!effectiveOnboardingOpen) return; setProbing(true); agentsApi.probeAdapter("hermes_local") .then((data) => { if (data.available) setDefaultAdapter("hermes_local"); }) .catch(() => {}) // graceful — keep claude_local .finally(() => setProbing(false)); }, [effectiveOnboardingOpen]); function handleClose() { setRouteDismissed(true); closeOnboarding(); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (defaultAdapter === "claude_local" && !rootDir.trim()) return; setLoading(true); setError(null); try { // Step 1: Create workspace (company) named after VOCAB.appName const company = await companiesApi.create({ name: VOCAB.appName }); setSelectedCompanyId(company.id); queryClient.invalidateQueries({ queryKey: queryKeys.companies.all }); // [nexus] hermes_local doesn't require a cwd; directory is optional const adapterConfig = defaultAdapter === "hermes_local" ? (rootDir.trim() ? { cwd: rootDir.trim() } : {}) : { cwd: rootDir.trim() }; const runtimeConfig = { heartbeat: { enabled: true, intervalSec: 3600, wakeOnDemand: true, cooldownSec: 10, maxConcurrentRuns: 1, }, }; // Step 2: Create PM agent with role "ceo" for elevated permissions // (display label is "Project Manager" via AGENT_ROLE_LABELS; ceo/ bundle // has been rewritten with PM content in 04-01) await agentsApi.create(company.id, { name: "Project Manager", role: "ceo", adapterType: defaultAdapter, adapterConfig, runtimeConfig, }); // Step 3: Create Engineer agent await agentsApi.create(company.id, { name: "Engineer", role: "engineer", adapterType: defaultAdapter, adapterConfig, runtimeConfig, }); queryClient.invalidateQueries({ queryKey: queryKeys.agents.list(company.id), }); // Navigate to dashboard — not an issue detail page closeOnboarding(); navigate(`/${company.issuePrefix}/dashboard`); } catch (err) { setError(err instanceof Error ? err.message : "Setup failed. Please try again."); setLoading(false); } } if (!effectiveOnboardingOpen) return null; return (
{/* Backdrop */}
{/* Card */}
{/* Header */}

Welcome to {VOCAB.appName}

{defaultAdapter === "hermes_local" ? `${VOCAB.appName} will set up a local AI workspace with a ${VOCAB.ceo.toLowerCase()}, engineer, and generalist — no API key needed.` : `Choose a project root directory. ${VOCAB.appName} will set up a ${VOCAB.ceo.toLowerCase()} and engineer to start working.`}

{/* Form */}
setRootDir(e.target.value)} disabled={loading} autoFocus autoComplete="off" className="font-mono text-sm" />
{error && (

{error}

)}
); }