feat(codexlens): add CodexLens code indexing platform with incremental updates

- Add CodexLens Python package with SQLite FTS5 search and tree-sitter parsing
- Implement workspace-local index storage (.codexlens/ directory)
- Add incremental update CLI command for efficient file-level index refresh
- Integrate CodexLens with CCW tools (codex_lens action: update)
- Add CodexLens Auto-Sync hook template for automatic index updates on file changes
- Add CodexLens status card in CCW Dashboard CLI Manager with install/init buttons
- Add server APIs: /api/codexlens/status, /api/codexlens/bootstrap, /api/codexlens/init

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
catlog22
2025-12-12 15:02:32 +08:00
parent b74a90b416
commit a393601ec5
31 changed files with 2718 additions and 27 deletions

View File

@@ -3,12 +3,14 @@
// ========== CLI State ==========
let cliToolStatus = { gemini: {}, qwen: {}, codex: {} };
let codexLensStatus = { ready: false };
let defaultCliTool = 'gemini';
// ========== Initialization ==========
function initCliStatus() {
// Load CLI status on init
loadCliToolStatus();
loadCodexLensStatus();
}
// ========== Data Loading ==========
@@ -29,6 +31,23 @@ async function loadCliToolStatus() {
}
}
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;
// Update CodexLens badge
updateCodexLensBadge();
return data;
} catch (err) {
console.error('Failed to load CodexLens status:', err);
return null;
}
}
// ========== Badge Update ==========
function updateCliBadge() {
const badge = document.getElementById('badgeCliTools');
@@ -42,6 +61,15 @@ function updateCliBadge() {
}
}
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);
}
}
// ========== Rendering ==========
function renderCliStatus() {
const container = document.getElementById('cli-status-panel');
@@ -75,15 +103,39 @@ function renderCliStatus() {
`;
}).join('');
// CodexLens card
const codexLensHtml = `
<div class="cli-tool-card tool-codexlens ${codexLensStatus.ready ? 'available' : 'unavailable'}">
<div class="cli-tool-header">
<span class="cli-tool-status ${codexLensStatus.ready ? 'status-available' : 'status-unavailable'}"></span>
<span class="cli-tool-name">CodexLens</span>
<span class="badge px-1.5 py-0.5 text-xs rounded bg-muted text-muted-foreground">Index</span>
</div>
<div class="cli-tool-info">
${codexLensStatus.ready
? `<span class="text-success">v${codexLensStatus.version || 'installed'}</span>`
: `<span class="text-muted-foreground">Not Installed</span>`
}
</div>
<div class="cli-tool-actions flex gap-2 mt-2">
${!codexLensStatus.ready
? `<button class="btn-sm btn-primary" onclick="installCodexLens()">Install</button>`
: `<button class="btn-sm btn-outline" onclick="initCodexLensIndex()">Init Index</button>`
}
</div>
</div>
`;
container.innerHTML = `
<div class="cli-status-header">
<h3><i data-lucide="terminal" class="w-4 h-4"></i> CLI Tools</h3>
<button class="btn-icon" onclick="loadCliToolStatus()" title="Refresh">
<button class="btn-icon" onclick="refreshAllCliStatus()" title="Refresh">
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
</button>
</div>
<div class="cli-tools-grid">
${toolsHtml}
${codexLensHtml}
</div>
`;
@@ -99,3 +151,55 @@ function setDefaultCliTool(tool) {
renderCliStatus();
showRefreshToast(`Default CLI tool set to ${tool}`, 'success');
}
async function refreshAllCliStatus() {
await Promise.all([loadCliToolStatus(), loadCodexLensStatus()]);
renderCliStatus();
}
async function installCodexLens() {
showRefreshToast('Installing CodexLens...', 'info');
try {
const response = await fetch('/api/codexlens/bootstrap', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
const result = await response.json();
if (result.success) {
showRefreshToast('CodexLens installed successfully!', 'success');
await loadCodexLensStatus();
renderCliStatus();
} else {
showRefreshToast(`Install failed: ${result.error}`, 'error');
}
} catch (err) {
showRefreshToast(`Install error: ${err.message}`, 'error');
}
}
async function initCodexLensIndex() {
showRefreshToast('Initializing CodexLens index...', 'info');
try {
const response = await fetch('/api/codexlens/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: projectPath })
});
const result = await response.json();
if (result.success) {
const data = result.result?.result || result.result || result;
const files = data.files_indexed || 0;
const symbols = data.symbols_indexed || 0;
showRefreshToast(`Index created: ${files} files, ${symbols} symbols`, 'success');
} else {
showRefreshToast(`Init failed: ${result.error}`, 'error');
}
} catch (err) {
showRefreshToast(`Init error: ${err.message}`, 'error');
}
}