@colbymchenry/codegraph 0.9.2 → 0.9.4
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 +91 -66
- package/npm-shim.js +215 -24
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
[](https://nodejs.org/)
|
|
12
12
|
|
|
13
|
-
[](#)
|
|
14
|
-
[](#)
|
|
15
|
-
[](#)
|
|
13
|
+
[](#supported-platforms)
|
|
14
|
+
[](#supported-platforms)
|
|
15
|
+
[](#supported-platforms)
|
|
16
16
|
|
|
17
|
-
[](#)
|
|
18
|
-
[](#)
|
|
19
|
-
[](#)
|
|
20
|
-
[](#)
|
|
21
|
-
[](#)
|
|
17
|
+
[](#supported-agents)
|
|
18
|
+
[](#supported-agents)
|
|
19
|
+
[](#supported-agents)
|
|
20
|
+
[](#supported-agents)
|
|
21
|
+
[](#supported-agents)
|
|
22
22
|
|
|
23
23
|
</div>
|
|
24
24
|
|
|
@@ -56,6 +56,16 @@ codegraph init -i
|
|
|
56
56
|
|
|
57
57
|
</div>
|
|
58
58
|
|
|
59
|
+
### Uninstall
|
|
60
|
+
|
|
61
|
+
Changed your mind? One command removes CodeGraph from every agent it configured:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
codegraph uninstall
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
<sub>Reverses the installer — strips CodeGraph's MCP server config, instructions, and permissions from each configured agent. Your project indexes (`.codegraph/`) are left untouched; remove those per-project with `codegraph uninit`. Use `--target` to remove from specific agents, or `--yes` to run non-interactively.</sub>
|
|
68
|
+
|
|
59
69
|
---
|
|
60
70
|
|
|
61
71
|
## Why CodeGraph?
|
|
@@ -66,26 +76,26 @@ When Claude Code explores a codebase, it spawns **Explore agents** that scan fil
|
|
|
66
76
|
|
|
67
77
|
### Benchmark Results
|
|
68
78
|
|
|
69
|
-
Tested across **7 real-world open-source codebases** spanning 7 languages, comparing an agent (Claude Code, headless) answering one architecture question **with** and **without** CodeGraph. Each cell is the savings at the **median of 4 runs per arm**.
|
|
79
|
+
Tested across **7 real-world open-source codebases** spanning 7 languages, comparing an agent (Claude Code, headless) answering one architecture question **with** and **without** CodeGraph. Each cell is the savings at the **median of 4 runs per arm**. _Re-validated on **v0.9.4** (2026-05-24)._
|
|
70
80
|
|
|
71
|
-
> **Average: 35% cheaper ·
|
|
81
|
+
> **Average: 35% cheaper · 57% fewer tokens · 46% faster · 71% fewer tool calls**
|
|
72
82
|
|
|
73
83
|
| Codebase | Language | Cost | Tokens | Time | Tool calls |
|
|
74
84
|
|----------|----------|------|--------|------|------------|
|
|
75
|
-
| **VS Code** | TypeScript · ~10k files |
|
|
76
|
-
| **Excalidraw** | TypeScript · ~
|
|
77
|
-
| **Django** | Python · ~
|
|
78
|
-
| **Tokio** | Rust · ~
|
|
79
|
-
| **OkHttp** | Java · ~
|
|
80
|
-
| **Gin** | Go · ~
|
|
81
|
-
| **Alamofire** | Swift · ~
|
|
85
|
+
| **VS Code** | TypeScript · ~10k files | 26% cheaper | 78% fewer | 52% faster | 85% fewer |
|
|
86
|
+
| **Excalidraw** | TypeScript · ~640 | 52% cheaper | 90% fewer | 73% faster | 96% fewer |
|
|
87
|
+
| **Django** | Python · ~3k | 12% cheaper | 36% fewer | 19% faster | 53% fewer |
|
|
88
|
+
| **Tokio** | Rust · ~790 | 82% cheaper | 86% fewer | 71% faster | 92% fewer |
|
|
89
|
+
| **OkHttp** | Java · ~645 | 2% cheaper | 13% fewer | 31% faster | 45% fewer |
|
|
90
|
+
| **Gin** | Go · ~110 | 21% cheaper | 34% fewer | 27% faster | 40% fewer |
|
|
91
|
+
| **Alamofire** | Swift · ~110 | 47% cheaper | 64% fewer | 48% faster | 83% fewer |
|
|
82
92
|
|
|
83
93
|
The gains scale with codebase size: on large repos the agent answers from the index in a handful of calls with **zero file reads**, while the no-CodeGraph agent fans out across grep/find/Read (and the sub-agents it spawns). On a small repo like Gin (~150 files) native search is already cheap, so the margin narrows.
|
|
84
94
|
|
|
85
95
|
<details>
|
|
86
96
|
<summary><strong>Full benchmark details</strong></summary>
|
|
87
97
|
|
|
88
|
-
**Methodology.** Each arm is `claude -p` (Claude Opus 4.7
|
|
98
|
+
**Methodology.** Each arm is `claude -p` (Claude Opus 4.7) run headlessly against the repo with `--strict-mcp-config`: **WITH** = CodeGraph's MCP server enabled, **WITHOUT** = an empty MCP config. Built-in Read/Grep/Bash stay available to both. Same question per repo, **4 runs per arm, median reported**. Cost = the run's `total_cost_usd`; Tokens = total tokens processed (input incl. cached + output); Time = wall-clock; Tool calls = every tool invocation, including those inside any sub-agents the model spawns. Repos cloned at `--depth 1` and indexed by the same CodeGraph build that served them. Re-validated on codegraph **v0.9.4** (2026-05-24); per-repo numbers move run-to-run with how hard the without-arm thrashes (the median-of-4 smooths it, but tails remain — e.g. Tokio's without-arm hit $2.41/3m one batch).
|
|
89
99
|
|
|
90
100
|
**Queries:**
|
|
91
101
|
| Codebase | Query |
|
|
@@ -101,13 +111,13 @@ The gains scale with codebase size: on large repos the agent answers from the in
|
|
|
101
111
|
**Raw medians — WITH → WITHOUT:**
|
|
102
112
|
| Codebase | Cost | Tokens | Time | Tool calls |
|
|
103
113
|
|----------|------|--------|------|------------|
|
|
104
|
-
| VS Code | $0.
|
|
105
|
-
| Excalidraw | $0.
|
|
106
|
-
| Django | $0.
|
|
107
|
-
| Tokio | $0.
|
|
108
|
-
| OkHttp | $0.
|
|
109
|
-
| Gin | $0.
|
|
110
|
-
| Alamofire | $0.61 → $
|
|
114
|
+
| VS Code | $0.60 → $0.80 | 601k → 2.8M | 1m 10s → 2m 26s | 8 → 55 |
|
|
115
|
+
| Excalidraw | $0.43 → $0.90 | 344k → 3.5M | 48s → 2m 58s | 3 → 79 |
|
|
116
|
+
| Django | $0.59 → $0.67 | 739k → 1.2M | 1m 19s → 1m 38s | 9 → 19 |
|
|
117
|
+
| Tokio | $0.42 → $2.41 | 379k → 2.6M | 53s → 3m 2s | 4 → 53 |
|
|
118
|
+
| OkHttp | $0.47 → $0.47 | 636k → 730k | 42s → 1m 1s | 6 → 11 |
|
|
119
|
+
| Gin | $0.37 → $0.47 | 444k → 675k | 44s → 1m 0s | 6 → 10 |
|
|
120
|
+
| Alamofire | $0.61 → $1.14 | 1.0M → 2.8M | 1m 17s → 2m 27s | 12 → 69 |
|
|
111
121
|
|
|
112
122
|
**Why CodeGraph wins:** with the index available, the agent answers directly — `codegraph_context` to map the area, then one `codegraph_explore` for the relevant source — and stops, usually with zero file reads. Without it, the agent (and the Explore sub-agents it spawns) spends most of its budget on discovery (find/ls/grep) before reading the right code. CodeGraph only helps when queried *directly*, so its instructions steer agents to answer directly rather than delegate exploration to file-reading sub-agents — otherwise a sub-agent reads files regardless and CodeGraph becomes overhead.
|
|
113
123
|
|
|
@@ -253,25 +263,21 @@ CodeGraph builds a semantic knowledge graph of codebases for faster, smarter cod
|
|
|
253
263
|
|
|
254
264
|
### If `.codegraph/` exists in the project
|
|
255
265
|
|
|
256
|
-
**
|
|
257
|
-
|
|
258
|
-
**When spawning Explore agents**, include this instruction in the prompt:
|
|
266
|
+
**Answer directly with CodeGraph — don't delegate exploration to a file-reading sub-agent or a grep/read loop.** CodeGraph *is* the pre-built search index; re-deriving its answers with grep + Read repeats work it already did and costs more for the same result. For "how does X work?", architecture, trace, or where-is-X questions, answer in a handful of CodeGraph calls and stop — typically with **zero file reads**. The returned source is complete and authoritative: treat it as already read and do not re-open those files. Reach for raw Read/Grep only to confirm a specific detail CodeGraph didn't cover.
|
|
259
267
|
|
|
260
|
-
|
|
261
|
-
>
|
|
262
|
-
> **Rules:**
|
|
263
|
-
> 1. Follow the explore call budget in the `codegraph_explore` tool description — it scales automatically based on project size.
|
|
264
|
-
> 2. Do NOT re-read files that codegraph_explore already returned source code for. The source sections are complete and authoritative.
|
|
265
|
-
> 3. Only fall back to grep/glob/read for files listed under "Additional relevant files" if you need more detail, or if codegraph returned no results.
|
|
266
|
-
|
|
267
|
-
**The main session may only use these lightweight tools directly** (for targeted lookups before making edits, not for exploration):
|
|
268
|
+
**Tool selection by intent:**
|
|
268
269
|
|
|
269
270
|
| Tool | Use For |
|
|
270
271
|
|------|---------|
|
|
271
|
-
| `
|
|
272
|
-
| `
|
|
272
|
+
| `codegraph_context` | Map a task / feature / area first — composes search + node + callers + callees in one call |
|
|
273
|
+
| `codegraph_trace` | "How does X reach Y" — the call path, each hop's body inline (follows dynamic-dispatch hops grep can't) |
|
|
274
|
+
| `codegraph_explore` | Survey several related symbols' source in ONE budget-capped call |
|
|
275
|
+
| `codegraph_search` | Find a symbol by name |
|
|
276
|
+
| `codegraph_callers` / `codegraph_callees` | Walk call flow one hop at a time |
|
|
273
277
|
| `codegraph_impact` | Check what's affected before editing |
|
|
274
|
-
| `codegraph_node` | Get a single symbol's
|
|
278
|
+
| `codegraph_node` | Get a single symbol's source / signature |
|
|
279
|
+
|
|
280
|
+
A direct CodeGraph answer is a handful of calls; a grep/read exploration is dozens.
|
|
275
281
|
|
|
276
282
|
### If `.codegraph/` does NOT exist
|
|
277
283
|
|
|
@@ -287,34 +293,23 @@ At the start of a session, ask the user if they'd like to initialize CodeGraph:
|
|
|
287
293
|
## How It Works
|
|
288
294
|
|
|
289
295
|
```
|
|
290
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
291
|
-
│ Claude Code │
|
|
292
|
-
│ │
|
|
293
|
-
│ "Implement user authentication" │
|
|
294
|
-
│ │ │
|
|
295
|
-
│ ▼ │
|
|
296
|
-
│ ┌─────────────────┐ ┌─────────────────┐ │
|
|
297
|
-
│ │ Explore Agent │ ──── │ Explore Agent │ │
|
|
298
|
-
│ └────────┬────────┘ └────────┬────────┘ │
|
|
299
|
-
│ │ │ │
|
|
300
|
-
└───────────┼────────────────────────┼─────────────────────────────┘
|
|
301
|
-
│ │
|
|
302
|
-
▼ ▼
|
|
303
296
|
┌───────────────────────────────────────────────────────────────────┐
|
|
304
|
-
│
|
|
305
|
-
│
|
|
306
|
-
│
|
|
307
|
-
│
|
|
308
|
-
│
|
|
309
|
-
|
|
310
|
-
│
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
│
|
|
314
|
-
│
|
|
315
|
-
│
|
|
316
|
-
│
|
|
317
|
-
│
|
|
297
|
+
│ Claude Code │
|
|
298
|
+
│ │
|
|
299
|
+
│ "How does a request reach the database?" │
|
|
300
|
+
│ calls CodeGraph tools directly — no Explore sub-agent │
|
|
301
|
+
│ │ │
|
|
302
|
+
└─────────────────────────────────┬─────────────────────────────────┘
|
|
303
|
+
│
|
|
304
|
+
▼
|
|
305
|
+
┌───────────────────────────────────────────────────────────────────┐
|
|
306
|
+
│ CodeGraph MCP Server │
|
|
307
|
+
│ │
|
|
308
|
+
│ context · trace · explore · callers · callees · impact │
|
|
309
|
+
│ │ │
|
|
310
|
+
│ ▼ │
|
|
311
|
+
│ SQLite knowledge graph │
|
|
312
|
+
│ symbols · edges · files · FTS5 full-text search │
|
|
318
313
|
└───────────────────────────────────────────────────────────────────┘
|
|
319
314
|
```
|
|
320
315
|
|
|
@@ -333,6 +328,7 @@ At the start of a session, ask the user if they'd like to initialize CodeGraph:
|
|
|
333
328
|
```bash
|
|
334
329
|
codegraph # Run interactive installer
|
|
335
330
|
codegraph install # Run installer (explicit)
|
|
331
|
+
codegraph uninstall # Remove CodeGraph from your agents (inverse of install)
|
|
336
332
|
codegraph init [path] # Initialize in a project (--index to also index)
|
|
337
333
|
codegraph uninit [path] # Remove CodeGraph from a project (--force to skip prompt)
|
|
338
334
|
codegraph index [path] # Full index (--force to re-index, --quiet for less output)
|
|
@@ -341,6 +337,9 @@ codegraph status [path] # Show statistics
|
|
|
341
337
|
codegraph query <search> # Search symbols (--kind, --limit, --json)
|
|
342
338
|
codegraph files [path] # Show file structure (--format, --filter, --max-depth, --json)
|
|
343
339
|
codegraph context <task> # Build context for AI (--format, --max-nodes)
|
|
340
|
+
codegraph callers <symbol> # Find what calls a function/method (--limit, --json)
|
|
341
|
+
codegraph callees <symbol> # Find what a function/method calls (--limit, --json)
|
|
342
|
+
codegraph impact <symbol> # Analyze what code is affected by changing a symbol (--depth, --json)
|
|
344
343
|
codegraph affected [files...] # Find test files affected by changes (see below)
|
|
345
344
|
codegraph serve --mcp # Start MCP server
|
|
346
345
|
```
|
|
@@ -383,10 +382,12 @@ When running as an MCP server, CodeGraph exposes these tools to Claude Code:
|
|
|
383
382
|
|------|---------|
|
|
384
383
|
| `codegraph_search` | Find symbols by name across the codebase |
|
|
385
384
|
| `codegraph_context` | Build relevant code context for a task |
|
|
385
|
+
| `codegraph_trace` | Trace the call path between two symbols ("how does X reach Y") in one call — each hop with its body inline, following dynamic-dispatch hops (callbacks, React re-render, interface→impl) that grep can't |
|
|
386
386
|
| `codegraph_callers` | Find what calls a function |
|
|
387
387
|
| `codegraph_callees` | Find what a function calls |
|
|
388
388
|
| `codegraph_impact` | Analyze what code is affected by changing a symbol |
|
|
389
389
|
| `codegraph_node` | Get details about a specific symbol (optionally with source code) |
|
|
390
|
+
| `codegraph_explore` | Return source for several related symbols grouped by file, plus a relationship map, in one call |
|
|
390
391
|
| `codegraph_files` | Get indexed file structure (faster than filesystem scanning) |
|
|
391
392
|
| `codegraph_status` | Check index health and statistics |
|
|
392
393
|
|
|
@@ -436,6 +437,30 @@ What that means in practice:
|
|
|
436
437
|
> committed `dist/`. If you commit a dependency or build directory you don't want
|
|
437
438
|
> in the graph, add it to `.gitignore`.
|
|
438
439
|
|
|
440
|
+
## Supported Platforms
|
|
441
|
+
|
|
442
|
+
Every release ships a self-contained build (bundled Node runtime — nothing to
|
|
443
|
+
compile) for all three desktop OSes, on both Intel/AMD (x64) and ARM (arm64):
|
|
444
|
+
|
|
445
|
+
| Platform | Architectures | Install |
|
|
446
|
+
|----------|---------------|---------|
|
|
447
|
+
| Windows | x64, arm64 | PowerShell installer or npm |
|
|
448
|
+
| macOS | x64, arm64 | shell installer or npm |
|
|
449
|
+
| Linux | x64, arm64 | shell installer or npm |
|
|
450
|
+
|
|
451
|
+
See [Get Started](#get-started) for the one-line install commands.
|
|
452
|
+
|
|
453
|
+
## Supported Agents
|
|
454
|
+
|
|
455
|
+
The interactive installer auto-detects and configures each of these — wiring up
|
|
456
|
+
the MCP server and writing its instructions file:
|
|
457
|
+
|
|
458
|
+
- **Claude Code**
|
|
459
|
+
- **Cursor**
|
|
460
|
+
- **Codex CLI**
|
|
461
|
+
- **opencode**
|
|
462
|
+
- **Hermes Agent**
|
|
463
|
+
|
|
439
464
|
## Supported Languages
|
|
440
465
|
|
|
441
466
|
| Language | Extension | Status |
|
package/npm-shim.js
CHANGED
|
@@ -11,45 +11,236 @@
|
|
|
11
11
|
// (with node:sqlite), regardless of the user's Node version. The user's Node is
|
|
12
12
|
// only ever a launcher; even an ancient version can run this file.
|
|
13
13
|
//
|
|
14
|
+
// Self-heal (issue #303): some registries — notably the npmmirror/cnpm mirrors,
|
|
15
|
+
// and some corporate proxies — don't reliably mirror the per-platform
|
|
16
|
+
// optionalDependencies. npm treats an unfetchable optional dep as success and
|
|
17
|
+
// silently skips it, so the bundle goes missing and every command fails. When
|
|
18
|
+
// the installed bundle can't be resolved, this shim falls back to downloading
|
|
19
|
+
// the matching bundle straight from GitHub Releases — the very archive
|
|
20
|
+
// install.sh uses — into a cache dir, then runs that. Knobs:
|
|
21
|
+
// CODEGRAPH_NO_DOWNLOAD=1 disable the network fallback (print guidance)
|
|
22
|
+
// CODEGRAPH_INSTALL_DIR=DIR cache location (default: ~/.codegraph)
|
|
23
|
+
// CODEGRAPH_DOWNLOAD_BASE=URL release-download base (for mirrors/air-gapped)
|
|
24
|
+
//
|
|
14
25
|
// Wired up at release time as the main package's `bin`:
|
|
15
|
-
// "bin": { "codegraph": "
|
|
26
|
+
// "bin": { "codegraph": "npm-shim.js" }
|
|
16
27
|
// with the platform packages listed in `optionalDependencies`.
|
|
17
28
|
|
|
18
29
|
var childProcess = require('child_process');
|
|
30
|
+
var fs = require('fs');
|
|
31
|
+
var os = require('os');
|
|
32
|
+
var path = require('path');
|
|
19
33
|
|
|
20
34
|
var target = process.platform + '-' + process.arch; // e.g. darwin-arm64, linux-x64
|
|
21
35
|
var pkg = '@colbymchenry/codegraph-' + target;
|
|
22
36
|
var isWindows = process.platform === 'win32';
|
|
37
|
+
var REPO = 'colbymchenry/codegraph';
|
|
38
|
+
|
|
39
|
+
main().catch(function (e) {
|
|
40
|
+
process.stderr.write('codegraph: ' + (e && e.message ? e.message : String(e)) + '\n');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
async function main() {
|
|
45
|
+
// Happy path: the npm-installed optional dependency. Fall back to a download
|
|
46
|
+
// when the registry didn't deliver it.
|
|
47
|
+
var resolved = resolveInstalledBundle() || (await selfHealBundle());
|
|
48
|
+
var res = childProcess.spawnSync(resolved.command, resolved.args, { stdio: 'inherit' });
|
|
49
|
+
if (res.error) {
|
|
50
|
+
process.stderr.write('codegraph: ' + res.error.message + '\n');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
process.exit(res.status === null ? 1 : res.status);
|
|
54
|
+
}
|
|
23
55
|
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
// Resolve the launcher from the installed per-platform optionalDependency.
|
|
57
|
+
// Returns {command, args} or null if the package isn't installed.
|
|
58
|
+
function resolveInstalledBundle() {
|
|
59
|
+
try {
|
|
60
|
+
if (isWindows) {
|
|
61
|
+
// Modern Node refuses to spawn the bundle's .cmd directly (EINVAL, the
|
|
62
|
+
// CVE-2024-27980 hardening on Node 24), so invoke the bundled node.exe
|
|
63
|
+
// against the app entry point and pass --liftoff-only here.
|
|
64
|
+
var nodeExe = require.resolve(pkg + '/node.exe');
|
|
65
|
+
var entry = require.resolve(pkg + '/lib/dist/bin/codegraph.js');
|
|
66
|
+
return { command: nodeExe, args: liftoff(entry) };
|
|
67
|
+
}
|
|
68
|
+
return { command: require.resolve(pkg + '/bin/codegraph'), args: process.argv.slice(2) };
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Locate the launcher inside an extracted GitHub bundle directory (same
|
|
75
|
+
// node/lib/bin layout as the npm platform package). Returns {command, args} or
|
|
76
|
+
// null when the directory doesn't hold a usable bundle yet.
|
|
77
|
+
function launcherIn(dir) {
|
|
31
78
|
if (isWindows) {
|
|
32
|
-
|
|
33
|
-
var entry =
|
|
34
|
-
|
|
79
|
+
var nodeExe = path.join(dir, 'node.exe');
|
|
80
|
+
var entry = path.join(dir, 'lib', 'dist', 'bin', 'codegraph.js');
|
|
81
|
+
if (fs.existsSync(nodeExe) && fs.existsSync(entry)) {
|
|
82
|
+
return { command: nodeExe, args: liftoff(entry) };
|
|
83
|
+
}
|
|
35
84
|
} else {
|
|
36
|
-
|
|
37
|
-
args
|
|
85
|
+
var launcher = path.join(dir, 'bin', 'codegraph');
|
|
86
|
+
if (fs.existsSync(launcher)) return { command: launcher, args: process.argv.slice(2) };
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// --liftoff-only keeps tree-sitter's WASM grammars off V8's turboshaft tier to
|
|
92
|
+
// avoid the Zone OOM on Node >= 22 (issues #293/#298). The unix bin/codegraph
|
|
93
|
+
// launcher already passes it; on Windows we invoke node.exe directly so add it.
|
|
94
|
+
function liftoff(entry) {
|
|
95
|
+
return ['--liftoff-only', entry].concat(process.argv.slice(2));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Download + cache the platform bundle from GitHub Releases. Returns
|
|
99
|
+
// {command, args}; exits the process with guidance if it can't.
|
|
100
|
+
async function selfHealBundle() {
|
|
101
|
+
var version = readVersion();
|
|
102
|
+
var bundlesDir = path.join(process.env.CODEGRAPH_INSTALL_DIR || path.join(os.homedir(), '.codegraph'), 'bundles');
|
|
103
|
+
var dest = path.join(bundlesDir, target + '-' + version);
|
|
104
|
+
|
|
105
|
+
// Already downloaded by a previous run? Use it even when downloads are
|
|
106
|
+
// disabled — CODEGRAPH_NO_DOWNLOAD blocks fetching, not a cached bundle.
|
|
107
|
+
var cached = launcherIn(dest);
|
|
108
|
+
if (cached) return cached;
|
|
109
|
+
|
|
110
|
+
if (process.env.CODEGRAPH_NO_DOWNLOAD) {
|
|
111
|
+
fail('the network fallback is disabled (CODEGRAPH_NO_DOWNLOAD is set).');
|
|
38
112
|
}
|
|
39
|
-
|
|
113
|
+
|
|
114
|
+
var asset = 'codegraph-' + target + (isWindows ? '.zip' : '.tar.gz');
|
|
115
|
+
var base = process.env.CODEGRAPH_DOWNLOAD_BASE || ('https://github.com/' + REPO + '/releases/download');
|
|
116
|
+
var url = base + '/v' + version + '/' + asset;
|
|
117
|
+
|
|
40
118
|
process.stderr.write(
|
|
41
|
-
'codegraph:
|
|
42
|
-
'
|
|
43
|
-
'Try reinstalling: npm i -g @colbymchenry/codegraph\n' +
|
|
44
|
-
'Or use the standalone installer (no Node required):\n' +
|
|
45
|
-
' curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh\n'
|
|
119
|
+
'codegraph: platform bundle missing (registry did not provide ' + pkg + ').\n' +
|
|
120
|
+
'codegraph: downloading ' + asset + ' from GitHub Releases (' + version + ')...\n'
|
|
46
121
|
);
|
|
47
|
-
|
|
122
|
+
|
|
123
|
+
// Stage inside bundlesDir so the final rename is on the same filesystem (atomic,
|
|
124
|
+
// no EXDEV across tmpfs). Strip the archive's top-level codegraph-<target>/ dir.
|
|
125
|
+
fs.mkdirSync(bundlesDir, { recursive: true });
|
|
126
|
+
var stage = fs.mkdtempSync(path.join(bundlesDir, '.dl-'));
|
|
127
|
+
try {
|
|
128
|
+
var archivePath = path.join(stage, asset);
|
|
129
|
+
await download(url, archivePath, 6);
|
|
130
|
+
await verifyChecksum(archivePath, asset, base, version);
|
|
131
|
+
var extracted = path.join(stage, 'bundle');
|
|
132
|
+
fs.mkdirSync(extracted);
|
|
133
|
+
extract(archivePath, extracted);
|
|
134
|
+
|
|
135
|
+
var raced = launcherIn(dest); // another process may have finished meanwhile
|
|
136
|
+
if (raced) { rmrf(stage); return raced; }
|
|
137
|
+
try {
|
|
138
|
+
fs.renameSync(extracted, dest);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
var other = launcherIn(dest); // lost the race but theirs is valid
|
|
141
|
+
if (other) { rmrf(stage); return other; }
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
rmrf(stage);
|
|
146
|
+
fail('download failed (' + e.message + ').\n URL: ' + url);
|
|
147
|
+
}
|
|
148
|
+
rmrf(stage);
|
|
149
|
+
|
|
150
|
+
var ready = launcherIn(dest);
|
|
151
|
+
if (!ready) fail('downloaded bundle is missing its launcher under ' + dest + '.');
|
|
152
|
+
process.stderr.write('codegraph: bundle ready.\n');
|
|
153
|
+
return ready;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function readVersion() {
|
|
157
|
+
try {
|
|
158
|
+
return require(path.join(__dirname, 'package.json')).version;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
fail('could not read this package\'s version to locate a matching release.');
|
|
161
|
+
}
|
|
48
162
|
}
|
|
49
163
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
164
|
+
// GET with manual redirect following (GitHub release URLs redirect to a CDN).
|
|
165
|
+
function download(url, dest, redirectsLeft) {
|
|
166
|
+
return new Promise(function (resolve, reject) {
|
|
167
|
+
var https = require('https');
|
|
168
|
+
// timeout is an idle/inactivity timeout — it won't kill a slow-but-progressing
|
|
169
|
+
// download, only a stalled connection (so a blocked mirror fails fast with
|
|
170
|
+
// guidance instead of hanging the user's command forever).
|
|
171
|
+
var req = https.get(url, { headers: { 'User-Agent': 'codegraph-npm-shim' }, timeout: 30000 }, function (res) {
|
|
172
|
+
var status = res.statusCode;
|
|
173
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
174
|
+
res.resume();
|
|
175
|
+
if (redirectsLeft <= 0) { reject(new Error('too many redirects')); return; }
|
|
176
|
+
download(new URL(res.headers.location, url).toString(), dest, redirectsLeft - 1).then(resolve, reject);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (status !== 200) { res.resume(); reject(new Error('HTTP ' + status)); return; }
|
|
180
|
+
var file = fs.createWriteStream(dest);
|
|
181
|
+
res.on('error', reject);
|
|
182
|
+
res.pipe(file);
|
|
183
|
+
file.on('error', reject);
|
|
184
|
+
file.on('finish', function () { file.close(function () { resolve(); }); });
|
|
185
|
+
});
|
|
186
|
+
req.on('timeout', function () { req.destroy(new Error('connection timed out')); });
|
|
187
|
+
req.on('error', reject);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Best-effort integrity check. When the release publishes a SHA256SUMS file, the
|
|
192
|
+
// downloaded archive MUST match its listed hash or we abort. When that file is
|
|
193
|
+
// absent (older releases) or simply unreachable, we proceed — the archive still
|
|
194
|
+
// arrived from GitHub over TLS. So tampering/corruption is caught, while a
|
|
195
|
+
// missing checksum never breaks an install.
|
|
196
|
+
async function verifyChecksum(archivePath, asset, base, version) {
|
|
197
|
+
var sumsPath = archivePath + '.SHA256SUMS';
|
|
198
|
+
try {
|
|
199
|
+
await download(base + '/v' + version + '/SHA256SUMS', sumsPath, 6);
|
|
200
|
+
} catch (e) {
|
|
201
|
+
return; // not published / unreachable → skip
|
|
202
|
+
}
|
|
203
|
+
var expected = null;
|
|
204
|
+
var lines = fs.readFileSync(sumsPath, 'utf8').split('\n');
|
|
205
|
+
for (var i = 0; i < lines.length; i++) {
|
|
206
|
+
var m = lines[i].trim().match(/^([0-9a-fA-F]{64})\s+\*?(.+)$/);
|
|
207
|
+
if (m && path.basename(m[2].trim()) === asset) { expected = m[1].toLowerCase(); break; }
|
|
208
|
+
}
|
|
209
|
+
if (!expected) return; // asset not listed → nothing to check
|
|
210
|
+
var actual = require('crypto').createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
|
|
211
|
+
if (actual !== expected) {
|
|
212
|
+
throw new Error('checksum mismatch for ' + asset +
|
|
213
|
+
' (expected ' + expected.slice(0, 12) + '…, got ' + actual.slice(0, 12) + '…)');
|
|
214
|
+
}
|
|
215
|
+
process.stderr.write('codegraph: checksum verified.\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Extract via the system tar — present on macOS, Linux, and Windows 10+
|
|
219
|
+
// (bsdtar reads .zip too). No third-party dependency in the shim.
|
|
220
|
+
function extract(archive, destDir) {
|
|
221
|
+
var args = isWindows
|
|
222
|
+
? ['-xf', archive, '-C', destDir, '--strip-components=1']
|
|
223
|
+
: ['-xzf', archive, '-C', destDir, '--strip-components=1'];
|
|
224
|
+
var res = childProcess.spawnSync('tar', args, { stdio: 'ignore' });
|
|
225
|
+
if (res.error) throw new Error('tar unavailable: ' + res.error.message);
|
|
226
|
+
if (res.status !== 0) throw new Error('tar exited ' + res.status);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function rmrf(p) {
|
|
230
|
+
try { fs.rmSync(p, { recursive: true, force: true }); } catch (e) { /* best effort */ }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function fail(reason) {
|
|
234
|
+
process.stderr.write(
|
|
235
|
+
'codegraph: no prebuilt bundle for ' + target + '.\n' +
|
|
236
|
+
(reason ? 'codegraph: ' + reason + '\n' : '') +
|
|
237
|
+
'Expected the optional package ' + pkg + ' to be installed.\n' +
|
|
238
|
+
'A registry mirror (e.g. npmmirror/cnpm) that did not mirror the per-platform\n' +
|
|
239
|
+
'package is the usual cause. Fixes:\n' +
|
|
240
|
+
' - install from the official registry:\n' +
|
|
241
|
+
' npm i -g @colbymchenry/codegraph --registry=https://registry.npmjs.org\n' +
|
|
242
|
+
' - or use the standalone installer (no Node required):\n' +
|
|
243
|
+
' curl -fsSL https://raw.githubusercontent.com/' + REPO + '/main/install.sh | sh\n'
|
|
244
|
+
);
|
|
53
245
|
process.exit(1);
|
|
54
246
|
}
|
|
55
|
-
process.exit(res.status === null ? 1 : res.status);
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colbymchenry/codegraph",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Local-first code intelligence for AI agents (MCP). Self-contained — bundles its own runtime.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codegraph": "npm-shim.js"
|
|
7
7
|
},
|
|
8
8
|
"optionalDependencies": {
|
|
9
|
-
"@colbymchenry/codegraph-darwin-arm64": "0.9.
|
|
10
|
-
"@colbymchenry/codegraph-darwin-x64": "0.9.
|
|
11
|
-
"@colbymchenry/codegraph-linux-arm64": "0.9.
|
|
12
|
-
"@colbymchenry/codegraph-linux-x64": "0.9.
|
|
13
|
-
"@colbymchenry/codegraph-win32-arm64": "0.9.
|
|
14
|
-
"@colbymchenry/codegraph-win32-x64": "0.9.
|
|
9
|
+
"@colbymchenry/codegraph-darwin-arm64": "0.9.4",
|
|
10
|
+
"@colbymchenry/codegraph-darwin-x64": "0.9.4",
|
|
11
|
+
"@colbymchenry/codegraph-linux-arm64": "0.9.4",
|
|
12
|
+
"@colbymchenry/codegraph-linux-x64": "0.9.4",
|
|
13
|
+
"@colbymchenry/codegraph-win32-arm64": "0.9.4",
|
|
14
|
+
"@colbymchenry/codegraph-win32-x64": "0.9.4"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"npm-shim.js",
|