// CLI Status Component // Displays CLI tool availability status and allows setting default tool // ========== CLI State ========== let cliToolStatus = { gemini: {}, qwen: {}, codex: {}, claude: {} }; let codexLensStatus = { ready: false }; let semanticStatus = { available: false }; let ccwInstallStatus = { installed: true, workflowsInstalled: true, missingFiles: [], installPath: '' }; let defaultCliTool = 'gemini'; let promptConcatFormat = localStorage.getItem('ccw-prompt-format') || 'plain'; // plain, yaml, json let cliToolsConfig = {}; // CLI tools enable/disable config let apiEndpoints = []; // API endpoints from LiteLLM config // Smart Context settings let smartContextEnabled = localStorage.getItem('ccw-smart-context') === 'true'; let smartContextMaxFiles = parseInt(localStorage.getItem('ccw-smart-context-max-files') || '10', 10); // Native Resume settings let nativeResumeEnabled = localStorage.getItem('ccw-native-resume') !== 'false'; // default true // Recursive Query settings (for hierarchical storage aggregation) let recursiveQueryEnabled = localStorage.getItem('ccw-recursive-query') !== 'false'; // default true // Code Index MCP provider (codexlens, ace, or none) let codeIndexMcpProvider = 'codexlens'; // ========== Helper Functions ========== /** * Get the context-tools filename based on provider */ function getContextToolsFileName(provider) { switch (provider) { case 'ace': return 'context-tools-ace.md'; case 'none': return 'context-tools-none.md'; default: return 'context-tools.md'; } } // ========== Initialization ========== function initCliStatus() { // Load all statuses in one call using aggregated endpoint loadAllStatuses(); } // ========== Data Loading ========== /** * Load all statuses using aggregated endpoint (single API call) */ async function loadAllStatuses() { try { const response = await fetch('/api/status/all'); if (!response.ok) throw new Error('Failed to load status'); const data = await response.json(); // Update all status data cliToolStatus = data.cli || { gemini: {}, qwen: {}, codex: {}, claude: {} }; codexLensStatus = data.codexLens || { ready: false }; semanticStatus = data.semantic || { available: false }; ccwInstallStatus = data.ccwInstall || { installed: true, workflowsInstalled: true, missingFiles: [], installPath: '' }; // Load CLI tools config and API endpoints await Promise.all([ loadCliToolsConfig(), loadApiEndpoints() ]); // Update badges updateCliBadge(); updateCodexLensBadge(); updateCcwInstallBadge(); return data; } catch (err) { console.error('Failed to load aggregated status:', err); // Fallback to individual calls if aggregated endpoint fails return await loadAllStatusesFallback(); } } /** * Fallback: Load statuses individually if aggregated endpoint fails */ async function loadAllStatusesFallback() { console.warn('[CLI Status] Using fallback individual API calls'); await Promise.all([ loadCliToolStatus(), loadCodexLensStatus() ]); } /** * Legacy: Load CLI tool status individually */ async function loadCliToolStatus() { try { const response = await fetch('/api/cli/status'); if (!response.ok) throw new Error('Failed to load CLI status'); const data = await response.json(); cliToolStatus = data; // Update badge updateCliBadge(); return data; } catch (err) { console.error('Failed to load CLI status:', err); return null; } } /** * Legacy: Load CodexLens status individually */ async function loadCodexLensStatus() { try { const response = await fetch('/api/codexlens/status'); if (!response.ok) throw new Error('Failed to load CodexLens status'); const data = await response.json(); codexLensStatus = data; // Expose to window for other modules (e.g., codexlens-manager.js) if (!window.cliToolsStatus) { window.cliToolsStatus = {}; } window.cliToolsStatus.codexlens = { installed: data.ready || false, version: data.version || null, installedModels: [] // Will be populated by loadSemanticStatus }; // Update CodexLens badge updateCodexLensBadge(); // If CodexLens is ready, also check semantic status and models if (data.ready) { await loadSemanticStatus(); await loadInstalledModels(); } return data; } catch (err) { console.error('Failed to load CodexLens status:', err); return null; } } /** * Load CodexLens dashboard data using aggregated endpoint (single API call) * This is optimized for the CodexLens Manager page initialization * @returns {Promise} Dashboard init data or null on error */ async function loadCodexLensDashboardInit() { try { const response = await fetch('/api/codexlens/dashboard-init'); if (!response.ok) throw new Error('Failed to load CodexLens dashboard init'); const data = await response.json(); // Update status variables from aggregated response codexLensStatus = data.status || { ready: false }; semanticStatus = data.semantic || { available: false }; // Expose to window for other modules if (!window.cliToolsStatus) { window.cliToolsStatus = {}; } window.cliToolsStatus.codexlens = { installed: data.installed || false, version: data.status?.version || null, installedModels: [], config: data.config || {}, semantic: data.semantic || {} }; // Store config globally for easy access window.codexLensConfig = data.config || {}; window.codexLensStatusData = data.statusData || {}; // Update badges updateCodexLensBadge(); console.log('[CLI Status] CodexLens dashboard init loaded:', { installed: data.installed, version: data.status?.version, semanticAvailable: data.semantic?.available }); return data; } catch (err) { console.error('Failed to load CodexLens dashboard init:', err); // Fallback to individual calls return await loadCodexLensStatus(); } } /** * Legacy: Load semantic status individually */ async function loadSemanticStatus() { try { const response = await fetch('/api/codexlens/semantic/status'); if (!response.ok) throw new Error('Failed to load semantic status'); const data = await response.json(); semanticStatus = data; return data; } catch (err) { console.error('Failed to load semantic status:', err); return null; } } /** * Load installed embedding models */ async function loadInstalledModels() { try { const response = await fetch('/api/codexlens/models'); if (!response.ok) throw new Error('Failed to load models'); const data = await response.json(); if (data.success && data.result && data.result.models) { // Filter to only installed models const installedModels = data.result.models .filter(m => m.installed) .map(m => m.profile); // Update window.cliToolsStatus if (window.cliToolsStatus && window.cliToolsStatus.codexlens) { window.cliToolsStatus.codexlens.installedModels = installedModels; window.cliToolsStatus.codexlens.allModels = data.result.models; } console.log('[CLI Status] Installed models:', installedModels); return installedModels; } return []; } catch (err) { console.error('Failed to load installed models:', err); return []; } } /** * Load CLI tools config from .claude/cli-tools.json (project or global fallback) */ async function loadCliToolsConfig() { try { const response = await fetch('/api/cli/tools-config'); if (!response.ok) return null; const data = await response.json(); // Store full config and extract tools for backward compatibility cliToolsConfig = data.tools || {}; window.claudeCliToolsConfig = data; // Full config available globally // Load default tool from config if (data.defaultTool) { defaultCliTool = data.defaultTool; } // Load Code Index MCP provider from config if (data.settings?.codeIndexMcp) { codeIndexMcpProvider = data.settings.codeIndexMcp; } console.log('[CLI Config] Loaded from:', data._configInfo?.source || 'unknown', '| Default:', data.defaultTool, '| CodeIndexMCP:', codeIndexMcpProvider); return data; } catch (err) { console.error('Failed to load CLI tools config:', err); return null; } } /** * Update CLI tool enabled status */ async function updateCliToolEnabled(tool, enabled) { try { const response = await fetch('/api/cli/tools-config/' + tool, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: enabled }) }); if (!response.ok) throw new Error('Failed to update'); showRefreshToast(tool + (enabled ? ' enabled' : ' disabled'), 'success'); return await response.json(); } catch (err) { console.error('Failed to update CLI tool:', err); showRefreshToast('Failed to update ' + tool, 'error'); return null; } } /** * Load API endpoints from LiteLLM config */ async function loadApiEndpoints() { try { const response = await fetch('/api/litellm-api/endpoints'); if (!response.ok) return []; const data = await response.json(); apiEndpoints = data.endpoints || []; return apiEndpoints; } catch (err) { console.error('Failed to load API endpoints:', err); return []; } } // ========== Badge Update ========== function updateCliBadge() { const badge = document.getElementById('badgeCliTools'); if (badge) { const available = Object.values(cliToolStatus).filter(t => t.available).length; const total = Object.keys(cliToolStatus).length; badge.textContent = `${available}/${total}`; badge.classList.toggle('text-success', available === total); badge.classList.toggle('text-warning', available > 0 && available < total); badge.classList.toggle('text-destructive', available === 0); } } function updateCodexLensBadge() { const badge = document.getElementById('badgeCodexLens'); if (badge) { badge.textContent = codexLensStatus.ready ? 'Ready' : 'Not Installed'; badge.classList.toggle('text-success', codexLensStatus.ready); badge.classList.toggle('text-muted-foreground', !codexLensStatus.ready); } } function updateCcwInstallBadge() { const badge = document.getElementById('badgeCcwInstall'); if (badge) { if (ccwInstallStatus.installed) { badge.textContent = t('status.installed'); badge.classList.add('text-success'); badge.classList.remove('text-warning', 'text-destructive'); } else if (ccwInstallStatus.workflowsInstalled === false) { badge.textContent = t('status.incomplete'); badge.classList.add('text-warning'); badge.classList.remove('text-success', 'text-destructive'); } else { badge.textContent = t('status.notInstalled'); badge.classList.add('text-destructive'); badge.classList.remove('text-success', 'text-warning'); } } } // ========== Rendering ========== function renderCliStatus() { const container = document.getElementById('cli-status-panel'); if (!container) return; const toolDescriptions = { gemini: 'Google AI for code analysis', qwen: 'Alibaba AI assistant', codex: 'OpenAI code generation', claude: 'Anthropic AI assistant' }; const toolIcons = { gemini: 'sparkle', qwen: 'bot', codex: 'code-2', claude: 'brain' }; const tools = ['gemini', 'qwen', 'codex', 'claude']; const toolsHtml = tools.map(tool => { const status = cliToolStatus[tool] || {}; const isAvailable = status.available; const isDefault = defaultCliTool === tool; const config = cliToolsConfig[tool] || { enabled: true }; const isEnabled = config.enabled !== false; const canSetDefault = isAvailable && isEnabled && !isDefault; return `
${tool.charAt(0).toUpperCase() + tool.slice(1)} ${isDefault ? 'Default' : ''} ${!isEnabled && isAvailable ? 'Disabled' : ''}
${toolDescriptions[tool]}
${isAvailable ? (isEnabled ? ` Ready` : ` Disabled`) : ` Not Installed` }
${isAvailable ? (isEnabled ? `` : `` ) : ''} ${canSetDefault ? `` : '' }
`; }).join(''); // CodexLens card with semantic search info const codexLensHtml = `
CodexLens Index
${codexLensStatus.ready ? 'Code indexing & FTS search' : 'Full-text code search engine'}
${codexLensStatus.ready ? ` v${codexLensStatus.version || 'installed'}` : ` Not Installed` }
${!codexLensStatus.ready ? `` : ` ` }
`; // Semantic Search card (only show if CodexLens is installed) const semanticHtml = codexLensStatus.ready ? `
Semantic Search AI
${semanticStatus.available ? 'AI-powered code understanding' : 'Natural language code search'}
${semanticStatus.available ? ` ${semanticStatus.backend || 'Ready'}` : ` Not Installed` }
${!semanticStatus.available ? `
~130MB
` : `
bge-small-en-v1.5
`}
` : ''; // CCW Installation Status card (show warning if not fully installed) const ccwInstallHtml = !ccwInstallStatus.installed ? `
${t('status.ccwInstall')} ${t('status.required')}
${t('status.ccwInstallDesc')}
${ccwInstallStatus.missingFiles.length} ${t('status.filesMissing')}

