mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 14:22:02 +00:00
- Introduced a comprehensive integration plan for the Cursor CLI, including detailed phases for implementation. - Created initial markdown files for each phase, outlining objectives, tasks, and verification steps. - Established a global prompt template for starting new sessions with the Cursor CLI. - Added necessary types and configuration for Cursor models and their integration into the AutoMaker architecture. - Implemented routing logic to ensure proper model handling between Cursor and Claude providers. - Developed UI components for setup and settings management related to Cursor integration. - Included extensive testing and validation plans to ensure robust functionality across all scenarios.
14 KiB
14 KiB
Phase 6: UI Setup Wizard
Status: pending
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: pending
File: apps/ui/src/components/views/setup-view/steps/cursor-setup-step.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<CliStatus | null>(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 (
<Card className="w-full max-w-2xl mx-auto">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Terminal className="w-5 h-5" />
Cursor CLI Setup
<Badge variant="outline" className="ml-2">
Optional
</Badge>
</CardTitle>
<CardDescription>
Configure Cursor CLI as an alternative AI provider. You can skip this and use Claude
instead, or configure it later in Settings.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Installation Status */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">CLI Installation</span>
{isChecking ? (
<Loader2 className="w-4 h-4 animate-spin text-muted-foreground" />
) : status?.installed ? (
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<CheckCircle2 className="w-4 h-4" />
<span className="text-xs">v{status.version}</span>
</div>
) : (
<div className="flex items-center gap-2 text-amber-600 dark:text-amber-400">
<XCircle className="w-4 h-4" />
<span className="text-xs">Not installed</span>
</div>
)}
</div>
{!status?.installed && !isChecking && (
<Alert>
<AlertDescription className="text-sm space-y-3">
<p>Install Cursor CLI to use Cursor models:</p>
<div className="flex items-center gap-2">
<code className="flex-1 bg-muted p-2 rounded text-xs font-mono overflow-x-auto">
{status?.installCommand || 'curl https://cursor.com/install -fsS | bash'}
</code>
<Button variant="outline" size="sm" onClick={handleCopyInstallCommand}>
Copy
</Button>
</div>
<Button
variant="link"
size="sm"
className="p-0 h-auto"
onClick={() => window.open('https://cursor.com/docs/cli', '_blank')}
>
View installation docs
<ExternalLink className="w-3 h-3 ml-1" />
</Button>
</AlertDescription>
</Alert>
)}
</div>
{/* Authentication Status */}
{status?.installed && (
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Authentication</span>
{status.auth?.authenticated ? (
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<CheckCircle2 className="w-4 h-4" />
<span className="text-xs capitalize">
{status.auth.method === 'api_key' ? 'API Key' : 'Browser Login'}
</span>
</div>
) : (
<div className="flex items-center gap-2 text-amber-600 dark:text-amber-400">
<XCircle className="w-4 h-4" />
<span className="text-xs">Not authenticated</span>
</div>
)}
</div>
{!status.auth?.authenticated && (
<div className="space-y-3">
<p className="text-sm text-muted-foreground">
Run the login command in your terminal, then complete authentication in your
browser:
</p>
<div className="flex items-center gap-2">
<code className="flex-1 bg-muted p-2 rounded text-xs font-mono">
{status.loginCommand || 'cursor-agent login'}
</code>
</div>
<Button onClick={handleLogin} disabled={isLoggingIn} className="w-full">
{isLoggingIn ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Waiting for login...
</>
) : (
'Copy Command & Wait for Login'
)}
</Button>
</div>
)}
</div>
)}
{/* Action Buttons */}
<div className="flex gap-3 pt-4 border-t">
<Button variant="outline" onClick={onSkip} className="flex-1">
Skip for now
</Button>
<Button
onClick={onComplete}
disabled={!isComplete && status?.installed}
className="flex-1"
>
{isComplete ? 'Continue' : 'Complete setup to continue'}
</Button>
<Button
variant="ghost"
size="icon"
onClick={checkStatus}
disabled={isChecking}
title="Refresh status"
>
<RefreshCw className={`w-4 h-4 ${isChecking ? 'animate-spin' : ''}`} />
</Button>
</div>
{/* Info note */}
<p className="text-xs text-muted-foreground text-center">
You can always configure Cursor later in Settings → Providers
</p>
</CardContent>
</Card>
);
}
export default CursorSetupStep;
Task 6.2: Update Setup View Steps
Status: pending
File: apps/ui/src/components/views/setup-view.tsx
Add the Cursor step to the wizard:
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<Set<string>>(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 (
<div className="setup-view">
{/* Progress indicator */}
<div className="flex gap-2 mb-6">
{SETUP_STEPS.map((s, i) => (
<div
key={s.id}
className={cn(
'flex-1 h-2 rounded',
i < currentStep
? 'bg-green-500'
: i === currentStep
? 'bg-blue-500'
: skippedSteps.has(s.id)
? 'bg-gray-300'
: 'bg-gray-200'
)}
/>
))}
</div>
{/* Step title */}
<h2 className="text-xl font-semibold mb-4">
{step.title}
{step.optional && <span className="text-sm text-muted-foreground ml-2">(Optional)</span>}
</h2>
{/* Step component */}
<StepComponent onComplete={handleComplete} onSkip={() => handleSkip(step.id)} />
</div>
);
}
Task 6.3: Add Step Indicator for Optional Steps
Status: pending
Add visual indicator for optional vs required steps in the progress bar.
Verification
Test 1: Component Rendering
- Start the app with a fresh setup (or clear setup state)
- Navigate through setup steps
- Verify Cursor step appears after Claude step
- Verify "Optional" badge is displayed
Test 2: Skip Functionality
- Click "Skip for now" on Cursor step
- Verify step is skipped and progress continues
- Verify skipped state is persisted (if applicable)
Test 3: Installation Detection
-
With cursor-agent NOT installed:
- Should show "Not installed" status
- Should show install command
- Continue button should be disabled
-
With cursor-agent installed but not authenticated:
- Should show version number
- Should show "Not authenticated" status
- Should show login instructions
-
With cursor-agent installed and authenticated:
- Should show green checkmarks
- Continue button should be enabled
Test 4: Login Flow
- Click "Copy Command & Wait for Login"
- Verify command is copied to clipboard
- Run login command in terminal
- Verify status updates after authentication
- Verify success toast appears
Test 5: Refresh Status
- Click refresh button
- Verify loading state is shown
- 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