diff --git a/.automaker/feature_list.json b/.automaker/feature_list.json index ab138177..b8909b49 100644 --- a/.automaker/feature_list.json +++ b/.automaker/feature_list.json @@ -71,5 +71,22 @@ "startedAt": "2025-12-09T22:31:41.946Z", "imagePaths": [], "skipTests": true + }, + { + "id": "feature-1765321570899-oefrfast6", + "category": "Core", + "description": "I would like to have abbility to set correct model for new feautres. so inteas of only using claude opus we could use other models like sonnet or haiku for easier / light one tasks as well to add abbility how much thinking lvl we wanna use on each task as well", + "steps": [ + "User add new feature", + "User Describe it", + "Select the automated testing or manual one", + "If the task is light / easy to implement he use lighter model from anthropi sdk such as sonnet / haiku", + "agent execute task with correct model " + ], + "status": "verified", + "startedAt": "2025-12-09T23:07:37.223Z", + "imagePaths": [], + "skipTests": false, + "summary": "Added model selection (Haiku/Sonnet/Opus) and thinking level (None/Low/Medium/High) controls to feature creation and edit dialogs. Modified: app-store.ts (added AgentModel and ThinkingLevel types), board-view.tsx (UI controls), feature-executor.js (dynamic model/thinking config), feature-loader.js (field persistence). Agent now executes with user-selected model and extended thinking settings." } ] \ No newline at end of file diff --git a/app/electron/main.js b/app/electron/main.js index 75d45d72..968c442b 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -551,3 +551,21 @@ ipcMain.handle("auto-mode:commit-feature", async (_, { projectPath, featureId }) return { success: false, error: error.message }; } }); + +// ============================================================================ +// Claude CLI Detection IPC Handlers +// ============================================================================ + +/** + * Check Claude Code CLI installation status + */ +ipcMain.handle("claude:check-cli", async () => { + try { + const claudeCliDetector = require("./services/claude-cli-detector"); + const info = claudeCliDetector.getInstallationInfo(); + return { success: true, ...info }; + } catch (error) { + console.error("[IPC] claude:check-cli error:", error); + return { success: false, error: error.message }; + } +}); diff --git a/app/electron/preload.js b/app/electron/preload.js index 1b5f49e0..11819dec 100644 --- a/app/electron/preload.js +++ b/app/electron/preload.js @@ -138,6 +138,9 @@ contextBridge.exposeInMainWorld("electronAPI", { }; }, }, + + // Claude CLI Detection API + checkClaudeCli: () => ipcRenderer.invoke("claude:check-cli"), }); // Also expose a flag to detect if we're in Electron diff --git a/app/electron/services/claude-cli-detector.js b/app/electron/services/claude-cli-detector.js new file mode 100644 index 00000000..31030f0d --- /dev/null +++ b/app/electron/services/claude-cli-detector.js @@ -0,0 +1,119 @@ +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +class ClaudeCliDetector { + /** + * Check if Claude Code CLI is installed and accessible + * @returns {Object} { installed: boolean, path: string|null, version: string|null, method: 'cli'|'sdk'|'none' } + */ + static detectClaudeInstallation() { + try { + // Method 1: Check if 'claude' command is in PATH + try { + const claudePath = execSync('which claude', { encoding: 'utf-8' }).trim(); + const version = execSync('claude --version', { encoding: 'utf-8' }).trim(); + return { + installed: true, + path: claudePath, + version: version, + method: 'cli' + }; + } catch (error) { + // CLI not in PATH, check local installation + } + + // Method 2: Check for local installation + const localClaudePath = path.join(os.homedir(), '.claude', 'local', 'claude'); + if (fs.existsSync(localClaudePath)) { + try { + const version = execSync(`${localClaudePath} --version`, { encoding: 'utf-8' }).trim(); + return { + installed: true, + path: localClaudePath, + version: version, + method: 'cli-local' + }; + } catch (error) { + // Local CLI exists but may not be executable + } + } + + // Method 3: Check Windows path + if (process.platform === 'win32') { + try { + const claudePath = execSync('where claude', { encoding: 'utf-8' }).trim(); + const version = execSync('claude --version', { encoding: 'utf-8' }).trim(); + return { + installed: true, + path: claudePath, + version: version, + method: 'cli' + }; + } catch (error) { + // Not found + } + } + + // Method 4: SDK mode (using OAuth token) + if (process.env.CLAUDE_CODE_OAUTH_TOKEN) { + return { + installed: true, + path: null, + version: 'SDK Mode', + method: 'sdk' + }; + } + + return { + installed: false, + path: null, + version: null, + method: 'none' + }; + } catch (error) { + console.error('[ClaudeCliDetector] Error detecting Claude installation:', error); + return { + installed: false, + path: null, + version: null, + method: 'none', + error: error.message + }; + } + } + + /** + * Get installation recommendations + */ + static getInstallationInfo() { + const detection = this.detectClaudeInstallation(); + + if (detection.installed) { + return { + status: 'installed', + method: detection.method, + version: detection.version, + path: detection.path, + recommendation: detection.method === 'cli' + ? 'Using Claude Code CLI - optimal for long-running tasks' + : 'Using SDK mode - works well but CLI may provide better performance' + }; + } + + return { + status: 'not_installed', + recommendation: 'Consider installing Claude Code CLI for better performance with ultrathink', + installCommands: { + macos: 'curl -fsSL claude.ai/install.sh | bash', + windows: 'irm https://claude.ai/install.ps1 | iex', + linux: 'curl -fsSL claude.ai/install.sh | bash', + npm: 'npm install -g @anthropic-ai/claude-code' + } + }; + } +} + +module.exports = ClaudeCliDetector; + diff --git a/app/electron/services/feature-executor.js b/app/electron/services/feature-executor.js index 239c7518..6899d8c5 100644 --- a/app/electron/services/feature-executor.js +++ b/app/electron/services/feature-executor.js @@ -4,10 +4,97 @@ const contextManager = require("./context-manager"); const featureLoader = require("./feature-loader"); const mcpServerFactory = require("./mcp-server-factory"); +// Model name mappings +const MODEL_MAP = { + haiku: "claude-haiku-4-20250514", + sonnet: "claude-sonnet-4-20250514", + opus: "claude-opus-4-5-20251101", +}; + +// Thinking level to budget_tokens mapping +// These values control how much "thinking time" the model gets for extended thinking +const THINKING_BUDGET_MAP = { + none: null, // No extended thinking + low: 4096, // Light thinking + medium: 16384, // Moderate thinking + high: 65536, // Deep thinking + ultrathink: 262144, // Ultra-deep thinking (maximum reasoning) +}; + /** * Feature Executor - Handles feature implementation using Claude Agent SDK */ class FeatureExecutor { + /** + * Get the model string based on feature's model setting + */ + getModelString(feature) { + const modelKey = feature.model || "opus"; // Default to opus + return MODEL_MAP[modelKey] || MODEL_MAP.opus; + } + + /** + * Get thinking configuration based on feature's thinkingLevel + */ + getThinkingConfig(feature) { + const level = feature.thinkingLevel || "none"; + const budgetTokens = THINKING_BUDGET_MAP[level]; + + if (budgetTokens === null) { + return null; // No extended thinking + } + + return { + type: "enabled", + budget_tokens: budgetTokens, + }; + } + + /** + * Prepare for ultrathink execution - validate and warn + */ + prepareForUltrathink(feature, thinkingConfig) { + if (feature.thinkingLevel !== 'ultrathink') { + return { ready: true }; + } + + const warnings = []; + const recommendations = []; + + // Check CLI installation + const claudeCliDetector = require('./claude-cli-detector'); + const cliInfo = claudeCliDetector.getInstallationInfo(); + + if (cliInfo.status === 'not_installed') { + warnings.push('Claude Code CLI not detected - ultrathink may have timeout issues'); + recommendations.push('Install Claude Code CLI for optimal ultrathink performance'); + } + + // Validate budget tokens + if (thinkingConfig && thinkingConfig.budget_tokens > 32000) { + warnings.push(`Ultrathink budget (${thinkingConfig.budget_tokens} tokens) exceeds recommended 32K - may cause long-running requests`); + recommendations.push('Consider using batch processing for budgets above 32K'); + } + + // Cost estimate (rough) + const estimatedCost = (thinkingConfig?.budget_tokens || 0) / 1000 * 0.015; // Rough estimate + if (estimatedCost > 1.0) { + warnings.push(`Estimated cost: ~$${estimatedCost.toFixed(2)} per execution`); + } + + // Time estimate + warnings.push('Ultrathink tasks typically take 45-180 seconds'); + + return { + ready: true, + warnings, + recommendations, + estimatedCost, + estimatedTime: '45-180 seconds', + cliInfo + }; + } + /** * Sleep helper */ @@ -46,9 +133,39 @@ class FeatureExecutor { projectPath ); + // Get model and thinking configuration from feature settings + const modelString = this.getModelString(feature); + const thinkingConfig = this.getThinkingConfig(feature); + + // Prepare for ultrathink if needed + if (feature.thinkingLevel === 'ultrathink') { + const preparation = this.prepareForUltrathink(feature, thinkingConfig); + + console.log(`[FeatureExecutor] Ultrathink preparation:`, preparation); + + // Log warnings + if (preparation.warnings && preparation.warnings.length > 0) { + preparation.warnings.forEach(warning => { + console.warn(`[FeatureExecutor] ⚠️ ${warning}`); + }); + } + + // Send preparation info to renderer + sendToRenderer({ + type: 'auto_mode_ultrathink_preparation', + featureId: feature.id, + warnings: preparation.warnings || [], + recommendations: preparation.recommendations || [], + estimatedCost: preparation.estimatedCost, + estimatedTime: preparation.estimatedTime + }); + } + + console.log(`[FeatureExecutor] Using model: ${modelString}, thinking: ${feature.thinkingLevel || 'none'}`); + // Configure options for the SDK query const options = { - model: "claude-opus-4-5-20251101", + model: modelString, systemPrompt: promptBuilder.getCodingPrompt(), maxTurns: 1000, cwd: projectPath, @@ -74,6 +191,11 @@ class FeatureExecutor { abortController: abortController, }; + // Add thinking configuration if enabled + if (thinkingConfig) { + options.thinking = thinkingConfig; + } + // Build the prompt for this specific feature const prompt = promptBuilder.buildFeaturePrompt(feature); @@ -256,8 +378,38 @@ class FeatureExecutor { projectPath ); + // Get model and thinking configuration from feature settings + const modelString = this.getModelString(feature); + const thinkingConfig = this.getThinkingConfig(feature); + + // Prepare for ultrathink if needed + if (feature.thinkingLevel === 'ultrathink') { + const preparation = this.prepareForUltrathink(feature, thinkingConfig); + + console.log(`[FeatureExecutor] Ultrathink preparation:`, preparation); + + // Log warnings + if (preparation.warnings && preparation.warnings.length > 0) { + preparation.warnings.forEach(warning => { + console.warn(`[FeatureExecutor] ⚠️ ${warning}`); + }); + } + + // Send preparation info to renderer + sendToRenderer({ + type: 'auto_mode_ultrathink_preparation', + featureId: feature.id, + warnings: preparation.warnings || [], + recommendations: preparation.recommendations || [], + estimatedCost: preparation.estimatedCost, + estimatedTime: preparation.estimatedTime + }); + } + + console.log(`[FeatureExecutor] Resuming with model: ${modelString}, thinking: ${feature.thinkingLevel || 'none'}`); + const options = { - model: "claude-opus-4-5-20251101", + model: modelString, systemPrompt: promptBuilder.getVerificationPrompt(), maxTurns: 1000, cwd: projectPath, @@ -273,6 +425,11 @@ class FeatureExecutor { abortController: abortController, }; + // Add thinking configuration if enabled + if (thinkingConfig) { + options.thinking = thinkingConfig; + } + // Build prompt with previous context const prompt = promptBuilder.buildResumePrompt(feature, previousContext); diff --git a/app/electron/services/feature-loader.js b/app/electron/services/feature-loader.js index e8e1bbbd..31fe84dd 100644 --- a/app/electron/services/feature-loader.js +++ b/app/electron/services/feature-loader.js @@ -84,6 +84,12 @@ class FeatureLoader { if (f.summary !== undefined) { featureData.summary = f.summary; } + if (f.model !== undefined) { + featureData.model = f.model; + } + if (f.thinkingLevel !== undefined) { + featureData.thinkingLevel = f.thinkingLevel; + } return featureData; }); diff --git a/app/src/components/ui/dialog.tsx b/app/src/components/ui/dialog.tsx index d9ccec91..fec3338d 100644 --- a/app/src/components/ui/dialog.tsx +++ b/app/src/components/ui/dialog.tsx @@ -50,9 +50,11 @@ function DialogContent({ className, children, showCloseButton = true, + compact = false, ...props }: React.ComponentProps & { showCloseButton?: boolean + compact?: boolean }) { return ( @@ -60,7 +62,8 @@ function DialogContent({ Close diff --git a/app/src/components/ui/log-viewer.tsx b/app/src/components/ui/log-viewer.tsx index 708f596b..414fd580 100644 --- a/app/src/components/ui/log-viewer.tsx +++ b/app/src/components/ui/log-viewer.tsx @@ -13,6 +13,7 @@ import { Bug, Info, FileOutput, + Brain, } from "lucide-react"; import { cn } from "@/lib/utils"; import { @@ -43,6 +44,8 @@ const getLogIcon = (type: LogEntryType) => { return ; case "warning": return ; + case "thinking": + return ; case "debug": return ; default: diff --git a/app/src/components/views/agent-output-modal.tsx b/app/src/components/views/agent-output-modal.tsx index 14bb1787..701a943b 100644 --- a/app/src/components/views/agent-output-modal.tsx +++ b/app/src/components/views/agent-output-modal.tsx @@ -11,6 +11,7 @@ import { import { Loader2, List, FileText } from "lucide-react"; import { getElectronAPI } from "@/lib/electron"; import { LogViewer } from "@/components/ui/log-viewer"; +import type { AutoModeEvent } from "@/types/electron"; interface AgentOutputModalProps { open: boolean; @@ -113,44 +114,78 @@ export function AgentOutputModal({ if (!api?.autoMode) return; const unsubscribe = api.autoMode.onEvent((event) => { - // Filter events for this specific feature only - if (event.featureId !== featureId) { + // Filter events for this specific feature only (skip events without featureId) + if ("featureId" in event && event.featureId !== featureId) { return; } let newContent = ""; - if (event.type === "auto_mode_progress") { - newContent = event.content || ""; - } else if (event.type === "auto_mode_tool") { - const toolName = event.tool || "Unknown Tool"; - const toolInput = event.input - ? JSON.stringify(event.input, null, 2) - : ""; - newContent = `\n🔧 Tool: ${toolName}\n${ - toolInput ? `Input: ${toolInput}` : "" - }`; - } else if (event.type === "auto_mode_phase") { - const phaseEmoji = - event.phase === "planning" - ? "📋" - : event.phase === "action" - ? "⚡" - : "✅"; - newContent = `\n${phaseEmoji} ${event.message}\n`; - } else if (event.type === "auto_mode_error") { - newContent = `\n❌ Error: ${event.error}\n`; - } else if (event.type === "auto_mode_feature_complete") { - const emoji = event.passes ? "✅" : "⚠️"; - newContent = `\n${emoji} Task completed: ${event.message}\n`; + switch (event.type) { + case "auto_mode_progress": + newContent = event.content || ""; + break; + case "auto_mode_tool": + const toolName = event.tool || "Unknown Tool"; + const toolInput = event.input + ? JSON.stringify(event.input, null, 2) + : ""; + newContent = `\n🔧 Tool: ${toolName}\n${ + toolInput ? `Input: ${toolInput}` : "" + }`; + break; + case "auto_mode_phase": + const phaseEmoji = + event.phase === "planning" + ? "📋" + : event.phase === "action" + ? "⚡" + : "✅"; + newContent = `\n${phaseEmoji} ${event.message}\n`; + break; + case "auto_mode_error": + newContent = `\n❌ Error: ${event.error}\n`; + break; + case "auto_mode_ultrathink_preparation": + // Format thinking level preparation information + let prepContent = `\n🧠 Ultrathink Preparation\n`; + + if (event.warnings && event.warnings.length > 0) { + prepContent += `\n⚠️ Warnings:\n`; + event.warnings.forEach((warning: string) => { + prepContent += ` • ${warning}\n`; + }); + } + + if (event.recommendations && event.recommendations.length > 0) { + prepContent += `\n💡 Recommendations:\n`; + event.recommendations.forEach((rec: string) => { + prepContent += ` • ${rec}\n`; + }); + } + + if (event.estimatedCost !== undefined) { + prepContent += `\n💰 Estimated Cost: ~$${event.estimatedCost.toFixed(2)} per execution\n`; + } + + if (event.estimatedTime) { + prepContent += `\n⏱️ Estimated Time: ${event.estimatedTime}\n`; + } + + newContent = prepContent; + break; + case "auto_mode_feature_complete": + const emoji = event.passes ? "✅" : "⚠️"; + newContent = `\n${emoji} Task completed: ${event.message}\n`; - // Close the modal when the feature is verified (passes = true) - if (event.passes) { - // Small delay to show the completion message before closing - setTimeout(() => { - onClose(); - }, 1500); - } + // Close the modal when the feature is verified (passes = true) + if (event.passes) { + // Small delay to show the completion message before closing + setTimeout(() => { + onClose(); + }, 1500); + } + break; } if (newContent) { diff --git a/app/src/components/views/board-view.tsx b/app/src/components/views/board-view.tsx index 6d5a4d76..45150843 100644 --- a/app/src/components/views/board-view.tsx +++ b/app/src/components/views/board-view.tsx @@ -16,7 +16,7 @@ import { SortableContext, verticalListSortingStrategy, } from "@dnd-kit/sortable"; -import { useAppStore, Feature, FeatureImage, FeatureImagePath } from "@/store/app-store"; +import { useAppStore, Feature, FeatureImage, FeatureImagePath, AgentModel, ThinkingLevel } from "@/store/app-store"; import { getElectronAPI } from "@/lib/electron"; import { cn } from "@/lib/utils"; import { @@ -44,7 +44,7 @@ import { KanbanColumn } from "./kanban-column"; import { KanbanCard } from "./kanban-card"; import { AutoModeLog } from "./auto-mode-log"; import { AgentOutputModal } from "./agent-output-modal"; -import { Plus, RefreshCw, Play, StopCircle, Loader2, ChevronUp, ChevronDown, Users, Trash2, FastForward, FlaskConical, CheckCircle2, MessageSquare, GitCommit } from "lucide-react"; +import { Plus, RefreshCw, Play, StopCircle, Loader2, ChevronUp, ChevronDown, Users, Trash2, FastForward, FlaskConical, CheckCircle2, MessageSquare, GitCommit, Brain, Zap } from "lucide-react"; import { toast } from "sonner"; import { Slider } from "@/components/ui/slider"; import { Checkbox } from "@/components/ui/checkbox"; @@ -54,6 +54,7 @@ import { ACTION_SHORTCUTS, KeyboardShortcut, } from "@/hooks/use-keyboard-shortcuts"; +import { useWindowState } from "@/hooks/use-window-state"; type ColumnId = Feature["status"]; @@ -87,6 +88,8 @@ export function BoardView() { images: [] as FeatureImage[], imagePaths: [] as DescriptionImagePath[], skipTests: false, + model: "opus" as AgentModel, + thinkingLevel: "none" as ThinkingLevel, }); const [isLoading, setIsLoading] = useState(true); const [isMounted, setIsMounted] = useState(false); @@ -118,6 +121,9 @@ export function BoardView() { // Auto mode hook const autoMode = useAutoMode(); + // Window state hook for compact dialog mode + const { isMaximized } = useWindowState(); + // Get in-progress features for keyboard shortcuts (memoized for shortcuts) const inProgressFeaturesForShortcuts = useMemo(() => { return features.filter((f) => { @@ -405,6 +411,8 @@ export function BoardView() { imagePaths: f.imagePaths, skipTests: f.skipTests, summary: f.summary, + model: f.model, + thinkingLevel: f.thinkingLevel, })); await api.writeFile( `${currentProject.path}/.automaker/feature_list.json`, @@ -531,10 +539,12 @@ export function BoardView() { images: newFeature.images, imagePaths: newFeature.imagePaths, skipTests: newFeature.skipTests, + model: newFeature.model, + thinkingLevel: newFeature.thinkingLevel, }); // Persist the category saveCategory(category); - setNewFeature({ category: "", description: "", steps: [""], images: [], imagePaths: [], skipTests: false }); + setNewFeature({ category: "", description: "", steps: [""], images: [], imagePaths: [], skipTests: false, model: "opus", thinkingLevel: "none" }); setShowAddDialog(false); }; @@ -546,6 +556,8 @@ export function BoardView() { description: editingFeature.description, steps: editingFeature.steps, skipTests: editingFeature.skipTests, + model: editingFeature.model, + thinkingLevel: editingFeature.thinkingLevel, }); // Persist the category if it's new if (editingFeature.category) { @@ -1179,6 +1191,7 @@ export function BoardView() { {/* Add Feature Dialog */} { if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && newFeature.description) { @@ -1193,7 +1206,7 @@ export function BoardView() { Create a new feature card for the Kanban board. -
+
-

