How to call CodeLens from an AI agent, harness, or service. For the command catalog and JSON output shapes, see SKILL.md (full) and SKILL-QUICK.md (cheat sheet) — this guide covers the mechanics of integration and deliberately does not duplicate the catalog, so the two can't drift apart.
Command model (read first). CodeLens has 12 umbrella commands (
scan,search,context,deps,audit,security,summary,impact,api-map,doctor,history,graph). Every legacy leaf command (query,smell,dead-code,secrets,trace,circular,a11y,init,serve,watch,detect,refactor-safe, …) is now either a--check <sub-mode>under an umbrella or has been removed. Invoking a dropped name gives argparseinvalid choice. See the old→new map in SKILL.md.
| Method | When | Notes |
|---|---|---|
| CLI subprocess | Default for agents in any language | Stable JSON contract via --format json. Most robust. |
| MCP server | Agent host speaks MCP (Claude Desktop, Cursor, VS Code Copilot, Continue.dev, Cline) | 12 tools, one per umbrella, auto-discovered. Point the client at scripts/mcp_server.py; see mcp_config.json. There is no codelens serve — the client launches the server. |
| Python direct import | Same-process Python agent that wants dict results without a subprocess | Import from scripts/; call umbrella execute(args, workspace) or the graph_model API (see §7). Skips process overhead but couples you to internals. |
codelens scan . # build the graph once (auto-inits; no separate `init`)
codelens <umbrella> <workspace> --check <sub> --format jsonscan must have run once for the workspace (it builds .codelens/). Any other
registry-consuming command auto-builds the graph on first use if it is missing
(capped at 3000 files on auto-setup — run scan manually on large repos for a
full graph).
Python:
import json, subprocess
def codelens(*args, workspace="."):
proc = subprocess.run(
["codelens", *args, "--format", "json"],
cwd=workspace, capture_output=True, text=True,
encoding="utf-8", errors="replace", # required on Windows (cp1252 default)
timeout=300,
)
# stdout is a single JSON document; warnings/logs go to stderr.
return json.loads(proc.stdout)
dead = codelens("audit", ".", "--check", "dead-code")
callers = codelens("context", ".", "--check", "trace", "--name", "handleAuth", "--direction", "up")
secrets = codelens("security", ".", "--check", "secrets")Node.js:
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const run = promisify(execFile);
async function codelens(args, { cwd = "." } = {}) {
const { stdout } = await run("codelens", [...args, "--format", "json"],
{ cwd, maxBuffer: 64 * 1024 * 1024, timeout: 300_000 });
return JSON.parse(stdout);
}| Operation | Suggested timeout |
|---|---|
First scan (full graph build) |
300s (large repos: run once, out of band) |
scan --incremental |
60s |
| Read/query commands (search, context, audit, security, …) | 60–120s |
security --check vuln-scan (network to OSV) |
120s |
First scan is slow by design — it builds the SQLite graph. Subsequent scans are incremental. "Slow first indexing" is expected, not a bug.
--format json— the stable machine contract. stdout is one JSON document; deprecation warnings and progress lines go to stderr — capture the two separately, or read stdout only.- Umbrella commands return an envelope:
{"s": "ok", "st": {…}, "r": [ {…, "_check": name}, … ]}(one entry inrper sub-check run). Single-purpose commands return their own top-level shape. Exact shapes: see SKILL.md. - Other formats:
--format compact(single-char keys, ~50% smaller than json),ai(normalized{stats, items[], truncated}),markdown,sarif,graphml, plusjunit-xml/emacs/vim/gitlab-sastfor CI. Do not pass-f— the short flag is reserved to avoid an argparse conflict; use--format.
| Flag | Effect |
|---|---|
--lite |
Minimal, per-command-tailored output |
--max-tokens N |
Truncate to ~N tokens |
--top N / --limit N --offset N |
Keep/paginate list items (list-type results carry total_count/count/offset/limit/has_more) |
--detail minimal |
(summary) critical-severity only |
--format compact |
Single-char keys + abbreviated node/edge types (map in scripts/formatters/compact.py) |
| Symptom | Meaning | Recovery |
|---|---|---|
invalid choice: '<x>' |
Using a dropped/legacy top-level command | Map to the umbrella + --check (see SKILL.md) |
Result status: no_registry / empty |
No .codelens/ graph yet |
Run codelens scan <workspace>, retry |
context --check trace returns 0 |
Symbol name not found, or stale graph | search --mode symbol to find the exact name; scan --incremental if code changed |
security --check vuln-scan empty |
No lockfile present | Not an error — nothing to scan |
history --check ownership degraded |
No git repo | Falls back to mtime-based analysis |
Non-zero exit from check (hidden CI gate) |
Quality gate tripped on findings | Expected — inspect findings, not a crash |
Corrupt .codelens/ (JSONDecodeError) |
Interrupted write | Delete .codelens/, re-scan |
Recovery pattern: on no_registry/empty → scan once, retry; on invalid choice → translate the command, retry; a missing tree-sitter grammar degrades to
the regex parser automatically (no agent action needed).
- Registry is read-shared. Many agents can run read commands (search,
context, audit, security, …) against the same
.codelens/concurrently. scanis the sole writer. Serialize scans; never run two scans on one workspace at once. Run onescanup front, then fan out read commands.- Parallel-safe: any mix of read-only sub-checks on a scanned workspace —
e.g.
security --check secrets∥audit --check complexity∥deps --check circular. - Sequential:
scan→ (everything else); after code edits,scan --incrementalbefore re-reading.
| Situation | Behaviour |
|---|---|
| Empty workspace | scan returns zeroed files_scanned; reads return empty, not errors |
| No git | History/ownership degrade to mtime; no crash |
| Monorepo (multiple package.json) | Scanned as one workspace; scope with --diff-base <ref> to a changed subset |
| TypeScript/TSX-only | Fully supported (tree-sitter TS/TSX). .ts routing follows the frontend/backend paths in .codelens/codelens.config.json |
| Large repo | First scan capped at 3000 files on auto-setup; run scan explicitly for the full graph |
| Incremental scan + empty graph | Incremental scans don't repopulate graph_nodes/graph_edges — run a full scan when structural graph queries return empty (issue #25) |
For same-process Python agents. Add scripts/ to sys.path, then either call an
umbrella's execute(args, workspace) or query the graph directly.
The graph tables (graph_nodes + graph_edges) live in
.codelens/codelens.db, rebuilt from backend.json on every full scan.
Prefer this over iterating the flat registry for callers/callees/cycles.
import sys
sys.path.insert(0, "/path/to/codelens/scripts")
from graph_model import (
find_nodes_by_name, query_callers, query_callees,
graph_tables_populated, graph_stats, default_db_path,
)
db = default_db_path(workspace) # <workspace>/.codelens/codelens.db
if not graph_tables_populated(db):
... # run a full scan first
nodes = find_nodes_by_name("handleAuth", db) # case-insensitive / fuzzy
callers = query_callers(nodes[0]["node_id"], db, max_depth=3) # reverse BFS over CALLS
callees = query_callees(nodes[0]["node_id"], db, max_depth=3) # forward BFS over CALLS
stats = graph_stats(db) # node/edge counts + type distributionModule-level callers use a synthetic id "<file>:0:<module>" — recognise them
with graph_model.is_module_level_source_id().
import json, os
def read_registry(workspace):
d = os.path.join(workspace, ".codelens")
out = {}
for key, fn in (("frontend","frontend.json"), ("backend","backend.json"),
("config","codelens.config.json")):
p = os.path.join(d, fn)
out[key] = json.load(open(p, encoding="utf-8")) if os.path.exists(p) else None
return out.codelens/ layout: codelens.config.json (config) · frontend.json
(classes+ids) · backend.json (nodes+edges) · codelens.db (SQLite:
symbols/refs/graph) · mtimes.json (incremental-scan cache).
reference_count/ caller count is popularity, not importance. A function called once in a payment path can matter more than a util called 50×. Judge importance withcontext --check trace --direction up+ the call context (auth, payment, entry point), cross-checked withimpact.status: dead≠ safe to delete. Entry points (HTTP handlers, CLI subcommands, exported API) often have no inbound edges yet are critical.audit --check dead-codeannotatesdeletion_safety— trust that over raw edge counts.