@ulpi/codemap 0.3.10 → 0.3.12

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
@@ -111,6 +111,35 @@ codemap watch --debounce 500
111
111
  |--------|-------------|---------|
112
112
  | `--debounce <ms>` | Debounce interval | `300` |
113
113
 
114
+ ### `codemap statusline`
115
+
116
+ Install a CodeMap status indicator into Claude Code's statusline. It extends your existing statusline (if any) and appends CodeMap info.
117
+
118
+ ```bash
119
+ codemap statusline # Install
120
+ codemap statusline --uninstall # Remove and restore previous statusline
121
+ ```
122
+
123
+ The statusline shows at a glance:
124
+
125
+ ```
126
+ MyProject | git:main | Opus (1M context) | ctx:8%
127
+ CodeMap: ● 842 files · ◆12.4k chunks | 23m ago
128
+ ```
129
+
130
+ With multiple codemap MCP servers registered in `.mcp.json`, each gets its own line:
131
+
132
+ ```
133
+ MyProject | git:main | Opus (1M context) | ctx:8%
134
+ CodeMap: ● 842 files · ◆12.4k chunks | 23m ago
135
+ CodeMap (frontend): ○ 360 files · ◆1.9k chunks · ⚠3 stale | 2h ago
136
+ ```
137
+
138
+ - **●/○** — `codemap watch` running or not
139
+ - **◆** — indexed chunks (search units)
140
+ - **⚠N stale** — files modified since last index
141
+ - **IDX** — indexing in progress
142
+
114
143
  ### `codemap deps <file>`
115
144
 
116
145
  Show files this file depends on (outgoing imports).
package/dist/index.js CHANGED
@@ -5980,6 +5980,25 @@ symbol lookup, dependency analysis, and PageRank file ranking.
5980
5980
  console.log();
5981
5981
  console.log(chalk3.dim("\u2500".repeat(60)));
5982
5982
  console.log(chalk3.dim("Copy the above into your CLAUDE.md, .cursorrules, or agent config."));
5983
+ console.log();
5984
+ console.log(chalk3.bold("Multi-project setup"));
5985
+ console.log();
5986
+ console.log("You can give your AI agent code intelligence across multiple codebases.");
5987
+ console.log("Run " + chalk3.cyan("codemap init") + " + " + chalk3.cyan("codemap index") + " in each project, then register");
5988
+ console.log("one MCP server per project:");
5989
+ console.log();
5990
+ console.log(chalk3.dim(" // .mcp.json"));
5991
+ console.log(chalk3.dim(" {"));
5992
+ console.log(chalk3.dim(' "mcpServers": {'));
5993
+ console.log(chalk3.dim(' "codemap": {'));
5994
+ console.log(chalk3.dim(' "command": "codemap", "args": ["serve"]') + chalk3.dim(" \u2190 this project"));
5995
+ console.log(chalk3.dim(" },"));
5996
+ console.log(chalk3.dim(' "codemap-frontend": {'));
5997
+ console.log(chalk3.dim(' "command": "codemap",'));
5998
+ console.log(chalk3.dim(' "args": ["serve", "--cwd", "/path/to/frontend"]') + chalk3.dim(" \u2190 another project"));
5999
+ console.log(chalk3.dim(" }"));
6000
+ console.log(chalk3.dim(" }"));
6001
+ console.log(chalk3.dim(" }"));
5983
6002
  }
5984
6003
  var ULPI_API_BASE = "https://codemap.ulpi.io";
5985
6004
  var ULPI_AUTH_BASE = "https://codemap.ulpi.io/api/v1";
@@ -6118,14 +6137,24 @@ function registerIndex(program2) {
6118
6137
  console.log(chalk4.bold("Indexing ") + chalk4.cyan(projectDir));
6119
6138
  console.log(chalk4.dim(`Branch: ${branch} | Provider: ${config.embedding.provider} | Model: ${config.embedding.model}`));
6120
6139
  console.log();
6121
- const stats = await runInitPipeline(projectDir, config, {
6122
- branch,
6123
- dataDir: getDataDir2(),
6124
- onProgress: (progress) => {
6125
- const pct = progress.total ? ` (${progress.current ?? 0}/${progress.total})` : "";
6126
- console.log(chalk4.dim(`[${progress.phase}]`) + ` ${progress.message}${pct}`);
6140
+ let stats;
6141
+ try {
6142
+ stats = await runInitPipeline(projectDir, config, {
6143
+ branch,
6144
+ dataDir: getDataDir2(),
6145
+ onProgress: (progress) => {
6146
+ const pct = progress.total ? ` (${progress.current ?? 0}/${progress.total})` : "";
6147
+ console.log(chalk4.dim(`[${progress.phase}]`) + ` ${progress.message}${pct}`);
6148
+ }
6149
+ });
6150
+ } catch (err) {
6151
+ if (err instanceof Error && err.message.includes("Failed to acquire codemap lock")) {
6152
+ console.log(chalk4.yellow("Another codemap process is already indexing this project."));
6153
+ console.log(chalk4.dim("Wait for it to finish, or run `codemap status` to check."));
6154
+ return;
6127
6155
  }
6128
- });
6156
+ throw err;
6157
+ }
6129
6158
  console.log();
6130
6159
  console.log(chalk4.green("Index complete:"));
6131
6160
  console.log(` Files: ${stats.totalFiles}`);
@@ -7032,7 +7061,11 @@ import { homedir as homedir3 } from "os";
7032
7061
  function buildStatuslineScript(originalCommand) {
7033
7062
  const escapedCmd = originalCommand.replace(/'/g, "'\\''");
7034
7063
  const originalSection = originalCommand ? `ORIGINAL_CMD='${escapedCmd}'
7035
- echo "$SESSION_JSON" | eval "$ORIGINAL_CMD" 2>/dev/null || true` : "# (none)";
7064
+ original_out=$(echo "$SESSION_JSON" | eval "$ORIGINAL_CMD" 2>/dev/null || true)
7065
+ if [ -n "$original_out" ]; then
7066
+ echo "$original_out"
7067
+ has_line1=true
7068
+ fi` : "";
7036
7069
  return `#!/bin/bash
7037
7070
  # CodeMap statusline for Claude Code
7038
7071
  # Generated by: codemap statusline
@@ -7043,34 +7076,49 @@ set -euo pipefail
7043
7076
  # Read session JSON from stdin (Claude Code pipes it)
7044
7077
  SESSION_JSON=$(cat)
7045
7078
 
7046
- # --- Original statusline ---
7079
+ has_line1=false
7080
+
7081
+ # --- Original statusline command (if any) ---
7047
7082
  ${originalSection}
7048
7083
 
7049
- # --- CodeMap status ---
7050
- # Resolve the working directory from session JSON
7084
+ # --- Reconstruct Claude Code default status (line 1) ---
7051
7085
  current_dir=$(echo "$SESSION_JSON" | ${jsonExtract("cwd")} 2>/dev/null || pwd)
7052
7086
 
7053
- # Run codemap status --json for the project directory
7054
- cm_json=$(codemap status --json --cwd "$current_dir" 2>/dev/null || true)
7087
+ if [ "$has_line1" = "false" ]; then
7088
+ project_name=$(basename "$current_dir")
7089
+ git_branch=$(cd "$current_dir" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
7090
+ model_display=$(echo "$SESSION_JSON" | ${jsonExtract("display_name")} 2>/dev/null || echo "")
7091
+ ctx_size=$(echo "$SESSION_JSON" | ${jsonExtract("context_window_size")} 2>/dev/null || echo "")
7092
+ ctx_label=""
7093
+ if [ -n "$ctx_size" ]; then
7094
+ if [ "$ctx_size" -ge 1000000 ] 2>/dev/null; then
7095
+ ctx_label="1M context"
7096
+ elif [ "$ctx_size" -ge 100000 ] 2>/dev/null; then
7097
+ ctx_label="200k context"
7098
+ fi
7099
+ fi
7100
+ ctx_pct=$(echo "$SESSION_JSON" | ${jsonExtract("used_percentage")} 2>/dev/null || echo "")
7055
7101
 
7056
- if [ -z "$cm_json" ]; then
7057
- exit 0
7058
- fi
7102
+ line1="$project_name"
7103
+ if [ -n "$git_branch" ]; then
7104
+ line1="\${line1} | git:$git_branch"
7105
+ fi
7106
+ if [ -n "$model_display" ]; then
7107
+ if [ -n "$ctx_label" ]; then
7108
+ line1="\${line1} | \${model_display} (\${ctx_label})"
7109
+ else
7110
+ line1="\${line1} | \${model_display}"
7111
+ fi
7112
+ fi
7113
+ if [ -n "$ctx_pct" ]; then
7114
+ line1="\${line1} | ctx:\${ctx_pct}%"
7115
+ fi
7059
7116
 
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
7117
+ echo "$line1"
7065
7118
  fi
7066
7119
 
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")
7120
+ # --- Helper functions ---
7072
7121
 
7073
- # Format chunks (e.g. 12400 \u2192 12.4k)
7074
7122
  format_chunks() {
7075
7123
  local n=$1
7076
7124
  if [ "$n" -ge 1000 ]; then
@@ -7083,7 +7131,6 @@ format_chunks() {
7083
7131
  fi
7084
7132
  }
7085
7133
 
7086
- # Calculate time ago
7087
7134
  time_ago() {
7088
7135
  local updated="$1"
7089
7136
  if [ -z "$updated" ]; then
@@ -7093,7 +7140,6 @@ time_ago() {
7093
7140
  local now
7094
7141
  now=$(date +%s)
7095
7142
  local then_ts
7096
- # macOS date -j vs GNU date
7097
7143
  if date -j -f "%Y-%m-%dT%H:%M:%S" "$(echo "$updated" | cut -c1-19)" +%s >/dev/null 2>&1; then
7098
7144
  then_ts=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$(echo "$updated" | cut -c1-19)" +%s 2>/dev/null || echo "$now")
7099
7145
  else
@@ -7111,36 +7157,104 @@ time_ago() {
7111
7157
  fi
7112
7158
  }
7113
7159
 
7114
- # Watcher detection
7115
- watcher_icon="\u25CB"
7116
- if pgrep -f "codemap watch" >/dev/null 2>&1; then
7117
- watcher_icon="\u25CF"
7118
- fi
7160
+ # Print one CodeMap status line for a given project directory and label
7161
+ print_codemap_status() {
7162
+ local label="$1"
7163
+ local target_dir="$2"
7119
7164
 
7120
- # Indexing indicator
7121
- idx_icon=""
7122
- if [ "$indexing" = "true" ]; then
7123
- idx_icon=" IDX"
7124
- fi
7165
+ local cm_json
7166
+ cm_json=$(codemap status --json --cwd "$target_dir" 2>/dev/null || true)
7167
+ if [ -z "$cm_json" ]; then
7168
+ return
7169
+ fi
7125
7170
 
7126
- chunks_fmt=$(format_chunks "$total_chunks")
7171
+ local stats_null
7172
+ stats_null=$(echo "$cm_json" | ${jsonExtract("stats")} 2>/dev/null || echo "null")
7173
+ if [ "$stats_null" = "null" ] || [ -z "$stats_null" ]; then
7174
+ echo "\${label} \u2014 no index (run: codemap index)"
7175
+ return
7176
+ fi
7127
7177
 
7128
- # Build status line
7129
- line="CM: \${watcher_icon} \${total_files}f \${chunks_fmt}\u25C6"
7178
+ local total_files total_chunks stale_files last_updated indexing
7179
+ total_files=$(echo "$cm_json" | ${jsonPath("stats", "totalFiles")} 2>/dev/null || echo "0")
7180
+ total_chunks=$(echo "$cm_json" | ${jsonPath("stats", "totalChunks")} 2>/dev/null || echo "0")
7181
+ stale_files=$(echo "$cm_json" | ${jsonPath("stats", "staleFiles")} 2>/dev/null || echo "0")
7182
+ last_updated=$(echo "$cm_json" | ${jsonPath("stats", "lastUpdated")} 2>/dev/null || echo "")
7183
+ indexing=$(echo "$cm_json" | ${jsonExtract("indexing")} 2>/dev/null || echo "false")
7130
7184
 
7131
- if [ "$stale_files" -gt 0 ] 2>/dev/null; then
7132
- line="\${line} \u26A0\${stale_files}"
7133
- fi
7185
+ local watcher_icon="\u25CB"
7186
+ if pgrep -f "codemap watch" >/dev/null 2>&1; then
7187
+ watcher_icon="\u25CF"
7188
+ fi
7189
+
7190
+ local idx_icon=""
7191
+ if [ "$indexing" = "true" ]; then
7192
+ idx_icon=" \xB7 IDX"
7193
+ fi
7134
7194
 
7135
- line="\${line}\${idx_icon} | $(time_ago "$last_updated")"
7195
+ local chunks_fmt
7196
+ chunks_fmt=$(format_chunks "$total_chunks")
7197
+
7198
+ local line="\${label} \${watcher_icon} \${total_files} files \xB7 \u25C6\${chunks_fmt} chunks"
7199
+
7200
+ if [ "$stale_files" -gt 0 ] 2>/dev/null; then
7201
+ line="\${line} \xB7 \u26A0\${stale_files} stale"
7202
+ fi
7203
+
7204
+ line="\${line}\${idx_icon} | $(time_ago "$last_updated")"
7205
+
7206
+ echo "$line"
7207
+ }
7136
7208
 
7137
- echo "$line"
7209
+ # --- CodeMap status lines ---
7210
+
7211
+ # Discover codemap MCP servers from .mcp.json
7212
+ mcp_file="$current_dir/.mcp.json"
7213
+ found_servers=false
7214
+
7215
+ if [ -f "$mcp_file" ]; then
7216
+ # Use node to reliably parse JSON \u2014 extract "name|cwd" pairs for codemap servers
7217
+ codemap_servers=$(node -e "
7218
+ const fs = require('fs');
7219
+ try {
7220
+ const mcp = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8'));
7221
+ const servers = mcp.mcpServers || {};
7222
+ for (const [name, config] of Object.entries(servers)) {
7223
+ if (config.command !== 'codemap') continue;
7224
+ const args = config.args || [];
7225
+ const cwdIdx = args.indexOf('--cwd');
7226
+ const cwd = cwdIdx >= 0 && cwdIdx + 1 < args.length ? args[cwdIdx + 1] : '';
7227
+ console.log(name + '|' + cwd);
7228
+ }
7229
+ } catch {}
7230
+ " "$mcp_file" 2>/dev/null || true)
7231
+
7232
+ if [ -n "$codemap_servers" ]; then
7233
+ found_servers=true
7234
+ while IFS='|' read -r server_name server_cwd; do
7235
+ target_dir="\${server_cwd:-$current_dir}"
7236
+ # Derive label: "codemap" \u2192 "CodeMap:", "codemap-frontend" \u2192 "CodeMap (frontend):"
7237
+ suffix=$(echo "$server_name" | sed 's/^codemap-//' | sed 's/^codemap$//')
7238
+ if [ -n "$suffix" ]; then
7239
+ label="CodeMap (\${suffix}):"
7240
+ else
7241
+ label="CodeMap:"
7242
+ fi
7243
+ print_codemap_status "$label" "$target_dir"
7244
+ done <<< "$codemap_servers"
7245
+ fi
7246
+ fi
7247
+
7248
+ # Fallback: no .mcp.json or no codemap entries \u2014 show status for current dir
7249
+ if [ "$found_servers" = "false" ]; then
7250
+ print_codemap_status "CodeMap:" "$current_dir"
7251
+ fi
7138
7252
  `;
7139
7253
  }
7140
7254
  function jsonExtract(field) {
7141
7255
  return `grep -o '"${field}"[[:space:]]*:[[:space:]]*[^,}]*' | sed 's/.*:[[:space:]]*//' | sed 's/"//g' | head -1`;
7142
7256
  }
7143
- function jsonPath(parent, child) {
7257
+ function jsonPath(_parent, child) {
7144
7258
  return `grep -o '"${child}"[[:space:]]*:[[:space:]]*[^,}]*' | sed 's/.*:[[:space:]]*//' | sed 's/"//g' | head -1`;
7145
7259
  }
7146
7260
 
@@ -7202,11 +7316,16 @@ function install() {
7202
7316
  console.log(chalk17.dim("Your existing statusline is preserved \u2014 CodeMap appends to it."));
7203
7317
  }
7204
7318
  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"));
7319
+ console.log("The statusline will show:");
7320
+ console.log();
7321
+ console.log(" " + chalk17.cyan("MyProject | git:main | Opus (1M context) | ctx:8%"));
7322
+ console.log(" " + chalk17.cyan("CodeMap: \u25CF 842 files \xB7 \u25C612.4k chunks | 23m ago"));
7323
+ console.log();
7324
+ console.log(" With multiple codemap MCP servers:");
7325
+ console.log();
7326
+ console.log(" " + chalk17.cyan("MyProject | git:main | Opus (1M context) | ctx:8%"));
7327
+ console.log(" " + chalk17.cyan("CodeMap: \u25CF 842 files \xB7 \u25C612.4k chunks | 23m ago"));
7328
+ console.log(" " + chalk17.cyan("CodeMap (frontend): \u25CB 360 files \xB7 \u25C61.9k chunks \xB7 \u26A03 stale | 2h ago"));
7210
7329
  console.log();
7211
7330
  console.log(chalk17.dim("Restart Claude Code to see the statusline."));
7212
7331
  console.log(chalk17.dim("Run `codemap statusline --uninstall` to remove."));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulpi/codemap",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "type": "module",
5
5
  "description": "Standalone code intelligence CLI — hybrid vector + BM25 search, dependency analysis, PageRank",
6
6
  "bin": {
@@ -10,11 +10,9 @@
10
10
  ".": "./dist/index.js"
11
11
  },
12
12
  "files": [
13
- "dist",
14
- "scripts/postinstall.js"
13
+ "dist"
15
14
  ],
16
15
  "scripts": {
17
- "postinstall": "node scripts/postinstall.js",
18
16
  "build": "tsup && node --import tsx scripts/copy-grammars.ts",
19
17
  "typecheck": "tsc --noEmit",
20
18
  "test": "vitest run",
@@ -1,36 +0,0 @@
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.warn();
7
- console.warn(bold("codemap") + " installed successfully.");
8
- console.warn();
9
- console.warn("Get started:");
10
- console.warn(` ${cyan("codemap init")} ${dim("# Choose embedding provider + model")}`);
11
- console.warn(` ${cyan("codemap index")} ${dim("# Index your project")}`);
12
- console.warn(` ${cyan("codemap search")} ${dim('"query"')} ${dim("# Search your code")}`);
13
- console.warn();
14
- console.warn("Add as an MCP server to give your AI agent code intelligence:");
15
- console.warn(` ${cyan("codemap serve")} ${dim("# Start MCP server (stdio)")}`);
16
- console.warn();
17
- console.warn("Multi-project: Index each codebase, then register one MCP server per project.");
18
- console.warn("Your AI agent gets code intelligence across your entire stack.");
19
- console.warn();
20
- console.warn(` ${dim("# Index each project once:")}`);
21
- console.warn(` ${dim("$")} ${cyan("codemap init")} && ${cyan("codemap index")} ${dim("# current project")}`);
22
- console.warn(` ${dim("$")} cd ~/projects/frontend && ${cyan("codemap init")} && ${cyan("codemap index")}`);
23
- console.warn();
24
- console.warn(` ${dim("# .mcp.json — add to the project where you use your AI agent:")}`);
25
- console.warn(` ${dim("{")}`)
26
- console.warn(` ${dim(' "mcpServers": {')}`);
27
- console.warn(` ${dim(' "codemap": {')}`);
28
- console.warn(` ${dim(' "command": "codemap", "args": ["serve"]')} ${dim("← this project")}`);
29
- console.warn(` ${dim(' },')}`);
30
- console.warn(` ${dim(' "codemap-frontend": {')}`);
31
- console.warn(` ${dim(' "command": "codemap", "args": ["serve", "--cwd",')}`);
32
- console.warn(` ${dim(' "/path/to/frontend"]')} ${dim("← another project")}`);
33
- console.warn(` ${dim(' }')}`);
34
- console.warn(` ${dim(" }")}`);
35
- console.warn(` ${dim("}")}`);
36
- console.warn();