+

When enabled, this feature will require manual verification instead of automated TDD.

+ + {/* Model Selection */} +
+ +
+ {(["haiku", "sonnet", "opus"] as AgentModel[]).map((model) => ( + + ))} +
+

+ Haiku for simple tasks, Sonnet for balanced, Opus for complex tasks. +

+
+ + {/* Thinking Level */} +
+ +
+ {(["none", "low", "medium", "high", "ultrathink"] as ThinkingLevel[]).map((level) => ( + + ))} +
+

+ Higher thinking levels give the model more time to reason through complex problems. +

+
+ ))} + +

+ Haiku for simple tasks, Sonnet for balanced, Opus for complex tasks. +

+ + + {/* Thinking Level */} +
+ +
+ {(["none", "low", "medium", "high", "ultrathink"] as ThinkingLevel[]).map((level) => ( + + ))} +
+

+ Higher thinking levels give the model more time to reason through complex problems. +

+
)} @@ -1468,6 +1625,7 @@ export function BoardView() { } }}> { if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && followUpPrompt.trim()) { @@ -1487,7 +1645,7 @@ export function BoardView() { )} -
+
(null); const [testingGeminiConnection, setTestingGeminiConnection] = useState(false); const [geminiTestResult, setGeminiTestResult] = useState<{ success: boolean; message: string } | null>(null); + const [claudeCliStatus, setClaudeCliStatus] = useState<{ + success: boolean; + status?: string; + method?: string; + version?: string; + path?: string; + recommendation?: string; + installCommands?: { + macos?: string; + windows?: string; + linux?: string; + npm?: string; + }; + error?: string; + } | null>(null); useEffect(() => { setAnthropicKey(apiKeys.anthropic); setGoogleKey(apiKeys.google); }, [apiKeys]); + useEffect(() => { + const checkCliStatus = async () => { + const api = getElectronAPI(); + if (api?.checkClaudeCli) { + try { + const status = await api.checkClaudeCli(); + setClaudeCliStatus(status); + } catch (error) { + console.error("Failed to check Claude CLI status:", error); + } + } + }; + checkCliStatus(); + }, []); + const handleTestConnection = async () => { setTestingConnection(true); setTestResult(null); @@ -309,6 +340,86 @@ export function SettingsView() {
+ {/* Claude CLI Status Section */} + {claudeCliStatus && ( +
+
+
+ +

Claude Code CLI

+
+

+ Claude Code CLI provides better performance for long-running tasks, especially with ultrathink. +

+
+
+ {claudeCliStatus.success && claudeCliStatus.status === 'installed' ? ( +
+
+ +
+

Claude Code CLI Installed

+
+ {claudeCliStatus.method && ( +

Method: {claudeCliStatus.method}

+ )} + {claudeCliStatus.version && ( +

Version: {claudeCliStatus.version}

+ )} + {claudeCliStatus.path && ( +

+ Path: {claudeCliStatus.path} +

+ )} +
+
+
+ {claudeCliStatus.recommendation && ( +

{claudeCliStatus.recommendation}

+ )} +
+ ) : ( +
+
+ +
+

Claude Code CLI Not Detected

+

+ {claudeCliStatus.recommendation || 'Consider installing Claude Code CLI for optimal performance with ultrathink.'} +

+
+
+ {claudeCliStatus.installCommands && ( +
+

Installation Commands:

+
+ {claudeCliStatus.installCommands.npm && ( +
+

npm:

+ {claudeCliStatus.installCommands.npm} +
+ )} + {claudeCliStatus.installCommands.macos && ( +
+

macOS/Linux:

+ {claudeCliStatus.installCommands.macos} +
+ )} + {claudeCliStatus.installCommands.windows && ( +
+

Windows (PowerShell):

+ {claudeCliStatus.installCommands.windows} +
+ )} +
+
+ )} +
+ )} +
+
+ )} + {/* Appearance Section */}
diff --git a/app/src/hooks/use-window-state.ts b/app/src/hooks/use-window-state.ts new file mode 100644 index 00000000..8a4bc332 --- /dev/null +++ b/app/src/hooks/use-window-state.ts @@ -0,0 +1,54 @@ +import { useState, useEffect } from "react"; + +export interface WindowState { + isMaximized: boolean; + windowWidth: number; + windowHeight: number; +} + +/** + * Hook to track window state (dimensions and maximized status) + * For Electron apps, considers window maximized if width > 1400px + * Also listens for window resize events to update state + */ +export function useWindowState(): WindowState { + const [windowState, setWindowState] = useState(() => { + if (typeof window === "undefined") { + return { isMaximized: false, windowWidth: 0, windowHeight: 0 }; + } + const width = window.innerWidth; + const height = window.innerHeight; + return { + isMaximized: width > 1400, + windowWidth: width, + windowHeight: height, + }; + }); + + useEffect(() => { + if (typeof window === "undefined") return; + + const updateWindowState = () => { + const width = window.innerWidth; + const height = window.innerHeight; + setWindowState({ + isMaximized: width > 1400, + windowWidth: width, + windowHeight: height, + }); + }; + + // Set initial state + updateWindowState(); + + // Listen for resize events + window.addEventListener("resize", updateWindowState); + + return () => { + window.removeEventListener("resize", updateWindowState); + }; + }, []); + + return windowState; +} + diff --git a/app/src/lib/electron.ts b/app/src/lib/electron.ts index 91bb6854..8a3a13a0 100644 --- a/app/src/lib/electron.ts +++ b/app/src/lib/electron.ts @@ -41,21 +41,8 @@ export interface StatResult { error?: string; } -// Auto Mode types -export type AutoModePhase = "planning" | "action" | "verification"; - -export interface AutoModeEvent { - type: "auto_mode_feature_start" | "auto_mode_progress" | "auto_mode_tool" | "auto_mode_feature_complete" | "auto_mode_error" | "auto_mode_complete" | "auto_mode_phase"; - featureId?: string; - feature?: object; - content?: string; - tool?: string; - input?: unknown; - passes?: boolean; - message?: string; - error?: string; - phase?: AutoModePhase; -} +// Auto Mode types - Import from electron.d.ts to avoid duplication +import type { AutoModeEvent } from "@/types/electron"; export interface AutoModeAPI { start: (projectPath: string) => Promise<{ success: boolean; error?: string }>; @@ -92,6 +79,21 @@ export interface ElectronAPI { getPath: (name: string) => Promise; saveImageToTemp?: (data: string, filename: string, mimeType: string) => Promise; autoMode?: AutoModeAPI; + checkClaudeCli?: () => Promise<{ + success: boolean; + status?: string; + method?: string; + version?: string; + path?: string; + recommendation?: string; + installCommands?: { + macos?: string; + windows?: string; + linux?: string; + npm?: string; + }; + error?: string; + }>; } declare global { diff --git a/app/src/lib/log-parser.ts b/app/src/lib/log-parser.ts index 76a6ce15..04ab85ee 100644 --- a/app/src/lib/log-parser.ts +++ b/app/src/lib/log-parser.ts @@ -12,7 +12,8 @@ export type LogEntryType = | "success" | "info" | "debug" - | "warning"; + | "warning" + | "thinking"; export interface LogEntry { id: string; @@ -75,6 +76,18 @@ function detectEntryType(content: string): LogEntryType { return "warning"; } + // Thinking/Preparation info + if ( + trimmed.toLowerCase().includes("ultrathink") || + trimmed.toLowerCase().includes("thinking level") || + trimmed.toLowerCase().includes("estimated cost") || + trimmed.toLowerCase().includes("estimated time") || + trimmed.toLowerCase().includes("budget tokens") || + trimmed.match(/thinking.*preparation/i) + ) { + return "thinking"; + } + // Debug info (JSON, stack traces, etc.) if ( trimmed.startsWith("{") || @@ -130,6 +143,8 @@ function generateTitle(type: LogEntryType, content: string): string { return "Success"; case "warning": return "Warning"; + case "thinking": + return "Thinking Level"; case "debug": return "Debug Info"; case "prompt": @@ -180,6 +195,9 @@ export function parseLogOutput(rawOutput: string): LogEntry[] { trimmedLine.startsWith("✅") || trimmedLine.startsWith("❌") || trimmedLine.startsWith("⚠️") || + trimmedLine.startsWith("🧠") || + trimmedLine.toLowerCase().includes("ultrathink preparation") || + trimmedLine.toLowerCase().includes("thinking level") || (trimmedLine.startsWith("Input:") && currentEntry?.type === "tool_call"); if (isNewEntry) { @@ -321,6 +339,14 @@ export function getLogTypeColors(type: LogEntryType): { icon: "text-orange-400", badge: "bg-orange-500/20 text-orange-300", }; + case "thinking": + return { + bg: "bg-indigo-500/10", + border: "border-l-indigo-500", + text: "text-indigo-300", + icon: "text-indigo-400", + badge: "bg-indigo-500/20 text-indigo-300", + }; case "debug": return { bg: "bg-purple-500/10", diff --git a/app/src/store/app-store.ts b/app/src/store/app-store.ts index e58eda5a..02aa6559 100644 --- a/app/src/store/app-store.ts +++ b/app/src/store/app-store.ts @@ -52,6 +52,12 @@ export interface FeatureImagePath { mimeType: string; } +// Available models for feature execution +export type AgentModel = "opus" | "sonnet" | "haiku"; + +// Thinking level (budget_tokens) options +export type ThinkingLevel = "none" | "low" | "medium" | "high" | "ultrathink"; + export interface Feature { id: string; category: string; @@ -63,6 +69,8 @@ export interface Feature { startedAt?: string; // ISO timestamp for when the card moved to in_progress skipTests?: boolean; // When true, skip TDD approach and require manual verification summary?: string; // Summary of what was done/modified by the agent + model?: AgentModel; // Model to use for this feature (defaults to opus) + thinkingLevel?: ThinkingLevel; // Thinking level for extended thinking (defaults to none) } export interface AppState { diff --git a/app/src/types/electron.d.ts b/app/src/types/electron.d.ts index 2a44841a..137accdb 100644 --- a/app/src/types/electron.d.ts +++ b/app/src/types/electron.d.ts @@ -195,6 +195,14 @@ export type AutoModeEvent = featureId: string; phase: "planning" | "action" | "verification"; message: string; + } + | { + type: "auto_mode_ultrathink_preparation"; + featureId: string; + warnings: string[]; + recommendations: string[]; + estimatedCost?: number; + estimatedTime?: string; }; export interface AutoModeAPI { @@ -315,6 +323,23 @@ export interface ElectronAPI { // Auto Mode APIs autoMode: AutoModeAPI; + + // Claude CLI Detection API + checkClaudeCli: () => Promise<{ + success: boolean; + status?: string; + method?: string; + version?: string; + path?: string; + recommendation?: string; + installCommands?: { + macos?: string; + windows?: string; + linux?: string; + npm?: string; + }; + error?: string; + }>; } declare global {