code-graph-builder 0.6.0 → 0.8.0

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.
Files changed (2) hide show
  1. package/bin/cli.mjs +100 -17
  2. package/package.json +1 -1
package/bin/cli.mjs CHANGED
@@ -10,16 +10,17 @@
10
10
  * npx code-graph-builder --pip # force python3 direct mode
11
11
  */
12
12
 
13
- import { spawn, execFileSync } from "node:child_process";
13
+ import { spawn, execFileSync, execSync } from "node:child_process";
14
14
  import { createInterface } from "node:readline";
15
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
- import { homedir } from "node:os";
16
+ import { homedir, platform } from "node:os";
17
17
  import { join } from "node:path";
18
18
 
19
19
  const PYTHON_PACKAGE = "code-graph-builder";
20
20
  const MODULE_PATH = "code_graph_builder.mcp.server";
21
21
  const WORKSPACE_DIR = join(homedir(), ".code-graph-builder");
22
22
  const ENV_FILE = join(WORKSPACE_DIR, ".env");
23
+ const IS_WIN = platform() === "win32";
23
24
 
24
25
  // ---------------------------------------------------------------------------
25
26
  // Utilities
@@ -27,16 +28,40 @@ const ENV_FILE = join(WORKSPACE_DIR, ".env");
27
28
 
28
29
  function commandExists(cmd) {
29
30
  try {
30
- execFileSync("which", [cmd], { stdio: "pipe" });
31
+ // "which" on Unix/macOS, "where" on Windows
32
+ const checker = IS_WIN ? "where" : "which";
33
+ execFileSync(checker, [cmd], { stdio: "pipe" });
31
34
  return true;
32
35
  } catch {
33
36
  return false;
34
37
  }
35
38
  }
36
39
 
