/** * CLI Routes Module * Handles all CLI-related API endpoints */ import { getCliToolsStatus, getCliToolsFullStatus, installCliTool, uninstallCliTool, enableCliTool, disableCliTool, getExecutionHistory, getExecutionHistoryAsync, getExecutionDetail, getConversationDetail, getConversationDetailWithNativeInfo, deleteExecution, deleteExecutionAsync, batchDeleteExecutionsAsync, executeCliTool, getNativeSessionContent, getFormattedNativeConversation, getEnrichedConversation, getHistoryWithNativeInfo } from '../../tools/cli-executor.js'; import { SmartContentFormatter } from '../../tools/cli-output-converter.js'; import { generateSmartContext, formatSmartContext } from '../../tools/smart-context.js'; import { loadCliConfig, getToolConfig, updateToolConfig, getFullConfigResponse, PREDEFINED_MODELS } from '../../tools/cli-config-manager.js'; import { loadClaudeCliTools, ensureClaudeCliTools, saveClaudeCliTools, loadClaudeCliSettings, saveClaudeCliSettings, updateClaudeToolEnabled, updateClaudeCacheSettings, getClaudeCliToolsInfo, addClaudeApiEndpoint, removeClaudeApiEndpoint, addClaudeCustomEndpoint, // @deprecated - kept for backward compatibility removeClaudeCustomEndpoint, // @deprecated - kept for backward compatibility updateCodeIndexMcp, getCodeIndexMcp } from '../../tools/claude-cli-tools.js'; import type { RouteContext } from './types.js'; // ========== Active Executions State ========== // Stores running CLI executions for state recovery when view is opened/refreshed interface ActiveExecution { id: string; tool: string; mode: string; prompt: string; startTime: number; output: string; status: 'running' | 'completed' | 'error'; } const activeExecutions = new Map(); /** * Get all active CLI executions * Used by frontend to restore state when view is opened during execution */ export function getActiveExecutions(): ActiveExecution[] { return Array.from(activeExecutions.values()); } /** * Update active execution state from hook events * Called by hooks-routes when CLI events are received from terminal execution */ export function updateActiveExecution(event: { type: 'started' | 'output' | 'completed'; executionId: string; tool?: string; mode?: string; prompt?: string; output?: string; success?: boolean; }): void { const { type, executionId, tool, mode, prompt, output, success } = event; // Debug log for troubleshooting console.log(`[ActiveExec] ${type}: ${executionId} (current count: ${activeExecutions.size})`); if (!executionId) { console.warn('[ActiveExec] Missing executionId, skipping'); return; } if (type === 'started') { // Create new active execution activeExecutions.set(executionId, { id: executionId, tool: tool || 'unknown', mode: mode || 'analysis', prompt: (prompt || '').substring(0, 500), startTime: Date.now(), output: '', status: 'running' }); } else if (type === 'output') { // Append output to existing execution const activeExec = activeExecutions.get(executionId); if (activeExec && output) { activeExec.output += output; } } else if (type === 'completed') { // Mark as completed instead of immediately deleting // Keep execution visible for 5 minutes to allow page refreshes to see it const activeExec = activeExecutions.get(executionId); if (activeExec) { activeExec.status = success ? 'completed' : 'error'; // Auto-cleanup after 5 minutes setTimeout(() => { activeExecutions.delete(executionId); console.log(`[ActiveExec] Auto-cleaned completed execution: ${executionId}`); }, 5 * 60 * 1000); console.log(`[ActiveExec] Marked as ${activeExec.status}, will auto-clean in 5 minutes`); } } } /** * Handle CLI routes * @returns true if route was handled, false otherwise */ export async function handleCliRoutes(ctx: RouteContext): Promise { const { pathname, url, req, res, initialPath, handlePostRequest, broadcastToClients } = ctx; // API: Get Active CLI Executions (for state recovery) if (pathname === '/api/cli/active' && req.method === 'GET') { const executions = getActiveExecutions(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ executions })); return true; } // API: CLI Tools Status if (pathname === '/api/cli/status') { const status = await getCliToolsStatus(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(status)); return true; } // API: CLI Tools Full Status (with enabled state) if (pathname === '/api/cli/full-status') { const status = await getCliToolsFullStatus(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(status)); return true; } // API: Install CLI Tool if (pathname === '/api/cli/install' && req.method === 'POST') { handlePostRequest(req, res, async (body: unknown) => { const { tool } = body as { tool: string }; if (!tool) { return { error: 'Tool name is required', status: 400 }; } const result = await installCliTool(tool); if (result.success) { // Broadcast tool installed event broadcastToClients({ type: 'CLI_TOOL_INSTALLED', payload: { tool, timestamp: new Date().toISOString() } }); return { success: true, message: `${tool} installed successfully` }; } else { return { success: false, error: result.error, status: 500 }; } }); return true; } // API: Uninstall CLI Tool if (pathname === '/api/cli/uninstall' && req.method === 'POST') { handlePostRequest(req, res, async (body: unknown) => { const { tool } = body as { tool: string }; if (!tool) { return { error: 'Tool name is required', status: 400 }; } const result = await uninstallCliTool(tool); if (result.success) { // Broadcast tool uninstalled event broadcastToClients({ type: 'CLI_TOOL_UNINSTALLED', payload: { tool, timestamp: new Date().toISOString() } }); return { success: true, message: `${tool} uninstalled successfully` }; } else { return { success: false, error: result.error, status: 500 }; } }); return true; } // API: Enable CLI Tool if (pathname === '/api/cli/enable' && req.method === 'POST') { handlePostRequest(req, res, async (body: unknown) => { const { tool } = body as { tool: string }; if (!tool) { return { error: 'Tool name is required', status: 400 }; } const result = enableCliTool(tool); // Broadcast tool enabled event broadcastToClients({ type: 'CLI_TOOL_ENABLED', payload: { tool, timestamp: new Date().toISOString() } }); return { success: true, message: `${tool} enabled` }; }); return true; } // API: Disable CLI Tool if (pathname === '/api/cli/disable' && req.method === 'POST') { handlePostRequest(req, res, async (body: unknown) => { const { tool } = body as { tool: string }; if (!tool) { return { error: 'Tool name is required', status: 400 }; } const result = disableCliTool(tool); // Broadcast tool disabled event broadcastToClients({ type: 'CLI_TOOL_DISABLED', payload: { tool, timestamp: new Date().toISOString() } }); return { success: true, message: `${tool} disabled` }; }); return true; } // API: Get Full CLI Config (with predefined models) if (pathname === '/api/cli/config' && req.method === 'GET') { try { const response = getFullConfigResponse(initialPath); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(response)); } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); } return true; } // API: Get/Update Tool Config const configMatch = pathname.match(/^\/api\/cli\/config\/(gemini|qwen|codex|claude|opencode)$/); if (configMatch) { const tool = configMatch[1]; // GET: Get single tool config if (req.method === 'GET') { try { const toolConfig = getToolConfig(initialPath, tool); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(toolConfig)); } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); } return true; } // PUT: Update tool config if (req.method === 'PUT') { handlePostRequest(req, res, async (body: unknown) => { try { const updates = body as { enabled?: boolean; primaryModel?: string; secondaryModel?: string; tags?: string[] }; const updated = updateToolConfig(initialPath, tool, updates); // Broadcast config updated event broadcastToClients({ type: 'CLI_CONFIG_UPDATED', payload: { tool, config: updated, timestamp: new Date().toISOString() } }); return { success: true, config: updated }; } catch (err) { return { error: (err as Error).message, status: 500 }; } }); return true; } } // Helper: Get API endpoints from tools (type: 'api-endpoint') const getApiEndpointsFromTools = (config: any) => { return Object.entries(config.tools) .filter(([_, t]: [string, any]) => t.type === 'api-endpoint') .map(([name, t]: [string, any]) => ({ id: t.id || name, name, enabled: t.enabled })); }; // API: Get all API endpoints (for --tool custom --model ) if (pathname === '/api/cli/endpoints' && req.method === 'GET') { try { // Use ensureClaudeCliTools to auto-create config if missing const config = ensureClaudeCliTools(initialPath); const endpoints = getApiEndpointsFromTools(config); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ endpoints })); } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); } return true; } // API: Add/Update API endpoint if (pathname === '/api/cli/endpoints' && req.method === 'POST') { handlePostRequest(req, res, async (body: unknown) => { try { const { id, name, enabled } = body as { id: string; name: string; enabled: boolean }; if (!id || !name) { return { error: 'id and name are required', status: 400 }; } const config = addClaudeApiEndpoint(initialPath, { id, name, enabled: enabled !== false }); broadcastToClients({ type: 'CLI_ENDPOINT_UPDATED', payload: { endpoint: { id, name, enabled }, timestamp: new Date().toISOString() } }); return { success: true, endpoints: getApiEndpointsFromTools(config) }; } catch (err) { return { error: (err as Error).message, status: 500 }; } }); return true; } // API: Update API endpoint enabled status if (pathname.match(/^\/api\/cli\/endpoints\/[^/]+$/) && req.method === 'PUT') { const endpointId = pathname.split('/').pop() || ''; handlePostRequest(req, res, async (body: unknown) => { try { const { enabled, name: newName } = body as { enabled?: boolean; name?: string }; const config = loadClaudeCliTools(initialPath); // Find the tool by id (api-endpoint type) const toolEntry = Object.entries(config.tools).find( ([_, t]: [string, any]) => t.type === 'api-endpoint' && t.id === endpointId ); if (!toolEntry) { return { error: 'Endpoint not found', status: 404 }; } const [toolName, tool] = toolEntry as [string, any]; if (typeof enabled === 'boolean') tool.enabled = enabled; // If name changes, we need to rename the key if (newName && newName !== toolName) { delete config.tools[toolName]; config.tools[newName] = tool; } saveClaudeCliTools(initialPath, config); const endpoint = { id: tool.id || toolName, name: newName || toolName, enabled: tool.enabled }; broadcastToClients({ type: 'CLI_ENDPOINT_UPDATED', payload: { endpoint, timestamp: new Date().toISOString() } }); return { success: true, endpoint }; } catch (err) { return { error: (err as Error).message, status: 500 }; } }); return true; } // API: Delete API endpoint if (pathname.match(/^\/api\/cli\/endpoints\/[^/]+$/) && req.method === 'DELETE') { const endpointId = pathname.split('/').pop() || ''; try { const config = removeClaudeApiEndpoint(initialPath, endpointId); broadcastToClients({ type: 'CLI_ENDPOINT_DELETED', payload: { endpointId, timestamp: new Date().toISOString() } }); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, endpoints: getApiEndpointsFromTools(config) })); } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); } return true; } // API: CLI Execution History if (pathname === '/api/cli/history') { const projectPath = url.searchParams.get('path') || initialPath; const limit = parseInt(url.searchParams.get('limit') || '50', 10); const tool = url.searchParams.get('tool') || null; const status = url.searchParams.get('status') || null; const category = url.searchParams.get('category') as 'user' | 'internal' | 'insight' | null; const search = url.searchParams.get('search') || null; const recursive = url.searchParams.get('recursive') !== 'false'; getExecutionHistoryAsync(projectPath, { limit, tool, status, category, search, recursive }) .then(history => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(history)); }) .catch(err => { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.message })); }); return true; } // API: CLI Execution Detail (GET) or Delete (DELETE) if (pathname === '/api/cli/execution') { const projectPath = url.searchParams.get('path') || initialPath; const executionId = url.searchParams.get('id'); if (!executionId) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Execution ID is required' })); return true; } // Handle DELETE request if (req.method === 'DELETE') { deleteExecutionAsync(projectPath, executionId) .then(result => { if (result.success) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, message: 'Execution deleted' })); } else { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: result.error || 'Delete failed' })); } }) .catch(err => { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.message })); }); return true; } // Handle GET request - return conversation with native session info const conversation = getConversationDetailWithNativeInfo(projectPath, executionId); if (!conversation) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Conversation not found' })); return true; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(conversation)); return true; } // API: Batch Delete CLI Executions if (pathname === '/api/cli/batch-delete' && req.method === 'POST') { handlePostRequest(req, res, async (body) => { const { path: projectPath, ids } = body as { path?: string; ids: string[] }; if (!ids || !Array.isArray(ids) || ids.length === 0) { return { error: 'ids array is required', status: 400 }; } const basePath = projectPath || initialPath; return await batchDeleteExecutionsAsync(basePath, ids); }); return true; } // API: Get Native Session Content if (pathname === '/api/cli/native-session') { const projectPath = url.searchParams.get('path') || initialPath; const executionId = url.searchParams.get('id'); const format = url.searchParams.get('format') || 'json'; if (!executionId) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Execution ID is required' })); return true; } try { let result; if (format === 'text') { result = await getFormattedNativeConversation(projectPath, executionId, { includeThoughts: url.searchParams.get('thoughts') === 'true', includeToolCalls: url.searchParams.get('tools') === 'true', includeTokens: url.searchParams.get('tokens') === 'true' }); } else if (format === 'pairs') { const enriched = await getEnrichedConversation(projectPath, executionId); result = enriched?.merged || null; } else { result = await getNativeSessionContent(projectPath, executionId); } if (!result) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Native session not found' })); return true; } res.writeHead(200, { 'Content-Type': format === 'text' ? 'text/plain' : 'application/json' }); res.end(format === 'text' ? result : JSON.stringify(result)); } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); } return true; } // API: Get Enriched Conversation if (pathname === '/api/cli/enriched') { const projectPath = url.searchParams.get('path') || initialPath; const executionId = url.searchParams.get('id'); if (!executionId) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Execution ID is required' })); return true; } getEnrichedConversation(projectPath, executionId) .then(result => { if (!result) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Conversation not found' })); return; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(result)); }) .catch(err => { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); }); return true; } // API: Get History with Native Session Info if (pathname === '/api/cli/history-native') { const projectPath = url.searchParams.get('path') || initialPath; const limit = parseInt(url.searchParams.get('limit') || '50', 10); const tool = url.searchParams.get('tool') || null; const status = url.searchParams.get('status') || null; const category = url.searchParams.get('category') as 'user' | 'internal' | 'insight' | null; const search = url.searchParams.get('search') || null; const recursive = url.searchParams.get('recursive') !== 'false'; getHistoryWithNativeInfo(projectPath, { limit, tool, status, category, search, recursive }) .then(history => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(history)); }) .catch(err => { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); }); return true; } // API: Execute CLI Tool if (pathname === '/api/cli/execute' && req.method === 'POST') { handlePostRequest(req, res, async (body) => { const { tool, prompt, mode, format, model, dir, includeDirs, timeout, smartContext, parentExecutionId, category } = body as any; if (!tool || !prompt) { return { error: 'tool and prompt are required', status: 400 }; } // Generate smart context if enabled let finalPrompt = prompt; if (smartContext?.enabled) { try { const contextResult = await generateSmartContext(prompt, { enabled: true, maxFiles: smartContext.maxFiles || 10, searchMode: 'text' }, dir || initialPath); const contextAppendage = formatSmartContext(contextResult); if (contextAppendage) { finalPrompt = prompt + contextAppendage; } } catch (err) { console.warn('[Smart Context] Failed to generate:', err); } } const executionId = `${Date.now()}-${tool}`; // Store active execution for state recovery activeExecutions.set(executionId, { id: executionId, tool, mode: mode || 'analysis', prompt: prompt.substring(0, 500), // Truncate for display startTime: Date.now(), output: '', status: 'running' }); // Broadcast execution started broadcastToClients({ type: 'CLI_EXECUTION_STARTED', payload: { executionId, tool, mode: mode || 'analysis', parentExecutionId, timestamp: new Date().toISOString() } }); try { const result = await executeCliTool({ tool, prompt: finalPrompt, mode: mode || 'analysis', format: format || 'plain', model, cd: dir || initialPath, includeDirs, timeout: timeout || 0, // 0 = no internal timeout, controlled by external caller category: category || 'user', parentExecutionId, stream: true }, (unit) => { // CliOutputUnit handler: use SmartContentFormatter for intelligent formatting (never returns null) const content = SmartContentFormatter.format(unit.content, unit.type); // Append to active execution buffer const activeExec = activeExecutions.get(executionId); if (activeExec) { activeExec.output += content || ''; } broadcastToClients({ type: 'CLI_OUTPUT', payload: { executionId, chunkType: unit.type, data: content } }); }); // Remove from active executions on completion activeExecutions.delete(executionId); // Broadcast completion broadcastToClients({ type: 'CLI_EXECUTION_COMPLETED', payload: { executionId, success: result.success, status: result.execution.status, duration_ms: result.execution.duration_ms } }); return { success: result.success, execution: result.execution }; } catch (error: unknown) { // Remove from active executions on error activeExecutions.delete(executionId); broadcastToClients({ type: 'CLI_EXECUTION_ERROR', payload: { executionId, error: (error as Error).message } }); return { error: (error as Error).message, status: 500 }; } }); return true; } // API: CLI Review - Submit review for an execution if (pathname.startsWith('/api/cli/review/') && req.method === 'POST') { const executionId = pathname.replace('/api/cli/review/', ''); handlePostRequest(req, res, async (body) => { const { status, rating, comments, reviewer } = body as { status: 'pending' | 'approved' | 'rejected' | 'changes_requested'; rating?: number; comments?: string; reviewer?: string; }; if (!status) { return { error: 'status is required', status: 400 }; } try { const historyStore = await import('../../tools/cli-history-store.js').then(m => m.getHistoryStore(initialPath)); const execution = historyStore.getConversation(executionId); if (!execution) { return { error: 'Execution not found', status: 404 }; } const review = historyStore.saveReview({ execution_id: executionId, status, rating, comments, reviewer }); broadcastToClients({ type: 'CLI_REVIEW_UPDATED', payload: { executionId, review, timestamp: new Date().toISOString() } }); return { success: true, review }; } catch (error: unknown) { return { error: (error as Error).message, status: 500 }; } }); return true; } // API: CLI Review - Get review for an execution if (pathname.startsWith('/api/cli/review/') && req.method === 'GET') { const executionId = pathname.replace('/api/cli/review/', ''); try { const historyStore = await import('../../tools/cli-history-store.js').then(m => m.getHistoryStore(initialPath)); const review = historyStore.getReview(executionId); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ review })); } catch (error: unknown) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (error as Error).message })); } return true; } // API: CLI Reviews - List all reviews if (pathname === '/api/cli/reviews' && req.method === 'GET') { try { const historyStore = await import('../../tools/cli-history-store.js').then(m => m.getHistoryStore(initialPath)); const statusFilter = url.searchParams.get('status') as 'pending' | 'approved' | 'rejected' | 'changes_requested' | null; const limit = parseInt(url.searchParams.get('limit') || '50', 10); const reviews = historyStore.getReviews({ status: statusFilter || undefined, limit }); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ reviews, count: reviews.length })); } catch (error: unknown) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (error as Error).message })); } return true; } // API: Get CLI Tools Config from .claude/cli-tools.json (with fallback to global) if (pathname === '/api/cli/tools-config' && req.method === 'GET') { try { // Use ensureClaudeCliTools to auto-create config if missing const toolsConfig = ensureClaudeCliTools(initialPath); const settingsConfig = loadClaudeCliSettings(initialPath); const info = getClaudeCliToolsInfo(initialPath); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ tools: toolsConfig, settings: settingsConfig, _configInfo: info })); } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); } return true; } // API: Update CLI Tools Config if (pathname === '/api/cli/tools-config' && req.method === 'PUT') { handlePostRequest(req, res, async (body: unknown) => { try { const updates = body as { tools?: any; settings?: any }; // Update tools config if provided if (updates.tools) { const currentTools = loadClaudeCliTools(initialPath); const updatedTools = { ...currentTools, tools: { ...currentTools.tools, ...(updates.tools.tools || {}) } }; saveClaudeCliTools(initialPath, updatedTools); } // Update settings config if provided if (updates.settings) { const currentSettings = loadClaudeCliSettings(initialPath); const s = updates.settings; // Deep merge: only update fields that are explicitly provided const updatedSettings = { ...currentSettings, // Scalar fields: only update if explicitly provided ...(s.defaultTool !== undefined && { defaultTool: s.defaultTool }), ...(s.promptFormat !== undefined && { promptFormat: s.promptFormat }), ...(s.nativeResume !== undefined && { nativeResume: s.nativeResume }), ...(s.recursiveQuery !== undefined && { recursiveQuery: s.recursiveQuery }), ...(s.codeIndexMcp !== undefined && { codeIndexMcp: s.codeIndexMcp }), // Nested objects: deep merge smartContext: { ...currentSettings.smartContext, ...(s.smartContext || {}) }, cache: { ...currentSettings.cache, ...(s.cache || {}) } }; saveClaudeCliSettings(initialPath, updatedSettings); } const toolsConfig = loadClaudeCliTools(initialPath); const settingsConfig = loadClaudeCliSettings(initialPath); broadcastToClients({ type: 'CLI_TOOLS_CONFIG_UPDATED', payload: { tools: toolsConfig, settings: settingsConfig, timestamp: new Date().toISOString() } }); return { success: true, tools: toolsConfig, settings: settingsConfig }; } catch (err) { return { error: (err as Error).message, status: 500 }; } }); return true; } // API: Update specific tool enabled status const toolsConfigMatch = pathname.match(/^\/api\/cli\/tools-config\/([a-zA-Z0-9_-]+)$/); if (toolsConfigMatch && req.method === 'PUT') { const toolName = toolsConfigMatch[1]; handlePostRequest(req, res, async (body: unknown) => { try { const { enabled } = body as { enabled: boolean }; const config = updateClaudeToolEnabled(initialPath, toolName, enabled); broadcastToClients({ type: 'CLI_TOOL_TOGGLED', payload: { tool: toolName, enabled, timestamp: new Date().toISOString() } }); return { success: true, config }; } catch (err) { return { error: (err as Error).message, status: 500 }; } }); return true; } // API: Update cache settings if (pathname === '/api/cli/tools-config/cache' && req.method === 'PUT') { handlePostRequest(req, res, async (body: unknown) => { try { const cacheSettings = body as { injectionMode?: string; defaultPrefix?: string; defaultSuffix?: string }; const settings = updateClaudeCacheSettings(initialPath, cacheSettings as any); broadcastToClients({ type: 'CLI_CACHE_SETTINGS_UPDATED', payload: { cache: settings.cache, timestamp: new Date().toISOString() } }); return { success: true, settings }; } catch (err) { return { error: (err as Error).message, status: 500 }; } }); return true; } // API: Get Code Index MCP provider if (pathname === '/api/cli/code-index-mcp' && req.method === 'GET') { try { const provider = getCodeIndexMcp(initialPath); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ provider })); } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (err as Error).message })); } return true; } // API: Update Code Index MCP provider if (pathname === '/api/cli/code-index-mcp' && req.method === 'PUT') { handlePostRequest(req, res, async (body: unknown) => { try { const { provider } = body as { provider: 'codexlens' | 'ace' | 'none' }; if (!provider || !['codexlens', 'ace', 'none'].includes(provider)) { return { error: 'Invalid provider. Must be "codexlens", "ace", or "none"', status: 400 }; } const result = updateCodeIndexMcp(initialPath, provider); if (result.success) { broadcastToClients({ type: 'CODE_INDEX_MCP_UPDATED', payload: { provider, timestamp: new Date().toISOString() } }); return { success: true, provider }; } else { return { error: result.error, status: 500 }; } } catch (err) { return { error: (err as Error).message, status: 500 }; } }); return true; } return false; }