diff --git a/apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx b/apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx
index 0b85c985..54bcc392 100644
--- a/apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx
+++ b/apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx
@@ -1,9 +1,35 @@
import { Label } from '@/components/ui/label';
import { Brain, UserCircle } from 'lucide-react';
import { cn } from '@/lib/utils';
-import { AgentModel, ThinkingLevel, AIProfile } from '@/store/app-store';
+import type { AgentModel, ThinkingLevel, AIProfile } from '@automaker/types';
+import { CURSOR_MODEL_MAP, profileHasThinking } from '@automaker/types';
import { PROFILE_ICONS } from './model-constants';
+/**
+ * Get display string for a profile's model configuration
+ */
+function getProfileModelDisplay(profile: AIProfile): string {
+ if (profile.provider === 'cursor') {
+ const cursorModel = profile.cursorModel || 'auto';
+ const modelConfig = CURSOR_MODEL_MAP[cursorModel];
+ return modelConfig?.label || cursorModel;
+ }
+ // Claude
+ return profile.model || 'sonnet';
+}
+
+/**
+ * Get display string for a profile's thinking configuration
+ */
+function getProfileThinkingDisplay(profile: AIProfile): string | null {
+ if (profile.provider === 'cursor') {
+ // For Cursor, thinking is embedded in the model
+ return profileHasThinking(profile) ? 'thinking' : null;
+ }
+ // Claude
+ return profile.thinkingLevel && profile.thinkingLevel !== 'none' ? profile.thinkingLevel : null;
+}
+
interface ProfileQuickSelectProps {
profiles: AIProfile[];
selectedModel: AgentModel;
@@ -23,7 +49,11 @@ export function ProfileQuickSelect({
showManageLink = false,
onManageLinkClick,
}: ProfileQuickSelectProps) {
- if (profiles.length === 0) {
+ // Filter to only Claude profiles for now - Cursor profiles will be supported
+ // when features support provider selection (Phase 9)
+ const claudeProfiles = profiles.filter((p) => p.provider === 'claude');
+
+ if (claudeProfiles.length === 0) {
return null;
}
@@ -39,7 +69,7 @@ export function ProfileQuickSelect({
- {profiles.slice(0, 6).map((profile) => {
+ {claudeProfiles.slice(0, 6).map((profile) => {
const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : Brain;
const isSelected =
selectedModel === profile.model && selectedThinkingLevel === profile.thinkingLevel;
@@ -47,7 +77,7 @@ export function ProfileQuickSelect({
diff --git a/apps/ui/src/components/views/profiles-view/components/profile-form.tsx b/apps/ui/src/components/views/profiles-view/components/profile-form.tsx
index 5cc8c4b1..370e7cd2 100644
--- a/apps/ui/src/components/views/profiles-view/components/profile-form.tsx
+++ b/apps/ui/src/components/views/profiles-view/components/profile-form.tsx
@@ -4,13 +4,20 @@ import { HotkeyButton } from '@/components/ui/hotkey-button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
+import { Badge } from '@/components/ui/badge';
import { cn, modelSupportsThinking } from '@/lib/utils';
import { DialogFooter } from '@/components/ui/dialog';
-import { Brain } from 'lucide-react';
+import { Brain, Bot, Terminal } from 'lucide-react';
import { toast } from 'sonner';
-import type { AIProfile, AgentModel, ThinkingLevel } from '@/store/app-store';
+import type {
+ AIProfile,
+ AgentModel,
+ ThinkingLevel,
+ ModelProvider,
+ CursorModelId,
+} from '@automaker/types';
+import { CURSOR_MODEL_MAP, cursorModelHasThinking } from '@automaker/types';
import { CLAUDE_MODELS, THINKING_LEVELS, ICON_OPTIONS } from '../constants';
-import { getProviderFromModel } from '../utils';
interface ProfileFormProps {
profile: Partial
;
@@ -30,13 +37,27 @@ export function ProfileForm({
const [formData, setFormData] = useState({
name: profile.name || '',
description: profile.description || '',
- model: profile.model || ('opus' as AgentModel),
+ provider: (profile.provider || 'claude') as ModelProvider,
+ // Claude-specific
+ model: profile.model || ('sonnet' as AgentModel),
thinkingLevel: profile.thinkingLevel || ('none' as ThinkingLevel),
+ // Cursor-specific
+ cursorModel: profile.cursorModel || ('auto' as CursorModelId),
icon: profile.icon || 'Brain',
});
- const provider = getProviderFromModel(formData.model);
- const supportsThinking = modelSupportsThinking(formData.model);
+ const supportsThinking = formData.provider === 'claude' && modelSupportsThinking(formData.model);
+
+ const handleProviderChange = (provider: ModelProvider) => {
+ setFormData({
+ ...formData,
+ provider,
+ // Reset to defaults when switching providers
+ model: provider === 'claude' ? 'sonnet' : formData.model,
+ thinkingLevel: provider === 'claude' ? 'none' : formData.thinkingLevel,
+ cursorModel: provider === 'cursor' ? 'auto' : formData.cursorModel,
+ });
+ };
const handleModelChange = (model: AgentModel) => {
setFormData({
@@ -45,21 +66,39 @@ export function ProfileForm({
});
};
+ const handleCursorModelChange = (cursorModel: CursorModelId) => {
+ setFormData({
+ ...formData,
+ cursorModel,
+ });
+ };
+
const handleSubmit = () => {
if (!formData.name.trim()) {
toast.error('Please enter a profile name');
return;
}
- onSave({
+ const baseProfile = {
name: formData.name.trim(),
description: formData.description.trim(),
- model: formData.model,
- thinkingLevel: supportsThinking ? formData.thinkingLevel : 'none',
- provider,
+ provider: formData.provider,
isBuiltIn: false,
icon: formData.icon,
- });
+ };
+
+ if (formData.provider === 'cursor') {
+ onSave({
+ ...baseProfile,
+ cursorModel: formData.cursorModel,
+ });
+ } else {
+ onSave({
+ ...baseProfile,
+ model: formData.model,
+ thinkingLevel: supportsThinking ? formData.thinkingLevel : 'none',
+ });
+ }
};
return (
@@ -113,34 +152,128 @@ export function ProfileForm({
- {/* Model Selection */}
+ {/* Provider Selection */}
-
-
- {CLAUDE_MODELS.map(({ id, label }) => (
-
- ))}
+
+
+
+
- {/* Thinking Level */}
- {supportsThinking && (
+ {/* Claude Model Selection */}
+ {formData.provider === 'claude' && (
+
+
+
+ {CLAUDE_MODELS.map(({ id, label }) => (
+
+ ))}
+
+
+ )}
+
+ {/* Cursor Model Selection */}
+ {formData.provider === 'cursor' && (
+
+
+
+ {Object.entries(CURSOR_MODEL_MAP).map(([id, config]) => (
+
+ ))}
+
+ {formData.cursorModel && cursorModelHasThinking(formData.cursorModel) && (
+
+ This model has built-in extended thinking capabilities.
+
+ )}
+
+ )}
+
+ {/* Claude Thinking Level */}
+ {formData.provider === 'claude' && supportsThinking && (
{profile.description}
-
- {profile.model}
+ {/* Provider badge */}
+
+ {profile.provider === 'cursor' ? (
+
+ ) : (
+
+ )}
+ {profile.provider === 'cursor' ? 'Cursor' : 'Claude'}
- {profile.thinkingLevel !== 'none' && (
+
+ {/* Model badge */}
+
+ {profile.provider === 'cursor'
+ ? CURSOR_MODEL_MAP[profile.cursorModel || 'auto']?.label ||
+ profile.cursorModel ||
+ 'auto'
+ : profile.model || 'sonnet'}
+
+
+ {/* Thinking badge - works for both providers */}
+ {profileHasThinking(profile) && (
- {profile.thinkingLevel}
+ {profile.provider === 'cursor' ? 'Thinking' : profile.thinkingLevel}
)}
diff --git a/apps/ui/src/components/views/profiles-view/utils.ts b/apps/ui/src/components/views/profiles-view/utils.ts
index d6a9ce3e..f464a8d0 100644
--- a/apps/ui/src/components/views/profiles-view/utils.ts
+++ b/apps/ui/src/components/views/profiles-view/utils.ts
@@ -1,6 +1,48 @@
-import type { AgentModel, ModelProvider } from '@/store/app-store';
+import type { AgentModel, ModelProvider, AIProfile } from '@automaker/types';
+import { CURSOR_MODEL_MAP } from '@automaker/types';
-// Helper to determine provider from model
+// Helper to determine provider from model (legacy, always returns 'claude')
export function getProviderFromModel(model: AgentModel): ModelProvider {
return 'claude';
}
+
+/**
+ * Validate an AI profile for completeness and correctness
+ */
+export function validateProfile(profile: Partial
): {
+ valid: boolean;
+ errors: string[];
+} {
+ const errors: string[] = [];
+
+ // Name is required
+ if (!profile.name?.trim()) {
+ errors.push('Profile name is required');
+ }
+
+ // Provider must be valid
+ if (!profile.provider || !['claude', 'cursor'].includes(profile.provider)) {
+ errors.push('Invalid provider');
+ }
+
+ // Claude-specific validation
+ if (profile.provider === 'claude') {
+ if (!profile.model) {
+ errors.push('Claude model is required');
+ } else if (!['haiku', 'sonnet', 'opus'].includes(profile.model)) {
+ errors.push('Invalid Claude model');
+ }
+ }
+
+ // Cursor-specific validation
+ if (profile.provider === 'cursor') {
+ if (profile.cursorModel && !(profile.cursorModel in CURSOR_MODEL_MAP)) {
+ errors.push('Invalid Cursor model');
+ }
+ }
+
+ return {
+ valid: errors.length === 0,
+ errors,
+ };
+}
diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts
index 874e1a6d..5bb5b3d9 100644
--- a/apps/ui/src/store/app-store.ts
+++ b/apps/ui/src/store/app-store.ts
@@ -859,6 +859,7 @@ export interface AppActions {
// Default built-in AI profiles
const DEFAULT_AI_PROFILES: AIProfile[] = [
+ // Claude profiles
{
id: 'profile-heavy-task',
name: 'Heavy Task',
@@ -890,6 +891,34 @@ const DEFAULT_AI_PROFILES: AIProfile[] = [
isBuiltIn: true,
icon: 'Zap',
},
+ // Cursor profiles
+ {
+ id: 'profile-cursor-auto',
+ name: 'Cursor Auto',
+ description: 'Let Cursor choose the best model automatically.',
+ provider: 'cursor',
+ cursorModel: 'auto',
+ isBuiltIn: true,
+ icon: 'Sparkles',
+ },
+ {
+ id: 'profile-cursor-fast',
+ name: 'Cursor Fast',
+ description: 'Quick responses with GPT-4o Mini via Cursor.',
+ provider: 'cursor',
+ cursorModel: 'gpt-4o-mini',
+ isBuiltIn: true,
+ icon: 'Zap',
+ },
+ {
+ id: 'profile-cursor-thinking',
+ name: 'Cursor Thinking',
+ description: 'Claude Sonnet 4 with extended thinking via Cursor for complex tasks.',
+ provider: 'cursor',
+ cursorModel: 'claude-sonnet-4-thinking',
+ isBuiltIn: true,
+ icon: 'Brain',
+ },
];
const initialState: AppState = {
diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts
index 5c97dfbc..20523071 100644
--- a/libs/types/src/index.ts
+++ b/libs/types/src/index.ts
@@ -71,6 +71,8 @@ export {
SETTINGS_VERSION,
CREDENTIALS_VERSION,
PROJECT_SETTINGS_VERSION,
+ profileHasThinking,
+ getProfileModelString,
} from './settings.js';
// Model display constants
diff --git a/libs/types/src/settings.ts b/libs/types/src/settings.ts
index 40d1e04a..ade96ac3 100644
--- a/libs/types/src/settings.ts
+++ b/libs/types/src/settings.ts
@@ -7,6 +7,8 @@
*/
import type { AgentModel } from './model.js';
+import type { CursorModelId } from './cursor-models.js';
+import { CURSOR_MODEL_MAP } from './cursor-models.js';
// Re-export AgentModel for convenience
export type { AgentModel };
@@ -151,16 +153,54 @@ export interface AIProfile {
name: string;
/** User-friendly description */
description: string;
- /** Which Claude model to use (opus, sonnet, haiku) */
- model: AgentModel;
- /** Extended thinking level for reasoning-based tasks */
- thinkingLevel: ThinkingLevel;
- /** Provider (currently only "claude") */
+ /** Provider selection: 'claude' or 'cursor' */
provider: ModelProvider;
/** Whether this is a built-in default profile */
isBuiltIn: boolean;
/** Optional icon identifier or emoji */
icon?: string;
+
+ // Claude-specific settings
+ /** Which Claude model to use (opus, sonnet, haiku) - only for Claude provider */
+ model?: AgentModel;
+ /** Extended thinking level for reasoning-based tasks - only for Claude provider */
+ thinkingLevel?: ThinkingLevel;
+
+ // Cursor-specific settings
+ /** Which Cursor model to use - only for Cursor provider
+ * Note: For Cursor, thinking is embedded in the model ID (e.g., 'claude-sonnet-4-thinking')
+ */
+ cursorModel?: CursorModelId;
+}
+
+/**
+ * Helper to determine if a profile uses thinking mode
+ */
+export function profileHasThinking(profile: AIProfile): boolean {
+ if (profile.provider === 'claude') {
+ return profile.thinkingLevel !== undefined && profile.thinkingLevel !== 'none';
+ }
+
+ if (profile.provider === 'cursor') {
+ const model = profile.cursorModel || 'auto';
+ // Check using model map for hasThinking flag, or check for 'thinking' in name
+ const modelConfig = CURSOR_MODEL_MAP[model];
+ return modelConfig?.hasThinking ?? false;
+ }
+
+ return false;
+}
+
+/**
+ * Get effective model string for execution
+ */
+export function getProfileModelString(profile: AIProfile): string {
+ if (profile.provider === 'cursor') {
+ return `cursor:${profile.cursorModel || 'auto'}`;
+ }
+
+ // Claude
+ return profile.model || 'sonnet';
}
/**
diff --git a/plan/cursor-cli-integration/README.md b/plan/cursor-cli-integration/README.md
index 4e1fd7a1..ebf2b51b 100644
--- a/plan/cursor-cli-integration/README.md
+++ b/plan/cursor-cli-integration/README.md
@@ -14,7 +14,7 @@
| 5 | [Log Parser Integration](phases/phase-5-log-parser.md) | `completed` | ✅ |
| 6 | [UI Setup Wizard](phases/phase-6-setup-wizard.md) | `completed` | ✅ |
| 7 | [Settings View Provider Tabs](phases/phase-7-settings.md) | `completed` | ✅ |
-| 8 | [AI Profiles Integration](phases/phase-8-profiles.md) | `pending` | - |
+| 8 | [AI Profiles Integration](phases/phase-8-profiles.md) | `completed` | ✅ |
| 9 | [Task Execution Integration](phases/phase-9-execution.md) | `pending` | - |
| 10 | [Testing & Validation](phases/phase-10-testing.md) | `pending` | - |
diff --git a/plan/cursor-cli-integration/phases/phase-8-profiles.md b/plan/cursor-cli-integration/phases/phase-8-profiles.md
index 22173301..69fa900b 100644
--- a/plan/cursor-cli-integration/phases/phase-8-profiles.md
+++ b/plan/cursor-cli-integration/phases/phase-8-profiles.md
@@ -1,6 +1,6 @@
# Phase 8: AI Profiles Integration
-**Status:** `pending`
+**Status:** `completed`
**Dependencies:** Phase 1 (Types), Phase 7 (Settings)
**Estimated Effort:** Medium (UI + types)
@@ -31,7 +31,7 @@ Extend the AI Profiles system to support Cursor as a provider, with proper handl
### Task 8.1: Update AIProfile Type
-**Status:** `pending`
+**Status:** `completed`
**File:** `libs/types/src/settings.ts`
@@ -93,7 +93,7 @@ export function getProfileModelString(profile: AIProfile): string {
### Task 8.2: Update Profile Form Component
-**Status:** `pending`
+**Status:** `completed`
**File:** `apps/ui/src/components/views/profiles-view/components/profile-form.tsx`
@@ -289,7 +289,7 @@ export function ProfileForm({ profile, onSave, onCancel }: ProfileFormProps) {
### Task 8.3: Update Profile Card Display
-**Status:** `pending`
+**Status:** `completed`
**File:** `apps/ui/src/components/views/profiles-view/components/profile-card.tsx`
@@ -374,7 +374,7 @@ export function ProfileCard({ profile, onEdit, onDelete }: ProfileCardProps) {
### Task 8.4: Add Default Cursor Profiles
-**Status:** `pending`
+**Status:** `completed`
**File:** `apps/ui/src/components/views/profiles-view/constants.ts`
@@ -430,7 +430,7 @@ export const DEFAULT_PROFILES: AIProfile[] = [
### Task 8.5: Update Profile Validation
-**Status:** `pending`
+**Status:** `completed`
Add validation for profile data: