@ulpi/codemap 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -363,6 +363,46 @@ Add to `~/.config/zed/settings.json`:
363
363
  }
364
364
  ```
365
365
 
366
+ ### Multi-Project Setup
367
+
368
+ You can register multiple CodeMap MCP servers — one per codebase — so your AI agent can search across your frontend, backend, shared libraries, or any other project simultaneously. Each server indexes and serves its own project independently.
369
+
370
+ First, index each project:
371
+
372
+ ```bash
373
+ codemap init && codemap index # current project
374
+ cd /path/to/frontend && codemap init && codemap index # another project
375
+ ```
376
+
377
+ Then register them as MCP servers. The current project uses `codemap serve` with no extra arguments. Other projects use `--cwd` to point to their location:
378
+
379
+ #### Claude Code
380
+
381
+ Add to `.mcp.json` in your project root:
382
+
383
+ ```json
384
+ {
385
+ "mcpServers": {
386
+ "codemap": {
387
+ "command": "codemap",
388
+ "args": ["serve"]
389
+ },
390
+ "codemap-frontend": {
391
+ "command": "codemap",
392
+ "args": ["serve", "--cwd", "/path/to/frontend"]
393
+ }
394
+ }
395
+ }
396
+ ```
397
+
398
+ `"codemap"` serves the current project automatically. `"codemap-frontend"` serves a different codebase via `--cwd`. Add as many as you need — each gets its own set of 12 tools (`search_code`, `get_dependencies`, etc.) scoped to that project.
399
+
400
+ #### Other Editors
401
+
402
+ The same pattern works in any editor — add one entry with no `--cwd` for the local project, and additional entries with `--cwd` for other codebases. Use unique names (e.g. `codemap-api`, `codemap-shared`) so your AI agent can tell them apart.
403
+
404
+ Your AI agent will then have access to tools like `search_code`, `get_dependencies`, and `search_symbols` across all registered projects, making it easy to trace connections across your stack.
405
+
366
406
  ## MCP Tools Reference
367
407
 
368
408
  | Tool | Description |
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import "./chunk-WR342MP3.js";
5
5
  import "./chunk-2OBAJYRP.js";
6
6
 
7
7
  // src/index.ts
8
- import path13 from "path";
8
+ import path14 from "path";
9
9
  import { fileURLToPath } from "url";
10
10
 
11
11
  // ../../packages/intelligence/codemap-engine/dist/index.js
@@ -4250,6 +4250,19 @@ function releaseCodemapLock(projectDir, branch) {
4250
4250
  } catch {
4251
4251
  }
4252
4252
  }
4253
+ function isCodemapLocked(projectDir, branch) {
4254
+ const file = lockPath(projectDir, branch);
4255
+ const info = readLock(file);
4256
+ if (!info) return false;
4257
+ if (isStale(info)) {
4258
+ try {
4259
+ fs6.unlinkSync(file);
4260
+ } catch {
4261
+ }
4262
+ return false;
4263
+ }
4264
+ return true;
4265
+ }
4253
4266
  function saveManifest(branchDir, manifest) {
4254
4267
  fs7.mkdirSync(branchDir, { recursive: true });
4255
4268
  const file = path7.join(branchDir, "manifest.json");
@@ -6164,13 +6177,14 @@ function registerStatus(program2) {
6164
6177
  const projectDir = program2.opts().cwd || process.cwd();
6165
6178
  const branch = getCurrentBranch(projectDir);
6166
6179
  const statsFile = codemapStatsFile(projectDir, branch, getDataDir2());
6180
+ const indexing = isCodemapLocked(projectDir, branch);
6167
6181
  let stats = null;
6168
6182
  try {
6169
6183
  stats = JSON.parse(fs14.readFileSync(statsFile, "utf-8"));
6170
6184
  } catch {
6171
6185
  }
6172
6186
  if (opts.json) {
6173
- console.log(JSON.stringify({ projectDir, branch, stats }, null, 2));
6187
+ console.log(JSON.stringify({ projectDir, branch, stats, indexing }, null, 2));
6174
6188
  return;
6175
6189
  }
6176
6190
  console.log(chalk6.bold("Codemap Status"));
@@ -6179,6 +6193,10 @@ function registerStatus(program2) {
6179
6193
  console.log(`Branch: ${branch}`);
6180
6194
  console.log(`Data dir: ${getDataDir2()}`);
6181
6195
  console.log();
6196
+ if (indexing) {
6197
+ console.log(chalk6.yellow("Indexing in progress..."));
6198
+ console.log();
6199
+ }
6182
6200
  if (!stats) {
6183
6201
  console.log(chalk6.yellow("No index found. Run `codemap index` first."));
6184
6202
  return;
@@ -6869,13 +6887,10 @@ function registerCoupling(program2) {
6869
6887
  const projectDir = program2.opts().cwd || process.cwd();
6870
6888
  const branch = getCurrentBranch(projectDir);
6871
6889
  const graphFile = depgraphGraphFile(projectDir, branch, getDataDir2());
6872
- let graph;
6873
- try {
6874
- graph = loadGraph(graphFile);
6875
- } catch {
6890
+ const graph = loadGraph(graphFile);
6891
+ if (!graph) {
6876
6892
  console.error(chalk14.red("Dependency graph not available. Run `codemap index` first."));
6877
6893
  process.exit(1);
6878
- return;
6879
6894
  }
6880
6895
  let coupling = computeCoupling(graph);
6881
6896
  if (opts.module) {
@@ -6914,13 +6929,10 @@ function registerGraphStats(program2) {
6914
6929
  program2.command("graph-stats").description("Show aggregate dependency graph statistics").option("--json", "Output as JSON").action(async (opts) => {
6915
6930
  const projectDir = program2.opts().cwd || process.cwd();
6916
6931
  const branch = getCurrentBranch(projectDir);
6917
- let metrics;
6918
- try {
6919
- metrics = loadMetrics(depgraphMetricsFile(projectDir, branch, getDataDir2()));
6920
- } catch {
6932
+ const metrics = loadMetrics(depgraphMetricsFile(projectDir, branch, getDataDir2()));
6933
+ if (!metrics) {
6921
6934
  console.error(chalk15.red("Dependency metrics not available. Run `codemap index` first."));
6922
6935
  process.exit(1);
6923
- return;
6924
6936
  }
6925
6937
  const result = {
6926
6938
  totalFiles: metrics.totalFiles,
@@ -7010,9 +7022,239 @@ function registerRebuild(program2) {
7010
7022
  });
7011
7023
  }
7012
7024
 
7025
+ // src/commands/statusline.ts
7026
+ import chalk17 from "chalk";
7027
+ import fs18 from "fs";
7028
+ import path13 from "path";
7029
+ import { homedir as homedir3 } from "os";
7030
+
7031
+ // src/commands/statusline-script.ts
7032
+ function buildStatuslineScript(originalCommand) {
7033
+ const escapedCmd = originalCommand.replace(/'/g, "'\\''");
7034
+ const originalSection = originalCommand ? `ORIGINAL_CMD='${escapedCmd}'
7035
+ echo "$SESSION_JSON" | eval "$ORIGINAL_CMD" 2>/dev/null || true` : "# (none)";
7036
+ return `#!/bin/bash
7037
+ # CodeMap statusline for Claude Code
7038
+ # Generated by: codemap statusline
7039
+ # Do not edit \u2014 re-run \`codemap statusline\` to regenerate.
7040
+
7041
+ set -euo pipefail
7042
+
7043
+ # Read session JSON from stdin (Claude Code pipes it)
7044
+ SESSION_JSON=$(cat)
7045
+
7046
+ # --- Original statusline ---
7047
+ ${originalSection}
7048
+
7049
+ # --- CodeMap status ---
7050
+ # Resolve the working directory from session JSON
7051
+ current_dir=$(echo "$SESSION_JSON" | ${jsonExtract("cwd")} 2>/dev/null || pwd)
7052
+
7053
+ # Run codemap status --json for the project directory
7054
+ cm_json=$(codemap status --json --cwd "$current_dir" 2>/dev/null || true)
7055
+
7056
+ if [ -z "$cm_json" ]; then
7057
+ exit 0
7058
+ fi
7059
+
7060
+ # Parse fields
7061
+ stats_null=$(echo "$cm_json" | ${jsonExtract("stats")} 2>/dev/null || echo "null")
7062
+ if [ "$stats_null" = "null" ] || [ -z "$stats_null" ]; then
7063
+ echo "CM: \u2014 no index (run: codemap index)"
7064
+ exit 0
7065
+ fi
7066
+
7067
+ total_files=$(echo "$cm_json" | ${jsonPath("stats", "totalFiles")} 2>/dev/null || echo "0")
7068
+ total_chunks=$(echo "$cm_json" | ${jsonPath("stats", "totalChunks")} 2>/dev/null || echo "0")
7069
+ stale_files=$(echo "$cm_json" | ${jsonPath("stats", "staleFiles")} 2>/dev/null || echo "0")
7070
+ last_updated=$(echo "$cm_json" | ${jsonPath("stats", "lastUpdated")} 2>/dev/null || echo "")
7071
+ indexing=$(echo "$cm_json" | ${jsonExtract("indexing")} 2>/dev/null || echo "false")
7072
+
7073
+ # Format chunks (e.g. 12400 \u2192 12.4k)
7074
+ format_chunks() {
7075
+ local n=$1
7076
+ if [ "$n" -ge 1000 ]; then
7077
+ local k=$((n / 100))
7078
+ local whole=$((k / 10))
7079
+ local frac=$((k % 10))
7080
+ echo "\${whole}.\${frac}k"
7081
+ else
7082
+ echo "$n"
7083
+ fi
7084
+ }
7085
+
7086
+ # Calculate time ago
7087
+ time_ago() {
7088
+ local updated="$1"
7089
+ if [ -z "$updated" ]; then
7090
+ echo "unknown"
7091
+ return
7092
+ fi
7093
+ local now
7094
+ now=$(date +%s)
7095
+ local then_ts
7096
+ # macOS date -j vs GNU date
7097
+ if date -j -f "%Y-%m-%dT%H:%M:%S" "$(echo "$updated" | cut -c1-19)" +%s >/dev/null 2>&1; then
7098
+ then_ts=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$(echo "$updated" | cut -c1-19)" +%s 2>/dev/null || echo "$now")
7099
+ else
7100
+ then_ts=$(date -d "$updated" +%s 2>/dev/null || echo "$now")
7101
+ fi
7102
+ local diff=$(( now - then_ts ))
7103
+ if [ "$diff" -lt 60 ]; then
7104
+ echo "\${diff}s ago"
7105
+ elif [ "$diff" -lt 3600 ]; then
7106
+ echo "$(( diff / 60 ))m ago"
7107
+ elif [ "$diff" -lt 86400 ]; then
7108
+ echo "$(( diff / 3600 ))h ago"
7109
+ else
7110
+ echo "$(( diff / 86400 ))d ago"
7111
+ fi
7112
+ }
7113
+
7114
+ # Watcher detection
7115
+ watcher_icon="\u25CB"
7116
+ if pgrep -f "codemap watch" >/dev/null 2>&1; then
7117
+ watcher_icon="\u25CF"
7118
+ fi
7119
+
7120
+ # Indexing indicator
7121
+ idx_icon=""
7122
+ if [ "$indexing" = "true" ]; then
7123
+ idx_icon=" IDX"
7124
+ fi
7125
+
7126
+ chunks_fmt=$(format_chunks "$total_chunks")
7127
+
7128
+ # Build status line
7129
+ line="CM: \${watcher_icon} \${total_files}f \${chunks_fmt}\u25C6"
7130
+
7131
+ if [ "$stale_files" -gt 0 ] 2>/dev/null; then
7132
+ line="\${line} \u26A0\${stale_files}"
7133
+ fi
7134
+
7135
+ line="\${line}\${idx_icon} | $(time_ago "$last_updated")"
7136
+
7137
+ echo "$line"
7138
+ `;
7139
+ }
7140
+ function jsonExtract(field) {
7141
+ return `grep -o '"${field}"[[:space:]]*:[[:space:]]*[^,}]*' | sed 's/.*:[[:space:]]*//' | sed 's/"//g' | head -1`;
7142
+ }
7143
+ function jsonPath(parent, child) {
7144
+ return `grep -o '"${child}"[[:space:]]*:[[:space:]]*[^,}]*' | sed 's/.*:[[:space:]]*//' | sed 's/"//g' | head -1`;
7145
+ }
7146
+
7147
+ // src/commands/statusline.ts
7148
+ var CLAUDE_DIR = path13.join(homedir3(), ".claude");
7149
+ var SETTINGS_FILE2 = path13.join(CLAUDE_DIR, "settings.json");
7150
+ var SCRIPT_FILE = path13.join(CLAUDE_DIR, "statusline-command.sh");
7151
+ var BACKUP_FILE = path13.join(CLAUDE_DIR, "statusline-command.sh.bak");
7152
+ function readSettings() {
7153
+ try {
7154
+ return JSON.parse(fs18.readFileSync(SETTINGS_FILE2, "utf-8"));
7155
+ } catch {
7156
+ return {};
7157
+ }
7158
+ }
7159
+ function writeSettings(settings) {
7160
+ fs18.mkdirSync(CLAUDE_DIR, { recursive: true });
7161
+ fs18.writeFileSync(SETTINGS_FILE2, JSON.stringify(settings, null, 2) + "\n");
7162
+ }
7163
+ function registerStatusline(program2) {
7164
+ program2.command("statusline").description("Install CodeMap status into Claude Code statusline").option("--uninstall", "Remove CodeMap from statusline and restore backup").action(async (opts) => {
7165
+ if (opts.uninstall) {
7166
+ uninstall();
7167
+ return;
7168
+ }
7169
+ install();
7170
+ });
7171
+ }
7172
+ function install() {
7173
+ const settings = readSettings();
7174
+ if (fs18.existsSync(SCRIPT_FILE)) {
7175
+ try {
7176
+ const existing = fs18.readFileSync(SCRIPT_FILE, "utf-8");
7177
+ if (existing.includes("Generated by: codemap statusline")) {
7178
+ console.log(chalk17.yellow("CodeMap statusline is already installed."));
7179
+ console.log(chalk17.dim("Run `codemap statusline --uninstall` to remove, then reinstall."));
7180
+ return;
7181
+ }
7182
+ } catch {
7183
+ }
7184
+ }
7185
+ const originalCommand = settings.statusline_command || "";
7186
+ if (originalCommand && originalCommand !== SCRIPT_FILE) {
7187
+ fs18.mkdirSync(CLAUDE_DIR, { recursive: true });
7188
+ fs18.writeFileSync(BACKUP_FILE, originalCommand);
7189
+ console.log(chalk17.dim(`Saved original statusline command to ${BACKUP_FILE}`));
7190
+ }
7191
+ const script = buildStatuslineScript(originalCommand !== SCRIPT_FILE ? originalCommand : "");
7192
+ fs18.mkdirSync(CLAUDE_DIR, { recursive: true });
7193
+ fs18.writeFileSync(SCRIPT_FILE, script, { mode: 493 });
7194
+ if (settings.statusline_command !== SCRIPT_FILE) {
7195
+ settings.statusline_command = SCRIPT_FILE;
7196
+ writeSettings(settings);
7197
+ console.log(chalk17.dim(`Updated ${SETTINGS_FILE2}`));
7198
+ }
7199
+ console.log();
7200
+ console.log(chalk17.green("CodeMap statusline installed."));
7201
+ if (originalCommand && originalCommand !== SCRIPT_FILE) {
7202
+ console.log(chalk17.dim("Your existing statusline is preserved \u2014 CodeMap appends to it."));
7203
+ }
7204
+ console.log();
7205
+ console.log("CodeMap will append to the statusline:");
7206
+ console.log(chalk17.cyan(" CM: \u25CF 842f 12.4k\u25C6 | 23m ago") + chalk17.dim(" \u2014 watcher running, index fresh"));
7207
+ console.log(chalk17.cyan(" CM: \u25CB 842f 12.4k\u25C6 \u26A03 | 2h ago") + chalk17.dim(" \u2014 no watcher, 3 stale files"));
7208
+ console.log(chalk17.cyan(" CM: \u25CB 842f 12.4k\u25C6 IDX | 5s ago") + chalk17.dim(" \u2014 indexing in progress"));
7209
+ console.log(chalk17.cyan(" CM: \u2014 no index (run: codemap index)") + chalk17.dim(" \u2014 not yet indexed"));
7210
+ console.log();
7211
+ console.log(chalk17.dim("Restart Claude Code to see the statusline."));
7212
+ console.log(chalk17.dim("Run `codemap statusline --uninstall` to remove."));
7213
+ console.log();
7214
+ console.log(chalk17.dim("Tip: Install codemap in multiple projects to give each Claude Code"));
7215
+ console.log(chalk17.dim("session its own code intelligence \u2014 frontend, backend, shared libs."));
7216
+ console.log(chalk17.dim("Run `codemap init` in any project to get started."));
7217
+ }
7218
+ function uninstall() {
7219
+ let originalCommand = "";
7220
+ if (fs18.existsSync(BACKUP_FILE)) {
7221
+ try {
7222
+ originalCommand = fs18.readFileSync(BACKUP_FILE, "utf-8").trim();
7223
+ } catch {
7224
+ }
7225
+ try {
7226
+ fs18.unlinkSync(BACKUP_FILE);
7227
+ } catch {
7228
+ }
7229
+ }
7230
+ if (fs18.existsSync(SCRIPT_FILE)) {
7231
+ try {
7232
+ const content = fs18.readFileSync(SCRIPT_FILE, "utf-8");
7233
+ if (content.includes("Generated by: codemap statusline")) {
7234
+ fs18.unlinkSync(SCRIPT_FILE);
7235
+ console.log(chalk17.dim("Removed statusline script."));
7236
+ }
7237
+ } catch {
7238
+ }
7239
+ }
7240
+ const settings = readSettings();
7241
+ if (originalCommand && originalCommand !== SCRIPT_FILE) {
7242
+ settings.statusline_command = originalCommand;
7243
+ writeSettings(settings);
7244
+ console.log(chalk17.dim(`Restored original statusline command.`));
7245
+ } else if (settings.statusline_command) {
7246
+ delete settings.statusline_command;
7247
+ writeSettings(settings);
7248
+ console.log(chalk17.dim("Cleared statusline_command from settings."));
7249
+ }
7250
+ console.log();
7251
+ console.log(chalk17.green("CodeMap statusline removed."));
7252
+ console.log(chalk17.dim("Restart Claude Code for changes to take effect."));
7253
+ }
7254
+
7013
7255
  // src/index.ts
7014
- var __dirname = path13.dirname(fileURLToPath(import.meta.url));
7015
- var grammarsDir = path13.join(__dirname, "grammars");
7256
+ var __dirname = path14.dirname(fileURLToPath(import.meta.url));
7257
+ var grammarsDir = path14.join(__dirname, "grammars");
7016
7258
  setGrammarDir(grammarsDir);
7017
7259
  var program = new Command();
7018
7260
  program.name("codemap").description("Code intelligence CLI \u2014 hybrid search, dependency analysis, PageRank").version("0.3.5").option("--cwd <dir>", "Project directory (default: cwd)");
@@ -7033,4 +7275,5 @@ registerSummary(program);
7033
7275
  registerCoupling(program);
7034
7276
  registerGraphStats(program);
7035
7277
  registerRebuild(program);
7278
+ registerStatusline(program);
7036
7279
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulpi/codemap",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "type": "module",
5
5
  "description": "Standalone code intelligence CLI — hybrid vector + BM25 search, dependency analysis, PageRank",
6
6
  "bin": {
@@ -10,9 +10,11 @@
10
10
  ".": "./dist/index.js"
11
11
  },
12
12
  "files": [
13
- "dist"
13
+ "dist",
14
+ "scripts/postinstall.js"
14
15
  ],
15
16
  "scripts": {
17
+ "postinstall": "node scripts/postinstall.js",
16
18
  "build": "tsup && node --import tsx scripts/copy-grammars.ts",
17
19
  "typecheck": "tsc --noEmit",
18
20
  "test": "vitest run",
@@ -0,0 +1,36 @@
1
+ // Postinstall welcome message for `npm install -g @ulpi/codemap`
2
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
3
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
4
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
5
+
6
+ console.log();
7
+ console.log(bold("codemap") + " installed successfully.");
8
+ console.log();
9
+ console.log("Get started:");
10
+ console.log(` ${cyan("codemap init")} ${dim("# Choose embedding provider + model")}`);
11
+ console.log(` ${cyan("codemap index")} ${dim("# Index your project")}`);
12
+ console.log(` ${cyan("codemap search")} ${dim('"query"')} ${dim("# Search your code")}`);
13
+ console.log();
14
+ console.log("Add as an MCP server to give your AI agent code intelligence:");
15
+ console.log(` ${cyan("codemap serve")} ${dim("# Start MCP server (stdio)")}`);
16
+ console.log();
17
+ console.log("Multi-project: Index each codebase, then register one MCP server per project.");
18
+ console.log("Your AI agent gets code intelligence across your entire stack.");
19
+ console.log();
20
+ console.log(` ${dim("# Index each project once:")}`);
21
+ console.log(` ${dim("$")} ${cyan("codemap init")} && ${cyan("codemap index")} ${dim("# current project")}`);
22
+ console.log(` ${dim("$")} cd ~/projects/frontend && ${cyan("codemap init")} && ${cyan("codemap index")}`);
23
+ console.log();
24
+ console.log(` ${dim("# .mcp.json — add to the project where you use your AI agent:")}`);
25
+ console.log(` ${dim("{")}`)
26
+ console.log(` ${dim(' "mcpServers": {')}`);
27
+ console.log(` ${dim(' "codemap": {')}`);
28
+ console.log(` ${dim(' "command": "codemap", "args": ["serve"]')} ${dim("← this project")}`);
29
+ console.log(` ${dim(' },')}`);
30
+ console.log(` ${dim(' "codemap-frontend": {')}`);
31
+ console.log(` ${dim(' "command": "codemap", "args": ["serve", "--cwd",')}`);
32
+ console.log(` ${dim(' "/path/to/frontend"]')} ${dim("← another project")}`);
33
+ console.log(` ${dim(' }')}`);
34
+ console.log(` ${dim(" }")}`);
35
+ console.log(` ${dim("}")}`);
36
+ console.log();