${t('status.missingFiles')}:

    ${ccwInstallStatus.missingFiles.slice(0, 3).map(f => `
  • ${f}
  • `).join('')} ${ccwInstallStatus.missingFiles.length > 3 ? `
  • +${ccwInstallStatus.missingFiles.length - 3} more...
  • ` : ''}

${t('status.runToFix')}:

ccw install
` : ''; // API Endpoints section const apiEndpointsHtml = apiEndpoints.length > 0 ? `

API Endpoints

${apiEndpoints.length}
${apiEndpoints.map(ep => `
${ep.id}
${ep.model}
`).join('')}
` : ''; // Config source info const configInfo = window.claudeCliToolsConfig?._configInfo || {}; const configSourceLabel = configInfo.source === 'project' ? 'Project' : configInfo.source === 'global' ? 'Global' : 'Default'; const configSourceClass = configInfo.source === 'project' ? 'text-success' : configInfo.source === 'global' ? 'text-primary' : 'text-muted-foreground'; // CLI Settings section const settingsHtml = `

Settings

${configSourceLabel}

Format for multi-turn conversation concatenation

SQLite

CLI history stored in SQLite with FTS search

Auto-analyze prompt and add relevant file paths

Use native tool resume (gemini -r, qwen --resume, codex resume, claude --resume)

