# Phase 6: UI Setup Wizard **Status:** `completed` **Dependencies:** Phase 4 (Routes) **Estimated Effort:** Medium (React component) --- ## Objective Add an optional Cursor CLI setup step to the welcome wizard, allowing users to configure Cursor as an AI provider during initial setup. --- ## Tasks ### Task 6.1: Create Cursor Setup Step Component **Status:** `completed` **File:** `apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx` ```tsx import React, { useState, useEffect, useCallback } from 'react'; import { CheckCircle2, XCircle, Loader2, ExternalLink, Terminal, RefreshCw } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { toast } from 'sonner'; import { api } from '@/lib/http-api-client'; interface CursorSetupStepProps { onComplete: () => void; onSkip: () => void; } interface CliStatus { installed: boolean; version?: string; path?: string; auth?: { authenticated: boolean; method: string; }; installCommand?: string; loginCommand?: string; } export function CursorSetupStep({ onComplete, onSkip }: CursorSetupStepProps) { const [status, setStatus] = useState(null); const [isChecking, setIsChecking] = useState(true); const [isLoggingIn, setIsLoggingIn] = useState(false); const checkStatus = useCallback(async () => { setIsChecking(true); try { const result = await api.setup.getCursorStatus(); if (result.success) { setStatus({ installed: result.installed ?? false, version: result.version, path: result.path, auth: result.auth, installCommand: result.installCommand, loginCommand: result.loginCommand, }); if (result.auth?.authenticated) { toast.success('Cursor CLI is ready!'); } } else { toast.error('Failed to check Cursor status'); } } catch (error) { console.error('Failed to check Cursor status:', error); toast.error('Failed to check Cursor CLI status'); } finally { setIsChecking(false); } }, []); useEffect(() => { checkStatus(); }, [checkStatus]); const handleLogin = async () => { setIsLoggingIn(true); try { // Copy login command to clipboard and show instructions if (status?.loginCommand) { await navigator.clipboard.writeText(status.loginCommand); toast.info('Login command copied! Paste in terminal to authenticate.'); } // Poll for auth status let attempts = 0; const maxAttempts = 60; // 2 minutes with 2s interval const pollInterval = setInterval(async () => { attempts++; try { const result = await api.setup.getCursorStatus(); if (result.auth?.authenticated) { clearInterval(pollInterval); setStatus((prev) => (prev ? { ...prev, auth: result.auth } : null)); setIsLoggingIn(false); toast.success('Successfully logged in to Cursor!'); } } catch { // Ignore polling errors } if (attempts >= maxAttempts) { clearInterval(pollInterval); setIsLoggingIn(false); toast.error('Login timed out. Please try again.'); } }, 2000); } catch (error) { console.error('Login failed:', error); toast.error('Failed to start login process'); setIsLoggingIn(false); } }; const handleCopyInstallCommand = async () => { if (status?.installCommand) { await navigator.clipboard.writeText(status.installCommand); toast.success('Install command copied to clipboard!'); } }; const isComplete = status?.installed && status?.auth?.authenticated; return ( Cursor CLI Setup Optional Configure Cursor CLI as an alternative AI provider. You can skip this and use Claude instead, or configure it later in Settings. {/* Installation Status */}
CLI Installation {isChecking ? ( ) : status?.installed ? (
v{status.version}
) : (
Not installed
)}
{!status?.installed && !isChecking && (

Install Cursor CLI to use Cursor models:

{status?.installCommand || 'curl https://cursor.com/install -fsS | bash'}
)}
{/* Authentication Status */} {status?.installed && (
Authentication {status.auth?.authenticated ? (
{status.auth.method === 'api_key' ? 'API Key' : 'Browser Login'}
) : (
Not authenticated
)}
{!status.auth?.authenticated && (

Run the login command in your terminal, then complete authentication in your browser:

{status.loginCommand || 'cursor-agent login'}
)}
)} {/* Action Buttons */}
{/* Info note */}

You can always configure Cursor later in Settings → Providers

); } export default CursorSetupStep; ``` ### Task 6.2: Update Setup View Steps **Status:** `completed` **File:** `apps/ui/src/components/views/setup-view.tsx` Add the Cursor step to the wizard: ```tsx import { CursorSetupStep } from './setup-view/steps/cursor-setup-step'; // Add to steps configuration const SETUP_STEPS = [ // Existing steps... { id: 'claude', title: 'Claude CLI', optional: false, component: ClaudeSetupStep, }, // Add Cursor step { id: 'cursor', title: 'Cursor CLI', optional: true, component: CursorSetupStep, }, // Remaining steps... { id: 'project', title: 'Project', optional: false, component: ProjectSetupStep, }, ]; // In the render function, handle optional steps: function SetupView() { const [currentStep, setCurrentStep] = useState(0); const [skippedSteps, setSkippedSteps] = useState>(new Set()); const handleSkip = (stepId: string) => { setSkippedSteps((prev) => new Set([...prev, stepId])); setCurrentStep((prev) => prev + 1); }; const handleComplete = () => { setCurrentStep((prev) => prev + 1); }; const step = SETUP_STEPS[currentStep]; const StepComponent = step.component; return (
{/* Progress indicator */}
{SETUP_STEPS.map((s, i) => (
))}
{/* Step title */}

{step.title} {step.optional && (Optional)}

{/* Step component */} handleSkip(step.id)} />
); } ``` ### Task 6.3: Add Step Indicator for Optional Steps **Status:** `completed` Add visual indicator for optional vs required steps in the progress bar. --- ## Verification ### Test 1: Component Rendering 1. Start the app with a fresh setup (or clear setup state) 2. Navigate through setup steps 3. Verify Cursor step appears after Claude step 4. Verify "Optional" badge is displayed ### Test 2: Skip Functionality 1. Click "Skip for now" on Cursor step 2. Verify step is skipped and progress continues 3. Verify skipped state is persisted (if applicable) ### Test 3: Installation Detection 1. With cursor-agent NOT installed: - Should show "Not installed" status - Should show install command - Continue button should be disabled 2. With cursor-agent installed but not authenticated: - Should show version number - Should show "Not authenticated" status - Should show login instructions 3. With cursor-agent installed and authenticated: - Should show green checkmarks - Continue button should be enabled ### Test 4: Login Flow 1. Click "Copy Command & Wait for Login" 2. Verify command is copied to clipboard 3. Run login command in terminal 4. Verify status updates after authentication 5. Verify success toast appears ### Test 5: Refresh Status 1. Click refresh button 2. Verify loading state is shown 3. Verify status is re-fetched --- ## Verification Checklist Before marking this phase complete: - [ ] CursorSetupStep component renders correctly - [ ] Step appears in setup wizard flow - [ ] Skip button works and progresses to next step - [ ] Installation status is correctly detected - [ ] Authentication status is correctly detected - [ ] Login command copy works - [ ] Polling for auth status works - [ ] Refresh button updates status - [ ] Error states handled gracefully - [ ] Progress indicator shows optional step differently --- ## Files Changed | File | Action | Description | | --------------------------------------------------------------------- | ------ | -------------------- | | `apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx` | Create | Setup step component | | `apps/ui/src/components/views/setup-view.tsx` | Modify | Add step to wizard | --- ## Design Notes - The step is marked as optional with a badge - Skip button is always available for optional steps - The login flow is asynchronous with polling - Status can be manually refreshed - Error states show clear recovery instructions