diff --git a/ccw/src/templates/dashboard-js/views/codexlens-manager.js b/ccw/src/templates/dashboard-js/views/codexlens-manager.js
index 07f46b45..816cea39 100644
--- a/ccw/src/templates/dashboard-js/views/codexlens-manager.js
+++ b/ccw/src/templates/dashboard-js/views/codexlens-manager.js
@@ -6381,12 +6381,12 @@ async function showWatcherControlModal() {
// Get first indexed project path as default
let defaultPath = '';
- if (indexes.success && indexes.projects && indexes.projects.length > 0) {
- // Sort by last_indexed desc and pick the most recent
- const sorted = indexes.projects.sort((a, b) =>
- new Date(b.last_indexed || 0) - new Date(a.last_indexed || 0)
+ if (indexes.success && indexes.indexes && indexes.indexes.length > 0) {
+ // Sort by lastModified desc and pick the most recent
+ const sorted = indexes.indexes.sort((a, b) =>
+ new Date(b.lastModified || 0) - new Date(a.lastModified || 0)
);
- defaultPath = sorted[0].source_root || '';
+ defaultPath = sorted[0].path || '';
}
const modalHtml = buildWatcherControlContent(status, defaultPath);
diff --git a/ccw/src/templates/dashboard-js/views/skills-manager.js b/ccw/src/templates/dashboard-js/views/skills-manager.js
index bc84f50b..a8abcbc2 100644
--- a/ccw/src/templates/dashboard-js/views/skills-manager.js
+++ b/ccw/src/templates/dashboard-js/views/skills-manager.js
@@ -956,15 +956,13 @@ function renderSkillFileModal() {
-
+
${isEditing ? `
` : `
-
-
${escapeHtml(content)}
-
+
${escapeHtml(content)}
`}
diff --git a/codex-lens/src/codexlens/cli/commands.py b/codex-lens/src/codexlens/cli/commands.py
index e845b902..ebf81101 100644
--- a/codex-lens/src/codexlens/cli/commands.py
+++ b/codex-lens/src/codexlens/cli/commands.py
@@ -3645,6 +3645,84 @@ def index_status(
console.print(f" SPLADE encoder: {'[green]Yes[/green]' if splade_available else f'[red]No[/red] ({splade_err})'}")
+# ==================== Index Update Command ====================
+
+@index_app.command("update")
+def index_update(
+ file_path: Path = typer.Argument(..., exists=True, file_okay=True, dir_okay=False, help="Path to the file to update in the index."),
+ json_mode: bool = typer.Option(False, "--json", help="Output JSON response."),
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable debug logging."),
+) -> None:
+ """Update the index for a single file incrementally.
+
+ This is a lightweight command designed for use in hooks (e.g., Claude Code PostToolUse).
+ It updates only the specified file without scanning the entire directory.
+
+ The file's parent directory must already be indexed via 'codexlens index init'.
+
+ Examples:
+ codexlens index update src/main.py # Update single file
+ codexlens index update ./foo.ts --json # JSON output for hooks
+ """
+ _configure_logging(verbose, json_mode)
+
+ from codexlens.watcher.incremental_indexer import IncrementalIndexer
+
+ registry: RegistryStore | None = None
+ indexer: IncrementalIndexer | None = None
+
+ try:
+ registry = RegistryStore()
+ registry.initialize()
+ mapper = PathMapper()
+ config = Config()
+
+ resolved_path = file_path.resolve()
+
+ # Check if project is indexed
+ source_root = mapper.get_project_root(resolved_path)
+ if not source_root or not registry.get_project(source_root):
+ error_msg = f"Project containing file is not indexed: {file_path}"
+ if json_mode:
+ print_json(success=False, error=error_msg)
+ else:
+ console.print(f"[red]Error:[/red] {error_msg}")
+ console.print("[dim]Run 'codexlens index init' on the project root first.[/dim]")
+ raise typer.Exit(code=1)
+
+ indexer = IncrementalIndexer(registry, mapper, config)
+ result = indexer._index_file(resolved_path)
+
+ if result.success:
+ if json_mode:
+ print_json(success=True, result={
+ "path": str(result.path),
+ "symbols_count": result.symbols_count,
+ "status": "updated",
+ })
+ else:
+ console.print(f"[green]✓[/green] Updated index for [bold]{result.path.name}[/bold] ({result.symbols_count} symbols)")
+ else:
+ error_msg = result.error or f"Failed to update index for {file_path}"
+ if json_mode:
+ print_json(success=False, error=error_msg)
+ else:
+ console.print(f"[red]Error:[/red] {error_msg}")
+ raise typer.Exit(code=1)
+
+ except CodexLensError as exc:
+ if json_mode:
+ print_json(success=False, error=str(exc))
+ else:
+ console.print(f"[red]Update failed:[/red] {exc}")
+ raise typer.Exit(code=1)
+ finally:
+ if indexer:
+ indexer.close()
+ if registry:
+ registry.close()
+
+
# ==================== Index All Command ====================
@index_app.command("all")