Maximum files to include in smart context

Cache prefix/suffix injection mode for prompts

Code search provider (updates CLAUDE.md context-tools reference)

Current: ${getContextToolsFileName(codeIndexMcpProvider)}

`; container.innerHTML = `

CLI Tools

${ccwInstallHtml}
${toolsHtml} ${codexLensHtml} ${semanticHtml}
${apiEndpointsHtml} ${settingsHtml} `; // Initialize Lucide icons if (window.lucide) { lucide.createIcons(); } } // ========== Actions ========== function setDefaultCliTool(tool) { // Validate: tool must be available and enabled const status = cliToolStatus[tool] || {}; const config = cliToolsConfig[tool] || { enabled: true }; if (!status.available) { showRefreshToast(`Cannot set ${tool} as default: not installed`, 'error'); return; } if (config.enabled === false) { showRefreshToast(`Cannot set ${tool} as default: tool is disabled`, 'error'); return; } defaultCliTool = tool; // Save to config if (window.claudeCliToolsConfig) { window.claudeCliToolsConfig.defaultTool = tool; fetch('/api/cli/tools-config', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ defaultTool: tool }) }).catch(err => console.error('Failed to save default tool:', err)); } renderCliStatus(); showRefreshToast(`Default CLI tool set to ${tool}`, 'success'); } function setPromptFormat(format) { promptConcatFormat = format; localStorage.setItem('ccw-prompt-format', format); showRefreshToast(`Prompt format set to ${format.toUpperCase()}`, 'success'); } function setSmartContextEnabled(enabled) { smartContextEnabled = enabled; localStorage.setItem('ccw-smart-context', enabled.toString()); // Re-render the appropriate settings panel if (typeof renderCliSettingsSection === 'function') { renderCliSettingsSection(); } else { renderCliStatus(); } showRefreshToast(`Smart Context ${enabled ? 'enabled' : 'disabled'}`, 'success'); } function setSmartContextMaxFiles(max) { smartContextMaxFiles = parseInt(max, 10); localStorage.setItem('ccw-smart-context-max-files', max); showRefreshToast(`Smart Context max files set to ${max}`, 'success'); } function setNativeResumeEnabled(enabled) { nativeResumeEnabled = enabled; localStorage.setItem('ccw-native-resume', enabled.toString()); showRefreshToast(`Native Resume ${enabled ? 'enabled' : 'disabled'}`, 'success'); } function setRecursiveQueryEnabled(enabled) { recursiveQueryEnabled = enabled; localStorage.setItem('ccw-recursive-query', enabled.toString()); showRefreshToast(`Recursive Query ${enabled ? 'enabled' : 'disabled'}`, 'success'); } function getCacheInjectionMode() { if (window.claudeCliToolsConfig && window.claudeCliToolsConfig.settings) { return window.claudeCliToolsConfig.settings.cache?.injectionMode || 'auto'; } return localStorage.getItem('ccw-cache-injection-mode') || 'auto'; } async function setCacheInjectionMode(mode) { try { const response = await fetch('/api/cli/tools-config/cache', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ injectionMode: mode }) }); if (response.ok) { localStorage.setItem('ccw-cache-injection-mode', mode); if (window.claudeCliToolsConfig) { window.claudeCliToolsConfig.settings.cache.injectionMode = mode; } showRefreshToast(`Cache injection mode set to ${mode}`, 'success'); } else { showRefreshToast('Failed to update cache settings', 'error'); } } catch (err) { console.error('Failed to update cache settings:', err); showRefreshToast('Failed to update cache settings', 'error'); } } async function setCodeIndexMcpProvider(provider) { try { const response = await fetch('/api/cli/code-index-mcp', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider: provider }) }); if (response.ok) { codeIndexMcpProvider = provider; if (window.claudeCliToolsConfig && window.claudeCliToolsConfig.settings) { window.claudeCliToolsConfig.settings.codeIndexMcp = provider; } const providerName = provider === 'ace' ? 'ACE (Augment)' : provider === 'none' ? 'None (Built-in only)' : 'CodexLens'; showRefreshToast(`Code Index MCP switched to ${providerName}`, 'success'); // Re-render both CLI status and settings section if (typeof renderCliStatus === 'function') renderCliStatus(); if (typeof renderCliSettingsSection === 'function') renderCliSettingsSection(); } else { const data = await response.json(); showRefreshToast(`Failed to switch Code Index MCP: ${data.error}`, 'error'); } } catch (err) { console.error('Failed to switch Code Index MCP:', err); showRefreshToast('Failed to switch Code Index MCP', 'error'); } } async function refreshAllCliStatus() { await loadAllStatuses(); renderCliStatus(); } async function toggleCliTool(tool, enabled) { // If disabling the current default tool, switch to another available+enabled tool if (!enabled && defaultCliTool === tool) { const tools = ['gemini', 'qwen', 'codex', 'claude']; const newDefault = tools.find(t => { if (t === tool) return false; const status = cliToolStatus[t] || {}; const config = cliToolsConfig[t] || { enabled: true }; return status.available && config.enabled !== false; }); if (newDefault) { defaultCliTool = newDefault; if (window.claudeCliToolsConfig) { window.claudeCliToolsConfig.defaultTool = newDefault; } showRefreshToast(`Default tool switched to ${newDefault}`, 'info'); } else { showRefreshToast(`Warning: No other enabled tool available for default`, 'warning'); } } await updateCliToolEnabled(tool, enabled); await loadAllStatuses(); renderCliStatus(); } function installCodexLens() { openCodexLensInstallWizard(); } function openCodexLensInstallWizard() { const modal = document.createElement('div'); modal.id = 'codexlensInstallModal'; modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; modal.innerHTML = `

Install CodexLens

Python-based code indexing engine

What will be installed:

  • Python virtual environment - Isolated Python environment
  • CodexLens package - Code indexing and search engine
  • SQLite FTS5 - Full-text search database

Installation Location

~/.codexlens/venv

First installation may take 2-3 minutes to download and setup Python packages.

`; document.body.appendChild(modal); if (window.lucide) { lucide.createIcons(); } } function closeCodexLensInstallWizard() { const modal = document.getElementById('codexlensInstallModal'); if (modal) { modal.remove(); } } async function startCodexLensInstall() { const progressDiv = document.getElementById('codexlensInstallProgress'); const installBtn = document.getElementById('codexlensInstallBtn'); const statusText = document.getElementById('codexlensInstallStatus'); const progressBar = document.getElementById('codexlensProgressBar'); // Show progress, disable button progressDiv.classList.remove('hidden'); installBtn.disabled = true; installBtn.innerHTML = 'Installing...'; // Simulate progress stages const stages = [ { progress: 10, text: 'Creating virtual environment...' }, { progress: 30, text: 'Installing pip packages...' }, { progress: 50, text: 'Installing CodexLens package...' }, { progress: 70, text: 'Setting up Python dependencies...' }, { progress: 90, text: 'Finalizing installation...' } ]; let currentStage = 0; const progressInterval = setInterval(() => { if (currentStage < stages.length) { statusText.textContent = stages[currentStage].text; progressBar.style.width = `${stages[currentStage].progress}%`; currentStage++; } }, 1500); try { const response = await fetch('/api/codexlens/bootstrap', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); clearInterval(progressInterval); const result = await response.json(); if (result.success) { progressBar.style.width = '100%'; statusText.textContent = 'Installation complete!'; setTimeout(() => { closeCodexLensInstallWizard(); showRefreshToast('CodexLens installed successfully!', 'success'); loadCodexLensStatus().then(() => renderCliStatus()); }, 1000); } else { statusText.textContent = `Error: ${result.error}`; progressBar.classList.add('bg-destructive'); installBtn.disabled = false; installBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } catch (err) { clearInterval(progressInterval); statusText.textContent = `Error: ${err.message}`; progressBar.classList.add('bg-destructive'); installBtn.disabled = false; installBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } function uninstallCodexLens() { openCodexLensUninstallWizard(); } function openCodexLensUninstallWizard() { const modal = document.createElement('div'); modal.id = 'codexlensUninstallModal'; modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; modal.innerHTML = `

Uninstall CodexLens

Remove CodexLens and all data

What will be removed:

  • Virtual environment at ~/.codexlens/venv
  • All CodexLens indexed data and databases
  • Configuration and semantic search models

Warning

This action cannot be undone. All indexed code data will be permanently deleted.

`; document.body.appendChild(modal); if (window.lucide) { lucide.createIcons(); } } function closeCodexLensUninstallWizard() { const modal = document.getElementById('codexlensUninstallModal'); if (modal) { modal.remove(); } } async function startCodexLensUninstall() { const progressDiv = document.getElementById('codexlensUninstallProgress'); const uninstallBtn = document.getElementById('codexlensUninstallBtn'); const statusText = document.getElementById('codexlensUninstallStatus'); const progressBar = document.getElementById('codexlensUninstallProgressBar'); // Show progress, disable button progressDiv.classList.remove('hidden'); uninstallBtn.disabled = true; uninstallBtn.innerHTML = 'Uninstalling...'; // Simulate progress stages const stages = [ { progress: 25, text: 'Removing virtual environment...' }, { progress: 50, text: 'Deleting indexed data...' }, { progress: 75, text: 'Cleaning up configuration...' }, { progress: 90, text: 'Finalizing removal...' } ]; let currentStage = 0; const progressInterval = setInterval(() => { if (currentStage < stages.length) { statusText.textContent = stages[currentStage].text; progressBar.style.width = `${stages[currentStage].progress}%`; currentStage++; } }, 500); try { const response = await fetch('/api/codexlens/uninstall', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); clearInterval(progressInterval); const result = await response.json(); if (result.success) { progressBar.style.width = '100%'; statusText.textContent = 'Uninstallation complete!'; setTimeout(() => { closeCodexLensUninstallWizard(); showRefreshToast('CodexLens uninstalled successfully!', 'success'); loadCodexLensStatus().then(() => renderCliStatus()); }, 1000); } else { statusText.textContent = `Error: ${result.error}`; progressBar.classList.remove('bg-destructive'); progressBar.classList.add('bg-destructive'); uninstallBtn.disabled = false; uninstallBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } catch (err) { clearInterval(progressInterval); statusText.textContent = `Error: ${err.message}`; progressBar.classList.remove('bg-destructive'); progressBar.classList.add('bg-destructive'); uninstallBtn.disabled = false; uninstallBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } async function initCodexLensIndex() { // Get current workspace path from multiple sources let targetPath = null; // Helper function to check if path is valid const isValidPath = (path) => { return path && typeof path === 'string' && path.length > 0 && (path.includes('/') || path.includes('\\')) && !path.startsWith('{{') && !path.endsWith('}}'); }; console.log('[CodexLens] Attempting to get project path...'); // Try 1: Global projectPath variable if (isValidPath(projectPath)) { targetPath = projectPath; console.log('[CodexLens] ✓ Using global projectPath:', targetPath); } // Try 2: Get from workflowData if (!targetPath && typeof workflowData !== 'undefined' && workflowData && isValidPath(workflowData.projectPath)) { targetPath = workflowData.projectPath; console.log('[CodexLens] ✓ Using workflowData.projectPath:', targetPath); } // Try 3: Get from current path display element if (!targetPath) { const currentPathEl = document.getElementById('currentPath'); if (currentPathEl && currentPathEl.textContent) { const pathText = currentPathEl.textContent.trim(); if (isValidPath(pathText)) { targetPath = pathText; console.log('[CodexLens] ✓ Using currentPath element text:', targetPath); } } } // Final validation if (!targetPath) { showRefreshToast('Error: No workspace loaded. Please open a workspace first.', 'error'); console.error('[CodexLens] No valid project path available'); console.error('[CodexLens] Attempted sources: projectPath:', projectPath, 'workflowData:', workflowData); return; } showRefreshToast('Initializing CodexLens index...', 'info'); console.log('[CodexLens] Initializing index for path:', targetPath); try { const response = await fetch('/api/codexlens/init', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: targetPath }) }); const result = await response.json(); console.log('[CodexLens] Init result:', result); if (result.success) { let data = null; // Try to parse nested JSON in output field if (result.output && typeof result.output === 'string') { try { // Extract JSON from output (it may contain other text before the JSON) const jsonMatch = result.output.match(/\{[\s\S]*\}/); if (jsonMatch) { const parsed = JSON.parse(jsonMatch[0]); data = parsed.result || parsed; console.log('[CodexLens] Parsed from output:', data); } } catch (e) { console.warn('[CodexLens] Failed to parse output as JSON:', e); } } // Fallback to direct result field if (!data) { data = result.result?.result || result.result || result; } const files = data.files_indexed || 0; const dirs = data.dirs_indexed || 0; const symbols = data.symbols_indexed || 0; console.log('[CodexLens] Parsed data:', { files, dirs, symbols }); if (files === 0 && dirs === 0) { showRefreshToast(`Warning: No files indexed. Path: ${targetPath}`, 'warning'); console.warn('[CodexLens] No files indexed. Full data:', data); } else { showRefreshToast(`Index created: ${files} files, ${dirs} directories`, 'success'); console.log('[CodexLens] Index created successfully'); // Reload CodexLens status and refresh the view loadCodexLensStatus().then(() => renderCliStatus()); } } else { showRefreshToast(`Init failed: ${result.error}`, 'error'); console.error('[CodexLens] Init error:', result.error); } } catch (err) { showRefreshToast(`Init error: ${err.message}`, 'error'); console.error('[CodexLens] Exception:', err); } } // ========== Semantic Search Installation Wizard ========== function openSemanticInstallWizard() { const modal = document.createElement('div'); modal.id = 'semanticInstallModal'; modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; modal.innerHTML = `