40
+ /**
41
+ * Find a working Python command. On Windows the command is typically
42
+ * "python" (the py-launcher or Store stub), while on Unix it is "python3".
43
+ * Returns the command string or null if none is found.
44
+ */
45
+ function findPython() {
46
+ const candidates = IS_WIN
47
+ ? ["python", "python3", "py"]
48
+ : ["python3", "python"];
49
+ for (const cmd of candidates) {
50
+ try {
51
+ const ver = execFileSync(cmd, ["--version"], { stdio: "pipe" }).toString().trim();
52
+ // Ensure it is Python 3.x
53
+ if (ver.includes("3.")) return cmd;
54
+ } catch { /* skip */ }
55
+ }
56
+ return null;
57
+ }
58
+
59
+ const PYTHON_CMD = findPython();
60
+
37
61
  function pythonPackageInstalled() {
62
+ if (!PYTHON_CMD) return false;
38
63
  try {
39
- execFileSync("python3", ["-c", `import ${MODULE_PATH.split(".")[0]}`], {
64
+ execFileSync(PYTHON_CMD, ["-c", `import ${MODULE_PATH.split(".")[0]}`], {
40
65
  stdio: "pipe",
41
66
  });
42
67
  return true;
@@ -268,7 +293,7 @@ async function runSetup() {
268
293
  log(' "mcpServers": {');
269
294
  log(' "code-graph-builder": {');
270
295
  log(' "command": "npx",');
271
- log(' "args": ["-y", "code-graph-builder", "--server"]');
296
+ log(' "args": ["-y", "code-graph-builder@latest", "--server"]');
272
297
  log(" }");
273
298
  log(" }");
274
299
  log(" }");
@@ -294,6 +319,7 @@ function runServer(cmd, args) {
294
319
  const child = spawn(cmd, args, {
295
320
  stdio: "inherit",
296
321
  env: mergedEnv,
322
+ shell: IS_WIN, // Windows needs shell for .cmd/.ps1 scripts (uvx, pipx, etc.)
297
323
  });
298
324
 
299
325
  child.on("error", (err) => {
@@ -306,6 +332,70 @@ function runServer(cmd, args) {
306
332
  });
307
333
  }
308
334
 
335
+ /**
336
+ * Find a working pip command. Returns [cmd, ...prefixArgs] or null.
337
+ * Tries: pip3, pip, python3 -m pip, python -m pip
338
+ */
339
+ function findPip() {
340
+ // Standalone pip
341
+ for (const cmd of IS_WIN ? ["pip", "pip3"] : ["pip3", "pip"]) {
342
+ if (commandExists(cmd)) return [cmd];
343
+ }
344
+ // python -m pip fallback
345
+ if (PYTHON_CMD) {
346
+ try {
347
+ execFileSync(PYTHON_CMD, ["-m", "pip", "--version"], { stdio: "pipe" });
348
+ return [PYTHON_CMD, "-m", "pip"];
349
+ } catch { /* skip */ }
350
+ }
351
+ return null;
352
+ }
353
+
354
+ /**
355
+ * Auto-install the Python package via pip, then start the server.
356
+ */
357
+ function autoInstallAndStart(extraArgs) {
358
+ const pip = findPip();
359
+ if (!pip) {
360
+ process.stderr.write(
361
+ `code-graph-builder requires Python 3.10+ with pip.\n\n` +
362
+ (PYTHON_CMD
363
+ ? `Python found (${PYTHON_CMD}) but pip is not available.\n\n`
364
+ : `Python 3 not found on PATH.\n\n`) +
365
+ `Please install Python 3.10+ first, then run:\n` +
366
+ ` npx code-graph-builder --server\n`
367
+ );
368
+ process.exit(1);
369
+ }
370
+
371
+ process.stderr.write(`Installing ${PYTHON_PACKAGE}...\n`);
372
+
373
+ try {
374
+ execSync(
375
+ [...pip, "install", PYTHON_PACKAGE].map(s => `"${s}"`).join(" "),
376
+ { stdio: "inherit", shell: true }
377
+ );
378
+ } catch (err) {
379
+ process.stderr.write(
380
+ `\nFailed to install ${PYTHON_PACKAGE}.\n` +
381
+ `Try manually: ${pip.join(" ")} install ${PYTHON_PACKAGE}\n`
382
+ );
383
+ process.exit(1);
384
+ }
385
+
386
+ // Verify installation succeeded
387
+ if (!pythonPackageInstalled()) {
388
+ process.stderr.write(
389
+ `\nInstallation completed but package not importable.\n` +
390
+ `Try manually: ${pip.join(" ")} install ${PYTHON_PACKAGE}\n`
391
+ );
392
+ process.exit(1);
393
+ }
394
+
395
+ process.stderr.write(`${PYTHON_PACKAGE} installed successfully.\n`);
396
+ runServer(PYTHON_CMD, ["-m", MODULE_PATH]);
397
+ }
398
+
309
399
  function startServer(extraArgs = []) {
310
400
  if (commandExists("uvx")) {
311
401
  runServer("uvx", [PYTHON_PACKAGE, ...extraArgs]);
@@ -314,17 +404,10 @@ function startServer(extraArgs = []) {
314
404
  } else if (commandExists("pipx")) {
315
405
  runServer("pipx", ["run", PYTHON_PACKAGE, ...extraArgs]);
316
406
  } else if (pythonPackageInstalled()) {
317
- runServer("python3", ["-m", MODULE_PATH]);
407
+ runServer(PYTHON_CMD, ["-m", MODULE_PATH]);
318
408
  } else {
319
- process.stderr.write(
320
- `code-graph-builder requires Python 3.10+.\n\n` +
321
- `Install options:\n` +
322
- ` 1. pip install ${PYTHON_PACKAGE}\n` +
323
- ` 2. curl -LsSf https://astral.sh/uv/install.sh | sh (installs uv)\n` +
324
- ` 3. pip install pipx\n\n` +
325
- `Then run: npx code-graph-builder --server\n`
326
- );
327
- process.exit(1);
409
+ // Auto-install via pip
410
+ autoInstallAndStart(extraArgs);
328
411
  }
329
412
  }
330
413
 
@@ -341,14 +424,14 @@ if (mode === "--setup") {
341
424
  } else if (mode === "--server" || mode === "--pip" || mode === "--python") {
342
425
  // Start MCP server directly
343
426
  if (mode === "--pip" || mode === "--python") {
344
- if (!pythonPackageInstalled()) {
427
+ if (!PYTHON_CMD || !pythonPackageInstalled()) {
345
428
  process.stderr.write(
346
429
  `Error: Python package '${PYTHON_PACKAGE}' is not installed.\n` +
347
430
  `Run: pip install ${PYTHON_PACKAGE}\n`
348
431
  );
349
432
  process.exit(1);
350
433
  }
351
- runServer("python3", ["-m", MODULE_PATH]);
434
+ runServer(PYTHON_CMD, ["-m", MODULE_PATH]);
352
435
  } else {
353
436
  startServer(args.slice(1));
354
437
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-graph-builder",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Code knowledge graph builder with MCP server for AI-assisted code navigation",
5
5
  "license": "MIT",
6
6
  "bin": {