diff --git a/apps/ui/src/components/views/setup-view.tsx b/apps/ui/src/components/views/setup-view.tsx index 051bcd12..8764630f 100644 --- a/apps/ui/src/components/views/setup-view.tsx +++ b/apps/ui/src/components/views/setup-view.tsx @@ -5,6 +5,7 @@ import { ThemeStep, CompleteStep, ClaudeSetupStep, + CursorSetupStep, GitHubSetupStep, } from './setup-view/steps'; import { useNavigate } from '@tanstack/react-router'; @@ -14,12 +15,13 @@ export function SetupView() { const { currentStep, setCurrentStep, completeSetup, setSkipClaudeSetup } = useSetupStore(); const navigate = useNavigate(); - const steps = ['welcome', 'theme', 'claude', 'github', 'complete'] as const; + const steps = ['welcome', 'theme', 'claude', 'cursor', 'github', 'complete'] as const; type StepName = (typeof steps)[number]; const getStepName = (): StepName => { if (currentStep === 'claude_detect' || currentStep === 'claude_auth') return 'claude'; if (currentStep === 'welcome') return 'welcome'; if (currentStep === 'theme') return 'theme'; + if (currentStep === 'cursor') return 'cursor'; if (currentStep === 'github') return 'github'; return 'complete'; }; @@ -37,6 +39,10 @@ export function SetupView() { setCurrentStep('claude_detect'); break; case 'claude': + console.log('[Setup Flow] Moving to cursor step'); + setCurrentStep('cursor'); + break; + case 'cursor': console.log('[Setup Flow] Moving to github step'); setCurrentStep('github'); break; @@ -56,15 +62,23 @@ export function SetupView() { case 'claude': setCurrentStep('theme'); break; - case 'github': + case 'cursor': setCurrentStep('claude_detect'); break; + case 'github': + setCurrentStep('cursor'); + break; } }; const handleSkipClaude = () => { console.log('[Setup Flow] Skipping Claude setup'); setSkipClaudeSetup(true); + setCurrentStep('cursor'); + }; + + const handleSkipCursor = () => { + console.log('[Setup Flow] Skipping Cursor setup'); setCurrentStep('github'); }; @@ -114,6 +128,14 @@ export function SetupView() { /> )} + {currentStep === 'cursor' && ( + handleNext('cursor')} + onBack={() => handleBack('cursor')} + onSkip={handleSkipCursor} + /> + )} + {currentStep === 'github' && ( handleNext('github')} diff --git a/apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx b/apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx new file mode 100644 index 00000000..ab9df67e --- /dev/null +++ b/apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx @@ -0,0 +1,368 @@ +import { useState, useEffect, useCallback, useRef } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { useSetupStore } from '@/store/setup-store'; +import { getElectronAPI } from '@/lib/electron'; +import { + CheckCircle2, + Loader2, + ArrowRight, + ArrowLeft, + ExternalLink, + Copy, + RefreshCw, + AlertTriangle, + Terminal, + XCircle, +} from 'lucide-react'; +import { toast } from 'sonner'; +import { StatusBadge } from '../components'; + +interface CursorSetupStepProps { + onNext: () => void; + onBack: () => void; + onSkip: () => void; +} + +interface CursorCliStatus { + installed: boolean; + version?: string | null; + path?: string | null; + auth?: { + authenticated: boolean; + method: string; + }; + installCommand?: string; + loginCommand?: string; +} + +export function CursorSetupStep({ onNext, onBack, onSkip }: CursorSetupStepProps) { + const { cursorCliStatus, setCursorCliStatus } = useSetupStore(); + const [isChecking, setIsChecking] = useState(false); + const [isLoggingIn, setIsLoggingIn] = useState(false); + const pollIntervalRef = useRef(null); + + const checkStatus = useCallback(async () => { + setIsChecking(true); + try { + const api = getElectronAPI(); + if (!api.setup?.getCursorStatus) { + return; + } + const result = await api.setup.getCursorStatus(); + if (result.success) { + const status: CursorCliStatus = { + installed: result.installed ?? false, + version: result.version, + path: result.path, + auth: result.auth, + installCommand: result.installCommand, + loginCommand: result.loginCommand, + }; + setCursorCliStatus(status); + + if (result.auth?.authenticated) { + toast.success('Cursor CLI is ready!'); + } + } + } catch (error) { + console.error('Failed to check Cursor status:', error); + } finally { + setIsChecking(false); + } + }, [setCursorCliStatus]); + + useEffect(() => { + checkStatus(); + // Cleanup polling on unmount + return () => { + if (pollIntervalRef.current) { + clearInterval(pollIntervalRef.current); + } + }; + }, [checkStatus]); + + const copyCommand = (command: string) => { + navigator.clipboard.writeText(command); + toast.success('Command copied to clipboard'); + }; + + const handleLogin = async () => { + setIsLoggingIn(true); + + try { + // Copy login command to clipboard and show instructions + const loginCommand = cursorCliStatus?.loginCommand || 'cursor-agent login'; + await navigator.clipboard.writeText(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 + + pollIntervalRef.current = setInterval(async () => { + attempts++; + + try { + const api = getElectronAPI(); + if (!api.setup?.getCursorStatus) { + return; + } + const result = await api.setup.getCursorStatus(); + + if (result.auth?.authenticated) { + if (pollIntervalRef.current) { + clearInterval(pollIntervalRef.current); + pollIntervalRef.current = null; + } + setCursorCliStatus({ + ...cursorCliStatus, + installed: result.installed ?? true, + version: result.version, + path: result.path, + auth: result.auth, + } as CursorCliStatus); + setIsLoggingIn(false); + toast.success('Successfully logged in to Cursor!'); + } + } catch { + // Ignore polling errors + } + + if (attempts >= maxAttempts) { + if (pollIntervalRef.current) { + clearInterval(pollIntervalRef.current); + pollIntervalRef.current = null; + } + 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 isReady = cursorCliStatus?.installed && cursorCliStatus?.auth?.authenticated; + + const getStatusBadge = () => { + if (isChecking) { + return ; + } + if (cursorCliStatus?.auth?.authenticated) { + return ; + } + if (cursorCliStatus?.installed) { + return ; + } + return ; + }; + + return ( +
+
+
+ +
+

Cursor CLI Setup

+

Optional - Use Cursor as an AI provider

+
+ + {/* Info Banner */} + + +
+ +
+

This step is optional

+

+ Configure Cursor CLI as an alternative AI provider. You can skip this and use Claude + instead, or configure it later in Settings. +

+
+
+
+
+ + {/* Status Card */} + + +
+ + + Cursor CLI Status + + Optional + + +
+ {getStatusBadge()} + +
+
+ + {cursorCliStatus?.installed + ? cursorCliStatus.auth?.authenticated + ? `Authenticated via ${cursorCliStatus.auth.method === 'api_key' ? 'API Key' : 'Browser Login'}${cursorCliStatus.version ? ` (v${cursorCliStatus.version})` : ''}` + : 'Installed but not authenticated' + : 'Not installed on your system'} + +
+ + {/* Success State */} + {isReady && ( +
+ +
+

Cursor CLI is ready!

+

+ You can use Cursor models for AI tasks. + {cursorCliStatus?.version && ( + Version: {cursorCliStatus.version} + )} +

+
+
+ )} + + {/* Not Installed */} + {!cursorCliStatus?.installed && !isChecking && ( +
+
+ +
+

Cursor CLI not found

+

+ Install the Cursor CLI to use Cursor models. +

+
+
+ +
+

Install Cursor CLI:

+
+ + {cursorCliStatus?.installCommand || + 'curl https://cursor.com/install -fsS | bash'} + + +
+ + View installation docs + + +
+
+ )} + + {/* Installed but not authenticated */} + {cursorCliStatus?.installed && !cursorCliStatus?.auth?.authenticated && !isChecking && ( +
+
+ +
+

Cursor CLI not authenticated

+

+ Run the login command to authenticate with Cursor. +

+
+
+ +
+

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

+
+ + {cursorCliStatus?.loginCommand || 'cursor-agent login'} + + +
+ +
+
+ )} + + {/* Loading State */} + {isChecking && ( +
+ +
+

Checking Cursor CLI status...

+
+
+ )} +
+
+ + {/* Navigation */} +
+ +
+ + +
+
+ + {/* Info note */} +

+ You can always configure Cursor later in Settings +

+
+ ); +} diff --git a/apps/ui/src/components/views/setup-view/steps/index.ts b/apps/ui/src/components/views/setup-view/steps/index.ts index 28bf064c..8293eda1 100644 --- a/apps/ui/src/components/views/setup-view/steps/index.ts +++ b/apps/ui/src/components/views/setup-view/steps/index.ts @@ -3,4 +3,5 @@ export { WelcomeStep } from './welcome-step'; export { ThemeStep } from './theme-step'; export { CompleteStep } from './complete-step'; export { ClaudeSetupStep } from './claude-setup-step'; +export { CursorSetupStep } from './cursor-setup-step'; export { GitHubSetupStep } from './github-setup-step'; diff --git a/apps/ui/src/lib/electron.ts b/apps/ui/src/lib/electron.ts index 698f915e..be3cd7bf 100644 --- a/apps/ui/src/lib/electron.ts +++ b/apps/ui/src/lib/electron.ts @@ -551,6 +551,19 @@ export interface ElectronAPI { user: string | null; error?: string; }>; + getCursorStatus?: () => Promise<{ + success: boolean; + installed?: boolean; + version?: string | null; + path?: string | null; + auth?: { + authenticated: boolean; + method: string; + }; + installCommand?: string; + loginCommand?: string; + error?: string; + }>; onInstallProgress?: (callback: (progress: any) => void) => () => void; onAuthProgress?: (callback: (progress: any) => void) => () => void; }; diff --git a/apps/ui/src/store/setup-store.ts b/apps/ui/src/store/setup-store.ts index e345ac91..f52a21d2 100644 --- a/apps/ui/src/store/setup-store.ts +++ b/apps/ui/src/store/setup-store.ts @@ -20,6 +20,20 @@ export interface GhCliStatus { error?: string; } +// Cursor CLI Status +export interface CursorCliStatus { + installed: boolean; + version?: string | null; + path?: string | null; + auth?: { + authenticated: boolean; + method: string; + }; + installCommand?: string; + loginCommand?: string; + error?: string; +} + // Claude Auth Method - all possible authentication sources export type ClaudeAuthMethod = | 'oauth_token_env' @@ -56,6 +70,7 @@ export type SetupStep = | 'theme' | 'claude_detect' | 'claude_auth' + | 'cursor' | 'github' | 'complete'; @@ -73,6 +88,9 @@ export interface SetupState { // GitHub CLI state ghCliStatus: GhCliStatus | null; + // Cursor CLI state + cursorCliStatus: CursorCliStatus | null; + // Setup preferences skipClaudeSetup: boolean; } @@ -94,6 +112,9 @@ export interface SetupActions { // GitHub CLI setGhCliStatus: (status: GhCliStatus | null) => void; + // Cursor CLI + setCursorCliStatus: (status: CursorCliStatus | null) => void; + // Preferences setSkipClaudeSetup: (skip: boolean) => void; } @@ -118,6 +139,7 @@ const initialState: SetupState = { claudeInstallProgress: { ...initialInstallProgress }, ghCliStatus: null, + cursorCliStatus: null, skipClaudeSetup: shouldSkipSetup, }; @@ -167,6 +189,9 @@ export const useSetupStore = create()( // GitHub CLI setGhCliStatus: (status) => set({ ghCliStatus: status }), + // Cursor CLI + setCursorCliStatus: (status) => set({ cursorCliStatus: status }), + // Preferences setSkipClaudeSetup: (skip) => set({ skipClaudeSetup: skip }), }), diff --git a/plan/cursor-cli-integration/README.md b/plan/cursor-cli-integration/README.md index 1f26cc3b..6fad613f 100644 --- a/plan/cursor-cli-integration/README.md +++ b/plan/cursor-cli-integration/README.md @@ -12,7 +12,7 @@ | 3 | [Provider Factory Integration](phases/phase-3-factory.md) | `completed` | ✅ | | 4 | [Setup Routes & Status Endpoints](phases/phase-4-routes.md) | `completed` | ✅ | | 5 | [Log Parser Integration](phases/phase-5-log-parser.md) | `completed` | ✅ | -| 6 | [UI Setup Wizard](phases/phase-6-setup-wizard.md) | `pending` | - | +| 6 | [UI Setup Wizard](phases/phase-6-setup-wizard.md) | `completed` | ✅ | | 7 | [Settings View Provider Tabs](phases/phase-7-settings.md) | `pending` | - | | 8 | [AI Profiles Integration](phases/phase-8-profiles.md) | `pending` | - | | 9 | [Task Execution Integration](phases/phase-9-execution.md) | `pending` | - | diff --git a/plan/cursor-cli-integration/phases/phase-6-setup-wizard.md b/plan/cursor-cli-integration/phases/phase-6-setup-wizard.md index 453fcbf4..3e7e00b7 100644 --- a/plan/cursor-cli-integration/phases/phase-6-setup-wizard.md +++ b/plan/cursor-cli-integration/phases/phase-6-setup-wizard.md @@ -1,6 +1,6 @@ # Phase 6: UI Setup Wizard -**Status:** `pending` +**Status:** `completed` **Dependencies:** Phase 4 (Routes) **Estimated Effort:** Medium (React component) @@ -16,7 +16,7 @@ Add an optional Cursor CLI setup step to the welcome wizard, allowing users to c ### Task 6.1: Create Cursor Setup Step Component -**Status:** `pending` +**Status:** `completed` **File:** `apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx` @@ -282,7 +282,7 @@ export default CursorSetupStep; ### Task 6.2: Update Setup View Steps -**Status:** `pending` +**Status:** `completed` **File:** `apps/ui/src/components/views/setup-view.tsx` @@ -369,7 +369,7 @@ function SetupView() { ### Task 6.3: Add Step Indicator for Optional Steps -**Status:** `pending` +**Status:** `completed` Add visual indicator for optional vs required steps in the progress bar.