Install Semantic Search

AI-powered code understanding

What will be installed:

  • sentence-transformers - ML framework
  • bge-small-en-v1.5 - Embedding model (~130MB)

Download Size

Total size: ~130MB. First-time model loading may take a few minutes.

`; document.body.appendChild(modal); // Initialize Lucide icons in modal if (window.lucide) { lucide.createIcons(); } } function closeSemanticInstallWizard() { const modal = document.getElementById('semanticInstallModal'); if (modal) { modal.remove(); } } async function startSemanticInstall() { const progressDiv = document.getElementById('semanticInstallProgress'); const installBtn = document.getElementById('semanticInstallBtn'); const statusText = document.getElementById('semanticInstallStatus'); const progressBar = document.getElementById('semanticProgressBar'); // Show progress, disable button progressDiv.classList.remove('hidden'); installBtn.disabled = true; installBtn.innerHTML = 'Installing...'; // Simulate progress stages const stages = [ { progress: 20, text: 'Installing sentence-transformers...' }, { progress: 50, text: 'Downloading embedding model...' }, { progress: 80, text: 'Setting up model cache...' }, { progress: 95, text: 'Finalizing installation...' } ]; let currentStage = 0; const progressInterval = setInterval(() => { if (currentStage < stages.length) { statusText.textContent = stages[currentStage].text; progressBar.style.width = `${stages[currentStage].progress}%`; currentStage++; } }, 2000); try { const response = await fetch('/api/codexlens/semantic/install', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); clearInterval(progressInterval); const result = await response.json(); if (result.success) { progressBar.style.width = '100%'; statusText.textContent = 'Installation complete!'; setTimeout(() => { closeSemanticInstallWizard(); showRefreshToast('Semantic search installed successfully!', 'success'); loadSemanticStatus().then(() => renderCliStatus()); }, 1000); } else { statusText.textContent = `Error: ${result.error}`; progressBar.classList.add('bg-destructive'); installBtn.disabled = false; installBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } catch (err) { clearInterval(progressInterval); statusText.textContent = `Error: ${err.message}`; progressBar.classList.add('bg-destructive'); installBtn.disabled = false; installBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } }