@leanlabsinnov/codegraph 0.1.2 → 0.1.3

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.
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  GraphDb,
7
7
  defaultDbPath
8
- } from "./chunk-2TORJYBO.js";
8
+ } from "./chunk-AVP24SX5.js";
9
9
  import {
10
10
  DEFAULT_CONFIG,
11
11
  LLM_PRESETS,
@@ -374,20 +374,20 @@ function checkNodeVersion() {
374
374
  detail: `v${version} (codegraph requires >= 20)`
375
375
  };
376
376
  }
377
- async function checkWritable(name, path6) {
377
+ async function checkWritable(name, path8) {
378
378
  try {
379
- await mkdir2(dirname(path6), { recursive: true });
379
+ await mkdir2(dirname(path8), { recursive: true });
380
380
  try {
381
- await access(path6, constants.W_OK);
381
+ await access(path8, constants.W_OK);
382
382
  } catch {
383
- await access(dirname(path6), constants.W_OK);
383
+ await access(dirname(path8), constants.W_OK);
384
384
  }
385
- return { name, status: "ok", detail: path6 };
385
+ return { name, status: "ok", detail: path8 };
386
386
  } catch (err) {
387
387
  return {
388
388
  name,
389
389
  status: "fail",
390
- detail: `${path6} (${err instanceof Error ? err.message : String(err)})`
390
+ detail: `${path8} (${err instanceof Error ? err.message : String(err)})`
391
391
  };
392
392
  }
393
393
  }
@@ -458,7 +458,8 @@ async function selfTestKuzu(dbPath, embeddingDimension) {
458
458
  }
459
459
 
460
460
  // src/commands/index.ts
461
- import path5 from "path";
461
+ import { rm } from "fs/promises";
462
+ import path6 from "path";
462
463
 
463
464
  // ../ingestion/src/parser.ts
464
465
  import { readFile as readFile2 } from "fs/promises";
@@ -879,9 +880,11 @@ function functionFromDeclaration(node, input, source) {
879
880
  if (!nameNode) return null;
880
881
  const name = nodeText(nameNode, source);
881
882
  const line = startLine(node);
883
+ const isComponent = (input.language === "tsx" || input.language === "jsx") && isPascalCase(name) && containsJsx(node.childForFieldName("body"));
884
+ const kind = isComponent ? "Component" : "Function";
882
885
  const id = makeNodeId({
883
886
  repoId: input.repoId,
884
- kind: "Function",
887
+ kind,
885
888
  path: input.relativePath,
886
889
  name,
887
890
  line
@@ -889,7 +892,7 @@ function functionFromDeclaration(node, input, source) {
889
892
  return {
890
893
  node: {
891
894
  id,
892
- kind: "Function",
895
+ kind,
893
896
  repoId: input.repoId,
894
897
  name,
895
898
  path: input.relativePath,
@@ -898,7 +901,7 @@ function functionFromDeclaration(node, input, source) {
898
901
  signature: extractSignature(node, source),
899
902
  leadingComment: leadingCommentFor(parentForLeadingComment(node), source),
900
903
  isExported: isExported(node),
901
- isAsync: hasAsyncModifier(node, source)
904
+ ...kind === "Function" ? { isAsync: hasAsyncModifier(node, source) } : {}
902
905
  }
903
906
  };
904
907
  }
@@ -968,6 +971,9 @@ function variableOrArrowFromDeclaration(node, input, source) {
968
971
  const nameNode = decl.childForFieldName("name");
969
972
  const value = decl.childForFieldName("value");
970
973
  if (!nameNode) return null;
974
+ if (nameNode.type !== "identifier" && nameNode.type !== "property_identifier") {
975
+ return null;
976
+ }
971
977
  const name = nodeText(nameNode, source);
972
978
  const line = startLine(decl);
973
979
  const isArrow = value?.type === "arrow_function" || value?.type === "function_expression";
@@ -1032,6 +1038,10 @@ function extractSignature(node, source) {
1032
1038
  function extractCalleeName(callee, source) {
1033
1039
  if (callee.type === "identifier") return nodeText(callee, source);
1034
1040
  if (callee.type === "member_expression") {
1041
+ const object = callee.childForFieldName("object");
1042
+ if (object && object.type === "identifier") {
1043
+ return nodeText(object, source);
1044
+ }
1035
1045
  const property = callee.childForFieldName("property");
1036
1046
  if (property) return nodeText(property, source);
1037
1047
  }
@@ -1146,7 +1156,8 @@ function resolveEdges(input) {
1146
1156
  repoId: input.repoId,
1147
1157
  fromPath: edge.fromPath ?? "",
1148
1158
  spec: edge.unresolvedTargetName,
1149
- known: input.knownFilePaths
1159
+ known: input.knownFilePaths,
1160
+ ...input.tsconfigPaths ? { tsconfigPaths: input.tsconfigPaths } : {}
1150
1161
  });
1151
1162
  if (!resolved) {
1152
1163
  dropped++;
@@ -1168,10 +1179,22 @@ function resolveEdges(input) {
1168
1179
  return { resolved: out, dropped };
1169
1180
  }
1170
1181
  function resolveImportPath(input) {
1171
- const { fromPath, spec, known } = input;
1172
- if (!spec.startsWith(".") && !spec.startsWith("/")) return null;
1173
- const baseDir = path2.posix.dirname(toPosix(fromPath));
1174
- const joined = path2.posix.normalize(path2.posix.join(baseDir, toPosix(spec)));
1182
+ const { fromPath, spec, known, tsconfigPaths } = input;
1183
+ if (spec.startsWith(".") || spec.startsWith("/")) {
1184
+ const baseDir = path2.posix.dirname(toPosix(fromPath));
1185
+ const joined = path2.posix.normalize(path2.posix.join(baseDir, toPosix(spec)));
1186
+ return firstMatchingCandidate(joined, known);
1187
+ }
1188
+ if (tsconfigPaths) {
1189
+ const candidates = expandAliasCandidates(spec, tsconfigPaths);
1190
+ for (const c of candidates) {
1191
+ const resolved = firstMatchingCandidate(c, known);
1192
+ if (resolved) return resolved;
1193
+ }
1194
+ }
1195
+ return null;
1196
+ }
1197
+ function firstMatchingCandidate(joined, known) {
1175
1198
  const candidates = [joined];
1176
1199
  const ext = path2.posix.extname(joined);
1177
1200
  const stem = ext ? joined.slice(0, -ext.length) : joined;
@@ -1182,12 +1205,37 @@ function resolveImportPath(input) {
1182
1205
  }
1183
1206
  return null;
1184
1207
  }
1208
+ function expandAliasCandidates(spec, cfg) {
1209
+ const sortedPatterns = Object.keys(cfg.paths).sort((a, b) => b.length - a.length);
1210
+ const out = [];
1211
+ for (const pattern of sortedPatterns) {
1212
+ const replacements = cfg.paths[pattern] ?? [];
1213
+ if (pattern.endsWith("/*")) {
1214
+ const prefix = pattern.slice(0, -1);
1215
+ if (!spec.startsWith(prefix)) continue;
1216
+ const tail = spec.slice(prefix.length);
1217
+ for (const r of replacements) {
1218
+ const rTail = r.endsWith("/*") ? r.slice(0, -1) : r.endsWith("*") ? r.slice(0, -1) : r;
1219
+ const joined = path2.posix.normalize(
1220
+ path2.posix.join(toPosix(cfg.baseUrl), `${rTail}${tail}`)
1221
+ );
1222
+ out.push(joined);
1223
+ }
1224
+ } else if (spec === pattern) {
1225
+ for (const r of replacements) {
1226
+ const joined = path2.posix.normalize(path2.posix.join(toPosix(cfg.baseUrl), r));
1227
+ out.push(joined);
1228
+ }
1229
+ }
1230
+ }
1231
+ return out;
1232
+ }
1185
1233
  function toPosix(p) {
1186
1234
  return p.split(path2.sep).join("/");
1187
1235
  }
1188
1236
 
1189
1237
  // ../ingestion/src/orchestrator.ts
1190
- import { readFile as readFile4, stat as stat2 } from "fs/promises";
1238
+ import { readFile as readFile5, stat as stat2 } from "fs/promises";
1191
1239
  import { cpus } from "os";
1192
1240
  import { join as join2 } from "path";
1193
1241
  import { createHash as createHash2 } from "crypto";
@@ -1226,25 +1274,127 @@ async function embedNodes(nodes, opts) {
1226
1274
  return result;
1227
1275
  }
1228
1276
 
1229
- // ../ingestion/src/repo-walker.ts
1230
- import { readFile as readFile3, readdir, stat } from "fs/promises";
1277
+ // ../ingestion/src/tsconfig.ts
1278
+ import { readFile as readFile3 } from "fs/promises";
1231
1279
  import path3 from "path";
1280
+ async function loadTsconfigPaths(repoRoot) {
1281
+ for (const name of ["tsconfig.json", "jsconfig.json"]) {
1282
+ const raw = await readIfExists(path3.join(repoRoot, name));
1283
+ if (!raw) continue;
1284
+ let parsed;
1285
+ try {
1286
+ parsed = JSON.parse(stripJsonComments(raw));
1287
+ } catch {
1288
+ continue;
1289
+ }
1290
+ const co = parsed.compilerOptions ?? {};
1291
+ const paths = co.paths ?? {};
1292
+ if (Object.keys(paths).length === 0) continue;
1293
+ const baseUrl = (co.baseUrl ?? ".").trim();
1294
+ return {
1295
+ baseUrl: toPosix2(baseUrl),
1296
+ paths
1297
+ };
1298
+ }
1299
+ return null;
1300
+ }
1301
+ async function readIfExists(filePath) {
1302
+ try {
1303
+ return await readFile3(filePath, "utf8");
1304
+ } catch {
1305
+ return null;
1306
+ }
1307
+ }
1308
+ function stripJsonComments(input) {
1309
+ let out = "";
1310
+ let i = 0;
1311
+ let inString = false;
1312
+ let stringChar = "";
1313
+ while (i < input.length) {
1314
+ const c = input[i];
1315
+ const next = input[i + 1];
1316
+ if (inString) {
1317
+ out += c;
1318
+ if (c === "\\" && next !== void 0) {
1319
+ out += next;
1320
+ i += 2;
1321
+ continue;
1322
+ }
1323
+ if (c === stringChar) inString = false;
1324
+ i++;
1325
+ continue;
1326
+ }
1327
+ if (c === '"' || c === "'") {
1328
+ inString = true;
1329
+ stringChar = c;
1330
+ out += c;
1331
+ i++;
1332
+ continue;
1333
+ }
1334
+ if (c === "/" && next === "/") {
1335
+ while (i < input.length && input[i] !== "\n") i++;
1336
+ continue;
1337
+ }
1338
+ if (c === "/" && next === "*") {
1339
+ i += 2;
1340
+ while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
1341
+ i += 2;
1342
+ continue;
1343
+ }
1344
+ out += c;
1345
+ i++;
1346
+ }
1347
+ return removeTrailingCommas(out);
1348
+ }
1349
+ function removeTrailingCommas(s) {
1350
+ return s.replace(/,\s*([}\]])/g, "$1");
1351
+ }
1352
+ function toPosix2(p) {
1353
+ return p.split(path3.sep).join("/");
1354
+ }
1355
+
1356
+ // ../ingestion/src/repo-walker.ts
1357
+ import { readFile as readFile4, readdir, stat } from "fs/promises";
1358
+ import path4 from "path";
1232
1359
  import ignore from "ignore";
1233
1360
  var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
1234
1361
  ".git",
1362
+ ".hg",
1363
+ ".svn",
1235
1364
  "node_modules",
1236
1365
  ".next",
1366
+ ".nuxt",
1367
+ ".svelte-kit",
1237
1368
  "dist",
1238
1369
  "build",
1239
1370
  "out",
1240
1371
  ".turbo",
1241
1372
  ".cache",
1373
+ ".parcel-cache",
1242
1374
  "coverage",
1375
+ ".nyc_output",
1243
1376
  ".idea",
1244
- ".vscode"
1377
+ ".vscode",
1378
+ ".vs",
1379
+ "venv",
1380
+ ".venv",
1381
+ "env",
1382
+ "__pycache__",
1383
+ "site-packages",
1384
+ ".pytest_cache",
1385
+ ".mypy_cache",
1386
+ ".ruff_cache",
1387
+ ".tox",
1388
+ ".eggs",
1389
+ "target",
1390
+ "vendor",
1391
+ "Pods",
1392
+ "DerivedData",
1393
+ ".gradle",
1394
+ ".terraform"
1245
1395
  ]);
1246
1396
  async function walkRepo(root) {
1247
- const base = path3.resolve(root);
1397
+ const base = path4.resolve(root);
1248
1398
  const matcher = ignore();
1249
1399
  await loadGitignoreInto(matcher, base);
1250
1400
  const out = [];
@@ -1261,7 +1411,7 @@ async function walkDir(absDir, relDir, ig, out) {
1261
1411
  for (const entry of entries) {
1262
1412
  const name = entry.name;
1263
1413
  const rel = relDir ? `${relDir}/${name}` : name;
1264
- const abs = path3.join(absDir, name);
1414
+ const abs = path4.join(absDir, name);
1265
1415
  if (entry.isDirectory()) {
1266
1416
  if (ALWAYS_SKIP_DIRS.has(name)) continue;
1267
1417
  if (ig.ignores(`${rel}/`)) continue;
@@ -1275,9 +1425,9 @@ async function walkDir(absDir, relDir, ig, out) {
1275
1425
  }
1276
1426
  }
1277
1427
  async function loadGitignoreInto(ig, absDir) {
1278
- const file = path3.join(absDir, ".gitignore");
1428
+ const file = path4.join(absDir, ".gitignore");
1279
1429
  try {
1280
- const text = await readFile3(file, "utf8");
1430
+ const text = await readFile4(file, "utf8");
1281
1431
  ig.add(text);
1282
1432
  } catch {
1283
1433
  }
@@ -1307,7 +1457,7 @@ async function indexRepo(opts) {
1307
1457
  await runWithConcurrency(parsable, parallelism, async (entry) => {
1308
1458
  const abs = join2(opts.repoPath, entry.rel);
1309
1459
  try {
1310
- const source = await readFile4(abs, "utf8");
1460
+ const source = await readFile5(abs, "utf8");
1311
1461
  const result = await extractFile({
1312
1462
  repoId: opts.repoId,
1313
1463
  relativePath: entry.rel,
@@ -1324,11 +1474,13 @@ async function indexRepo(opts) {
1324
1474
  opts.onProgress?.({ type: "parse", parsed: parsedCount, total: parsable.length });
1325
1475
  }
1326
1476
  });
1477
+ const tsconfigPaths = await loadTsconfigPaths(opts.repoPath);
1327
1478
  const { resolved, dropped } = resolveEdges({
1328
1479
  repoId: opts.repoId,
1329
1480
  nodes: allNodes,
1330
1481
  edges: allEdges,
1331
- knownFilePaths
1482
+ knownFilePaths,
1483
+ ...tsconfigPaths ? { tsconfigPaths } : {}
1332
1484
  });
1333
1485
  let embeddedById = /* @__PURE__ */ new Map();
1334
1486
  if (!opts.skipEmbeddings && opts.router) {
@@ -1377,10 +1529,10 @@ import kleur4 from "kleur";
1377
1529
 
1378
1530
  // src/repo-id.ts
1379
1531
  import { createHash as createHash3 } from "crypto";
1380
- import path4 from "path";
1532
+ import path5 from "path";
1381
1533
  function repoIdFromPath(absPath) {
1382
- const base = path4.basename(path4.resolve(absPath));
1383
- const sha = createHash3("sha1").update(path4.resolve(absPath)).digest("hex").slice(0, 8);
1534
+ const base = path5.basename(path5.resolve(absPath));
1535
+ const sha = createHash3("sha1").update(path5.resolve(absPath)).digest("hex").slice(0, 8);
1384
1536
  const safe = base.replace(/[^A-Za-z0-9_-]/g, "_");
1385
1537
  return `${safe}-${sha}`;
1386
1538
  }
@@ -1388,7 +1540,7 @@ function repoIdFromPath(absPath) {
1388
1540
  // src/commands/index.ts
1389
1541
  async function runIndexCommand(opts) {
1390
1542
  const config = await loadConfig();
1391
- const absolutePath = path5.resolve(opts.repoPath);
1543
+ const absolutePath = path6.resolve(opts.repoPath);
1392
1544
  const repoId = repoIdFromPath(absolutePath);
1393
1545
  const dbPath = config.data.dbPath ?? defaultDbPath();
1394
1546
  console.log(kleur4.dim(`repo: ${absolutePath}`));
@@ -1426,8 +1578,10 @@ async function runIndexCommand(opts) {
1426
1578
  router = void 0;
1427
1579
  }
1428
1580
  }
1429
- let parseBar = null;
1430
- let embedBar = null;
1581
+ const bars = {
1582
+ parse: null,
1583
+ embed: null
1584
+ };
1431
1585
  const result = await indexRepo({
1432
1586
  repoId,
1433
1587
  repoPath: absolutePath,
@@ -1439,8 +1593,8 @@ async function runIndexCommand(opts) {
1439
1593
  console.log(kleur4.dim(`walked ${event.files} files`));
1440
1594
  }
1441
1595
  if (event.type === "parse") {
1442
- if (!parseBar) parseBar = new ProgressBar("parsing", event.total);
1443
- parseBar.update(event.parsed);
1596
+ if (!bars.parse) bars.parse = new ProgressBar("parsing", event.total);
1597
+ bars.parse.update(event.parsed);
1444
1598
  }
1445
1599
  if (event.type === "upsert") {
1446
1600
  console.log(
@@ -1448,13 +1602,13 @@ async function runIndexCommand(opts) {
1448
1602
  );
1449
1603
  }
1450
1604
  if (event.type === "embed") {
1451
- if (!embedBar) embedBar = new ProgressBar("embedding", event.total);
1452
- embedBar.update(event.embedded);
1605
+ if (!bars.embed) bars.embed = new ProgressBar("embedding", event.total);
1606
+ bars.embed.update(event.embedded);
1453
1607
  }
1454
1608
  }
1455
1609
  });
1456
- parseBar?.done();
1457
- embedBar?.done();
1610
+ bars.parse?.done();
1611
+ bars.embed?.done();
1458
1612
  await graphDb.close();
1459
1613
  console.log();
1460
1614
  console.log(
@@ -1470,7 +1624,7 @@ async function runIndexCommand(opts) {
1470
1624
  }
1471
1625
  async function runStatusCommand(opts) {
1472
1626
  const config = await loadConfig();
1473
- const absolutePath = path5.resolve(opts.repoPath);
1627
+ const absolutePath = path6.resolve(opts.repoPath);
1474
1628
  const repoId = repoIdFromPath(absolutePath);
1475
1629
  const graphDb = new GraphDb({
1476
1630
  dbPath: config.data.dbPath ?? defaultDbPath(),
@@ -1488,10 +1642,62 @@ async function runStatusCommand(opts) {
1488
1642
  throw err;
1489
1643
  }
1490
1644
  }
1645
+ async function runWipeCommand(opts) {
1646
+ const config = await loadConfig();
1647
+ const dbPath = config.data.dbPath ?? defaultDbPath();
1648
+ if (opts.repoPath) {
1649
+ const absolutePath = path6.resolve(opts.repoPath);
1650
+ const repoId = repoIdFromPath(absolutePath);
1651
+ if (!opts.yes) {
1652
+ console.log(
1653
+ kleur4.yellow(
1654
+ `Will delete all graph rows for ${kleur4.bold(repoId)} (${absolutePath}). Pass --yes to confirm.`
1655
+ )
1656
+ );
1657
+ return;
1658
+ }
1659
+ const spinner2 = makeSpinner(`Wiping ${repoId}`).start();
1660
+ const graphDb = new GraphDb({
1661
+ dbPath,
1662
+ embeddingDimension: config.llm.embeddings.dimension
1663
+ });
1664
+ try {
1665
+ await graphDb.connect();
1666
+ await graphDb.migrate();
1667
+ await graphDb.deleteByRepo(repoId);
1668
+ await graphDb.close();
1669
+ spinner2.succeed(`Removed ${repoId} from ${dbPath}`);
1670
+ } catch (err) {
1671
+ spinner2.fail("Wipe failed");
1672
+ throw err;
1673
+ }
1674
+ return;
1675
+ }
1676
+ if (!opts.yes) {
1677
+ console.log(
1678
+ kleur4.yellow(
1679
+ `Will delete the entire graph directory at ${kleur4.bold(dbPath)}. Pass --yes to confirm.`
1680
+ )
1681
+ );
1682
+ return;
1683
+ }
1684
+ const spinner = makeSpinner(`Removing ${dbPath}`).start();
1685
+ try {
1686
+ await rm(dbPath, { recursive: true, force: true });
1687
+ spinner.succeed(`Graph directory removed. Re-run \`codegraph index <repo>\` to rebuild.`);
1688
+ } catch (err) {
1689
+ spinner.fail("Wipe failed");
1690
+ throw err;
1691
+ }
1692
+ }
1693
+
1694
+ // src/commands/init.ts
1695
+ import path7 from "path";
1696
+ import { confirm, password as password2, select as select2 } from "@inquirer/prompts";
1491
1697
 
1492
1698
  // ../mcp-server/dist/index.js
1493
1699
  import { createHash as createHash4 } from "crypto";
1494
- import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
1700
+ import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
1495
1701
  import { homedir } from "os";
1496
1702
  import { dirname as dirname3, resolve } from "path";
1497
1703
  import { randomUUID } from "crypto";
@@ -1587,8 +1793,8 @@ var affectedByTool = {
1587
1793
  inputSchema,
1588
1794
  annotations: { readOnlyHint: true, idempotentHint: true }
1589
1795
  },
1590
- handler: async ({ symbol, path: path6, depth, limit }, deps) => cachedJsonResult("affected_by", { symbol, path: path6, depth, limit }, deps, async () => {
1591
- const where = path6 ? "WHERE target.name = $symbol AND target.path = $path" : "WHERE target.name = $symbol";
1796
+ handler: async ({ symbol, path: path8, depth, limit }, deps) => cachedJsonResult("affected_by", { symbol, path: path8, depth, limit }, deps, async () => {
1797
+ const where = path8 ? "WHERE target.name = $symbol AND target.path = $path" : "WHERE target.name = $symbol";
1592
1798
  const rows = await deps.graph.query(
1593
1799
  `MATCH (target:Symbol) ${where}
1594
1800
  WITH target
@@ -1597,9 +1803,9 @@ var affectedByTool = {
1597
1803
  RETURN affected.id AS id, affected.name AS name, affected.kind AS kind,
1598
1804
  affected.path AS path, distance
1599
1805
  ORDER BY distance ASC, affected.path ASC LIMIT $limit`,
1600
- { symbol, path: path6 ?? null, limit }
1806
+ { symbol, path: path8 ?? null, limit }
1601
1807
  );
1602
- return { symbol, path: path6 ?? null, depth, count: rows.length, affected: rows };
1808
+ return { symbol, path: path8 ?? null, depth, count: rows.length, affected: rows };
1603
1809
  })
1604
1810
  };
1605
1811
  var inputSchema2 = {
@@ -1616,14 +1822,14 @@ var blastRadiusTool = {
1616
1822
  inputSchema: inputSchema2,
1617
1823
  annotations: { readOnlyHint: true, idempotentHint: true }
1618
1824
  },
1619
- handler: async ({ symbol, path: path6, depth, sampleSize }, deps) => cachedJsonResult("blast_radius", { symbol, path: path6, depth, sampleSize }, deps, async () => {
1620
- const where = path6 ? "WHERE target.name = $symbol AND target.path = $path" : "WHERE target.name = $symbol";
1825
+ handler: async ({ symbol, path: path8, depth, sampleSize }, deps) => cachedJsonResult("blast_radius", { symbol, path: path8, depth, sampleSize }, deps, async () => {
1826
+ const where = path8 ? "WHERE target.name = $symbol AND target.path = $path" : "WHERE target.name = $symbol";
1621
1827
  const [stats] = await deps.graph.query(
1622
1828
  `MATCH (target:Symbol) ${where}
1623
1829
  WITH target
1624
1830
  MATCH (dependent:Symbol)-[:CALLS|IMPORTS|RENDERS*1..${depth}]->(target)
1625
1831
  RETURN count(DISTINCT dependent) AS total`,
1626
- { symbol, path: path6 ?? null }
1832
+ { symbol, path: path8 ?? null }
1627
1833
  );
1628
1834
  const sample = await deps.graph.query(
1629
1835
  `MATCH (target:Symbol) ${where}
@@ -1633,11 +1839,11 @@ var blastRadiusTool = {
1633
1839
  RETURN dependent.id AS id, dependent.name AS name, dependent.kind AS kind,
1634
1840
  dependent.path AS path, distance
1635
1841
  ORDER BY distance ASC, dependent.path ASC LIMIT $sampleSize`,
1636
- { symbol, path: path6 ?? null, sampleSize }
1842
+ { symbol, path: path8 ?? null, sampleSize }
1637
1843
  );
1638
1844
  return {
1639
1845
  symbol,
1640
- path: path6 ?? null,
1846
+ path: path8 ?? null,
1641
1847
  depth,
1642
1848
  totalDependents: stats?.total ?? 0,
1643
1849
  sample
@@ -1704,15 +1910,46 @@ var getComponentTreeTool = {
1704
1910
  annotations: { readOnlyHint: true, idempotentHint: true }
1705
1911
  },
1706
1912
  handler: async ({ name, depth }, deps) => cachedJsonResult("get_component_tree", { name, depth }, deps, async () => {
1707
- const rows = await deps.graph.query(
1708
- `MATCH (root:Symbol {kind: 'Component', name: $name})
1709
- OPTIONAL MATCH path = (root)-[:RENDERS*1..${depth}]->(child:Symbol)
1710
- WHERE child.kind = 'Component'
1711
- WITH root, collect(DISTINCT { name: child.name, id: child.id, path: child.path, depth: length(path) }) AS children
1712
- RETURN root.id AS rootId, root.name AS rootName, root.path AS rootPath, children`,
1913
+ const rootRows = await deps.graph.query(
1914
+ `MATCH (root:Symbol)
1915
+ WHERE root.name = $name AND (root.kind = 'Component' OR root.kind = 'Function' OR root.kind = 'Class')
1916
+ RETURN root.id AS rootId, root.name AS rootName, root.path AS rootPath, root.kind AS rootKind
1917
+ ORDER BY CASE root.kind WHEN 'Component' THEN 0 WHEN 'Function' THEN 1 ELSE 2 END
1918
+ LIMIT 1`,
1713
1919
  { name }
1714
1920
  );
1715
- return { name, depth, tree: rows[0] ?? null };
1921
+ const root = rootRows[0];
1922
+ if (!root) {
1923
+ return {
1924
+ name,
1925
+ depth,
1926
+ found: false,
1927
+ root: null,
1928
+ children: []
1929
+ };
1930
+ }
1931
+ const children = await deps.graph.query(
1932
+ `MATCH (root:Symbol {id: $rootId})
1933
+ OPTIONAL MATCH p = (root)-[:RENDERS*1..${depth}]->(child:Symbol)
1934
+ WITH child, min(length(p)) AS distance
1935
+ WHERE child IS NOT NULL
1936
+ RETURN child.id AS id, child.name AS name, child.kind AS kind,
1937
+ child.path AS path, distance
1938
+ ORDER BY distance ASC, child.name ASC`,
1939
+ { rootId: root.rootId }
1940
+ );
1941
+ return {
1942
+ name,
1943
+ depth,
1944
+ found: true,
1945
+ root: {
1946
+ id: root.rootId,
1947
+ name: root.rootName,
1948
+ path: root.rootPath,
1949
+ kind: root.rootKind
1950
+ },
1951
+ children
1952
+ };
1716
1953
  })
1717
1954
  };
1718
1955
  var inputSchema6 = {
@@ -1728,7 +1965,7 @@ var getDependenciesTool = {
1728
1965
  inputSchema: inputSchema6,
1729
1966
  annotations: { readOnlyHint: true, idempotentHint: true }
1730
1967
  },
1731
- handler: async ({ path: path6, depth, limit }, deps) => cachedJsonResult("get_dependencies", { path: path6, depth, limit }, deps, async () => {
1968
+ handler: async ({ path: path8, depth, limit }, deps) => cachedJsonResult("get_dependencies", { path: path8, depth, limit }, deps, async () => {
1732
1969
  const rows = await deps.graph.query(
1733
1970
  `MATCH (root:Symbol)
1734
1971
  WHERE root.kind = 'File' AND root.path = $path
@@ -1738,9 +1975,9 @@ var getDependenciesTool = {
1738
1975
  WHERE dep IS NOT NULL
1739
1976
  RETURN dep.id AS id, dep.path AS path, distance
1740
1977
  ORDER BY distance ASC, dep.path ASC LIMIT $limit`,
1741
- { path: path6, limit }
1978
+ { path: path8, limit }
1742
1979
  );
1743
- return { path: path6, depth, count: rows.length, dependencies: rows };
1980
+ return { path: path8, depth, count: rows.length, dependencies: rows };
1744
1981
  })
1745
1982
  };
1746
1983
  var inputSchema7 = {
@@ -1755,15 +1992,15 @@ var getFileContextTool = {
1755
1992
  inputSchema: inputSchema7,
1756
1993
  annotations: { readOnlyHint: true, idempotentHint: true }
1757
1994
  },
1758
- handler: async ({ path: path6, symbolLimit }, deps) => cachedJsonResult("get_file_context", { path: path6, symbolLimit }, deps, async () => {
1995
+ handler: async ({ path: path8, symbolLimit }, deps) => cachedJsonResult("get_file_context", { path: path8, symbolLimit }, deps, async () => {
1759
1996
  const fileRows = await deps.graph.query(
1760
1997
  `MATCH (f:Symbol)
1761
1998
  WHERE f.kind = 'File' AND f.path = $path
1762
1999
  RETURN f.id AS id, f.path AS path, f.name AS name LIMIT 1`,
1763
- { path: path6 }
2000
+ { path: path8 }
1764
2001
  );
1765
2002
  if (fileRows.length === 0) {
1766
- return { path: path6, found: false };
2003
+ return { path: path8, found: false };
1767
2004
  }
1768
2005
  const [defined, imports, exports, importers] = await Promise.all([
1769
2006
  deps.graph.query(
@@ -1771,30 +2008,33 @@ var getFileContextTool = {
1771
2008
  WHERE f.kind = 'File' AND f.path = $path
1772
2009
  RETURN s.id AS id, s.name AS name, s.kind AS kind, s.lineStart AS line, s.signature AS signature
1773
2010
  ORDER BY s.lineStart LIMIT $limit`,
1774
- { path: path6, limit: symbolLimit }
2011
+ { path: path8, limit: symbolLimit }
1775
2012
  ),
1776
2013
  deps.graph.query(
2014
+ // Kuzu drops the node variable `t` from scope after `RETURN DISTINCT`, so the
2015
+ // ORDER BY references the projection alias instead - using `t.path` here
2016
+ // raises a "Variable t is not in scope" binder exception.
1777
2017
  `MATCH (f:Symbol)-[:IMPORTS]->(t:Symbol)
1778
2018
  WHERE f.kind = 'File' AND t.kind = 'File' AND f.path = $path
1779
- RETURN DISTINCT t.path AS path ORDER BY t.path`,
1780
- { path: path6 }
2019
+ RETURN DISTINCT t.path AS path ORDER BY path`,
2020
+ { path: path8 }
1781
2021
  ),
1782
2022
  deps.graph.query(
1783
2023
  `MATCH (f:Symbol)-[:EXPORTS]->(s:Symbol)
1784
2024
  WHERE f.kind = 'File' AND f.path = $path
1785
2025
  RETURN s.id AS id, s.name AS name, s.kind AS kind, s.lineStart AS line
1786
2026
  ORDER BY s.name`,
1787
- { path: path6 }
2027
+ { path: path8 }
1788
2028
  ),
1789
2029
  deps.graph.query(
1790
2030
  `MATCH (other:Symbol)-[:IMPORTS]->(f:Symbol)
1791
2031
  WHERE other.kind = 'File' AND f.kind = 'File' AND f.path = $path
1792
- RETURN DISTINCT other.path AS path ORDER BY other.path`,
1793
- { path: path6 }
2032
+ RETURN DISTINCT other.path AS path ORDER BY path`,
2033
+ { path: path8 }
1794
2034
  )
1795
2035
  ]);
1796
2036
  return {
1797
- path: path6,
2037
+ path: path8,
1798
2038
  found: true,
1799
2039
  file: fileRows[0],
1800
2040
  definedSymbols: defined,
@@ -2208,23 +2448,23 @@ var DEFAULT_TTL = 30;
2208
2448
  function defaultConfigPath() {
2209
2449
  return resolve(homedir(), ".codegraph", "config.json");
2210
2450
  }
2211
- async function readCodegraphConfig(path6 = defaultConfigPath()) {
2451
+ async function readCodegraphConfig(path8 = defaultConfigPath()) {
2212
2452
  try {
2213
- const raw = await readFile5(path6, "utf8");
2453
+ const raw = await readFile6(path8, "utf8");
2214
2454
  return JSON.parse(raw);
2215
2455
  } catch (err) {
2216
2456
  if (err.code === "ENOENT") return {};
2217
2457
  throw err;
2218
2458
  }
2219
2459
  }
2220
- async function writeCodegraphConfig(config, path6 = defaultConfigPath()) {
2221
- await mkdir3(dirname3(path6), { recursive: true });
2222
- await writeFile2(path6, `${JSON.stringify(config, null, 2)}
2460
+ async function writeCodegraphConfig(config, path8 = defaultConfigPath()) {
2461
+ await mkdir3(dirname3(path8), { recursive: true });
2462
+ await writeFile2(path8, `${JSON.stringify(config, null, 2)}
2223
2463
  `, "utf8");
2224
2464
  }
2225
2465
  async function resolveServerConfig(opts) {
2226
- const path6 = opts.configPath ?? defaultConfigPath();
2227
- const fileConfig = await readCodegraphConfig(path6);
2466
+ const path8 = opts.configPath ?? defaultConfigPath();
2467
+ const fileConfig = await readCodegraphConfig(path8);
2228
2468
  const env = process.env;
2229
2469
  let bearerToken = opts.overrides?.bearerToken ?? fileConfig.mcpToken ?? fileConfig.server?.bearerToken ?? env.CODEGRAPH_BEARER_TOKEN ?? "";
2230
2470
  let created = false;
@@ -2237,7 +2477,7 @@ async function resolveServerConfig(opts) {
2237
2477
  mcpToken: bearerToken,
2238
2478
  server: { ...fileConfig.server ?? {}, bearerToken }
2239
2479
  },
2240
- path6
2480
+ path8
2241
2481
  );
2242
2482
  }
2243
2483
  const config = {
@@ -2246,7 +2486,7 @@ async function resolveServerConfig(opts) {
2246
2486
  bearerToken,
2247
2487
  cacheTtlSeconds: opts.overrides?.cacheTtlSeconds ?? fileConfig.server?.cacheTtlSeconds ?? (env.CODEGRAPH_CACHE_TTL ? Number(env.CODEGRAPH_CACHE_TTL) : DEFAULT_TTL)
2248
2488
  };
2249
- return { config, configPath: path6, created };
2489
+ return { config, configPath: path8, created };
2250
2490
  }
2251
2491
  function resolveDbPath(fileConfig) {
2252
2492
  return fileConfig.data?.dbPath ?? process.env.CODEGRAPH_DB_PATH ?? void 0;
@@ -2294,7 +2534,7 @@ async function startMcpServer(portOrOptions) {
2294
2534
  async function loadGraphClient(dbPath) {
2295
2535
  let mod;
2296
2536
  try {
2297
- mod = await import("./src-PDNTANJD.js");
2537
+ mod = await import("./src-M7HSEMBT.js");
2298
2538
  } catch (err) {
2299
2539
  throw new Error(
2300
2540
  `Failed to import @codegraph/graph-db. Run \`pnpm -r build\` first. Underlying error: ${err instanceof Error ? err.message : String(err)}`
@@ -2352,8 +2592,473 @@ function adaptLlmRouter(router) {
2352
2592
  };
2353
2593
  }
2354
2594
 
2595
+ // src/commands/init.ts
2596
+ import boxen2 from "boxen";
2597
+ import kleur5 from "kleur";
2598
+ var TOTAL_STEPS = 6;
2599
+ async function runInitCommand() {
2600
+ printWelcome();
2601
+ const start = await confirm({ message: "Ready to get started?", default: true });
2602
+ if (!start) {
2603
+ console.log(kleur5.dim("Aborted. Run `codegraph init` again any time."));
2604
+ return;
2605
+ }
2606
+ runEnvCheck();
2607
+ await runLlmSetup();
2608
+ await runLiveTest();
2609
+ await runIndexStep();
2610
+ const bearerToken = await runServeStep();
2611
+ await runClientStep(bearerToken);
2612
+ await runCelebration();
2613
+ }
2614
+ function printWelcome() {
2615
+ printBanner();
2616
+ const description = [
2617
+ kleur5.bold("CodeGraph") + " parses your JS/TS repo with tree-sitter, stores nodes (Function, Class,",
2618
+ "Route\u2026) and edges (CALLS, IMPORTS, RENDERS\u2026) in an embedded Kuzu graph with",
2619
+ "vector embeddings, then runs a local MCP server your AI assistant can call.",
2620
+ "",
2621
+ kleur5.dim("This wizard will:"),
2622
+ ` ${kleur5.cyan("1.")} Configure an LLM provider (cloud or local)`,
2623
+ ` ${kleur5.cyan("2.")} Verify your credentials with a live round-trip`,
2624
+ ` ${kleur5.cyan("3.")} Index this repo into the graph`,
2625
+ ` ${kleur5.cyan("4.")} Show you how to boot the MCP server`,
2626
+ ` ${kleur5.cyan("5.")} Print a copy-paste config for your AI client`
2627
+ ].join("\n");
2628
+ process.stdout.write(
2629
+ `${boxen2(description, {
2630
+ padding: 1,
2631
+ margin: { top: 0, bottom: 1, left: 0, right: 0 },
2632
+ borderStyle: "round",
2633
+ borderColor: "cyan",
2634
+ title: kleur5.bold("Welcome"),
2635
+ titleAlignment: "left"
2636
+ })}
2637
+ `
2638
+ );
2639
+ }
2640
+ function stepHeader(n, label) {
2641
+ console.log();
2642
+ console.log(
2643
+ kleur5.cyan(`\u2500\u2500\u2500\u2500 Step ${n}/${TOTAL_STEPS}: ${kleur5.bold(label)} ${"\u2500".repeat(Math.max(4, 40 - label.length))}`)
2644
+ );
2645
+ }
2646
+ function runEnvCheck() {
2647
+ stepHeader(1, "Environment check");
2648
+ const version = process.versions.node;
2649
+ const major = Number(version.split(".")[0] ?? 0);
2650
+ if (major < 20) {
2651
+ console.log(`${kleur5.red("\u2717")} node version v${version} ${kleur5.red("(codegraph requires >= 20)")}`);
2652
+ throw new Error(`Node ${version} is too old; install Node 20+ and re-run \`codegraph init\`.`);
2653
+ }
2654
+ console.log(`${kleur5.green("\u2713")} node version ${kleur5.dim(`v${version}`)}`);
2655
+ }
2656
+ async function runLlmSetup() {
2657
+ stepHeader(2, "LLM setup");
2658
+ const mode = await select2({
2659
+ message: "How would you like to power CodeGraph?",
2660
+ choices: [
2661
+ {
2662
+ name: "Cloud provider",
2663
+ value: "cloud",
2664
+ description: "OpenAI, Anthropic, or Google. Fastest setup, requires an API key."
2665
+ },
2666
+ {
2667
+ name: "Local (Ollama)",
2668
+ value: "local",
2669
+ description: "Fully private. Requires Ollama running on :11434."
2670
+ }
2671
+ ]
2672
+ });
2673
+ let preset;
2674
+ if (mode === "local") {
2675
+ preset = "local-ollama";
2676
+ } else {
2677
+ preset = await select2({
2678
+ message: "Which cloud provider?",
2679
+ choices: [
2680
+ {
2681
+ name: "OpenAI",
2682
+ value: "byo-openai",
2683
+ description: "gpt-4o-mini + text-embedding-3-small"
2684
+ },
2685
+ {
2686
+ name: "Anthropic",
2687
+ value: "byo-anthropic",
2688
+ description: "claude-3-5-haiku (gen) + OpenAI text-embedding-3-small (embed)"
2689
+ },
2690
+ {
2691
+ name: "Google",
2692
+ value: "byo-google",
2693
+ description: "gemini-1.5-flash + text-embedding-004"
2694
+ }
2695
+ ]
2696
+ });
2697
+ }
2698
+ const lookup = LLM_PRESETS[preset];
2699
+ if (!lookup) {
2700
+ throw new Error(`Unknown preset "${preset}".`);
2701
+ }
2702
+ const config = await loadConfig();
2703
+ config.llm = { ...config.llm, ...lookup };
2704
+ await saveConfig(config);
2705
+ console.log();
2706
+ console.log(`${kleur5.green("\u2713")} preset saved ${kleur5.dim(`"${preset}" \u2192 ${configPath()}`)}`);
2707
+ console.log(`${kleur5.dim(" namespace ")}${kleur5.dim(namespaceLabel(config.llm))}`);
2708
+ if (preset === "byo-anthropic") {
2709
+ console.log();
2710
+ console.log(
2711
+ kleur5.yellow(
2712
+ "Note: Anthropic has no embedding API, so codegraph uses OpenAI embeddings."
2713
+ )
2714
+ );
2715
+ console.log(
2716
+ kleur5.yellow("You'll need both ANTHROPIC_API_KEY and OPENAI_API_KEY set.")
2717
+ );
2718
+ await promptApiKey("ANTHROPIC_API_KEY");
2719
+ await promptApiKey("OPENAI_API_KEY");
2720
+ return;
2721
+ }
2722
+ const envVar = apiKeyEnvVarFor2(preset);
2723
+ if (envVar) await promptApiKey(envVar);
2724
+ }
2725
+ function apiKeyEnvVarFor2(preset) {
2726
+ if (preset === "byo-openai" || preset === "managed-stub") return "OPENAI_API_KEY";
2727
+ if (preset === "byo-anthropic") return "ANTHROPIC_API_KEY";
2728
+ if (preset === "byo-google") return "GOOGLE_GENERATIVE_AI_API_KEY";
2729
+ return null;
2730
+ }
2731
+ async function promptApiKey(envVar) {
2732
+ console.log();
2733
+ if (process.env[envVar]) {
2734
+ console.log(`${kleur5.green("\u2713")} ${envVar} ${kleur5.dim("already set in this shell")}`);
2735
+ return;
2736
+ }
2737
+ const provide = await confirm({
2738
+ message: `${envVar} is not set. Provide it now? (input will be hidden)`,
2739
+ default: true
2740
+ });
2741
+ if (!provide) {
2742
+ console.log(
2743
+ kleur5.yellow(`! Skipped. Export ${envVar} before \`codegraph index\` or \`codegraph serve\`.`)
2744
+ );
2745
+ return;
2746
+ }
2747
+ const value = await password2({ message: `${envVar}`, mask: "*" });
2748
+ if (!value) {
2749
+ console.log(kleur5.yellow(`! Empty value \u2014 skipping ${envVar}.`));
2750
+ return;
2751
+ }
2752
+ process.env[envVar] = value;
2753
+ console.log();
2754
+ console.log(kleur5.dim("Add this to your ~/.zshrc or ~/.bashrc so it persists:"));
2755
+ console.log(` ${kleur5.cyan(`export ${envVar}=${maskTail(value)}`)}`);
2756
+ }
2757
+ function maskTail(value) {
2758
+ if (value.length <= 8) return "*".repeat(value.length);
2759
+ return `${"*".repeat(value.length - 4)}${value.slice(-4)}`;
2760
+ }
2761
+ async function runLiveTest() {
2762
+ stepHeader(3, "Verify credentials");
2763
+ while (true) {
2764
+ const config = await loadConfig();
2765
+ console.log(
2766
+ kleur5.dim(
2767
+ ` testing ${config.llm.generation.provider}:${config.llm.generation.model} + ${config.llm.embeddings.provider}:${config.llm.embeddings.model}`
2768
+ )
2769
+ );
2770
+ const spinner = makeSpinner("Calling provider").start();
2771
+ try {
2772
+ const router = await createLlmRouter({ config: config.llm });
2773
+ const result = await router.selfTest();
2774
+ spinner.succeed("LLM provider reachable");
2775
+ console.log(
2776
+ kleur5.dim(
2777
+ ` embed=${result.embedLatencyMs}ms gen=${result.generateLatencyMs}ms dims=${result.embedDims}`
2778
+ )
2779
+ );
2780
+ return;
2781
+ } catch (err) {
2782
+ spinner.fail("LLM provider unreachable");
2783
+ console.log(kleur5.red(` ${err instanceof Error ? err.message : String(err)}`));
2784
+ const choice = await select2({
2785
+ message: "What now?",
2786
+ choices: [
2787
+ { name: "Retry (I'll fix it in another terminal)", value: "retry" },
2788
+ { name: "Skip \u2014 set up LLM later", value: "skip" }
2789
+ ]
2790
+ });
2791
+ if (choice === "skip") {
2792
+ console.log(
2793
+ kleur5.yellow(
2794
+ "! Continuing without a verified LLM. Embeddings and semantic search will fail until you fix this."
2795
+ )
2796
+ );
2797
+ return;
2798
+ }
2799
+ }
2800
+ }
2801
+ }
2802
+ async function runIndexStep() {
2803
+ stepHeader(4, "Index this repo");
2804
+ const cwd = process.cwd();
2805
+ const display = abbreviateHome(cwd);
2806
+ const yes = await confirm({
2807
+ message: `Index the current directory (${display}) now?`,
2808
+ default: true
2809
+ });
2810
+ if (!yes) {
2811
+ console.log(kleur5.dim("Skipped. Run this when ready:"));
2812
+ console.log(` ${kleur5.cyan(`codegraph index ${display}`)}`);
2813
+ return;
2814
+ }
2815
+ console.log();
2816
+ try {
2817
+ await runIndexCommand({ repoPath: cwd });
2818
+ } catch (err) {
2819
+ console.log();
2820
+ console.log(
2821
+ kleur5.yellow(
2822
+ `! Index failed: ${err instanceof Error ? err.message : String(err)}`
2823
+ )
2824
+ );
2825
+ console.log(
2826
+ kleur5.dim(" You can retry later with `codegraph index <path>`. Continuing wizard.")
2827
+ );
2828
+ }
2829
+ }
2830
+ async function runServeStep() {
2831
+ stepHeader(5, "Boot the MCP server");
2832
+ const { config: serverConfig, created } = await resolveServerConfig({});
2833
+ if (created) {
2834
+ console.log(kleur5.dim(" generated a new bearer token for this machine"));
2835
+ }
2836
+ const url = `http://${serverConfig.host}:${serverConfig.port}/mcp`;
2837
+ const body = [
2838
+ "Open a " + kleur5.bold("new terminal") + " and run:",
2839
+ "",
2840
+ ` ${kleur5.cyan("codegraph serve")}`,
2841
+ "",
2842
+ kleur5.dim("Expected output:"),
2843
+ kleur5.dim(` codegraph mcp listening on ${url}`),
2844
+ "",
2845
+ kleur5.dim("Keep that terminal running while you use CodeGraph.")
2846
+ ].join("\n");
2847
+ process.stdout.write(
2848
+ `${boxen2(body, {
2849
+ padding: { top: 0, bottom: 0, left: 2, right: 2 },
2850
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
2851
+ borderStyle: "round",
2852
+ borderColor: "cyan",
2853
+ title: kleur5.bold("Start the server"),
2854
+ titleAlignment: "left"
2855
+ })}
2856
+ `
2857
+ );
2858
+ await confirm({
2859
+ message: "Done \u2014 the server is running in another terminal?",
2860
+ default: true
2861
+ });
2862
+ return serverConfig.bearerToken;
2863
+ }
2864
+ async function runClientStep(bearerToken) {
2865
+ stepHeader(6, "Connect your AI client");
2866
+ const url = "http://127.0.0.1:3748/mcp";
2867
+ const client = await select2({
2868
+ message: "Which AI client are you connecting?",
2869
+ choices: [
2870
+ { name: "Cursor", value: "cursor" },
2871
+ { name: "Claude Code", value: "claude" },
2872
+ { name: "Windsurf", value: "windsurf" },
2873
+ { name: "Skip for now", value: "skip" }
2874
+ ]
2875
+ });
2876
+ if (client === "skip") {
2877
+ console.log(
2878
+ kleur5.dim("Skipped. See docs/clients.md for per-client configuration when ready.")
2879
+ );
2880
+ return;
2881
+ }
2882
+ if (client === "cursor") {
2883
+ const snippet = JSON.stringify(
2884
+ {
2885
+ mcpServers: {
2886
+ codegraph: {
2887
+ url,
2888
+ headers: { Authorization: `Bearer ${bearerToken}` }
2889
+ }
2890
+ }
2891
+ },
2892
+ null,
2893
+ 2
2894
+ );
2895
+ const body = [
2896
+ "Create or edit " + kleur5.cyan(".cursor/mcp.json") + " in your project root:",
2897
+ "",
2898
+ kleur5.green(snippet),
2899
+ "",
2900
+ kleur5.dim("Then open Cursor \u2192 Settings \u2192 MCP."),
2901
+ kleur5.dim("`codegraph` should appear as connected with 10 tools.")
2902
+ ].join("\n");
2903
+ process.stdout.write(
2904
+ `${boxen2(body, {
2905
+ padding: 1,
2906
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
2907
+ borderStyle: "round",
2908
+ borderColor: "magenta",
2909
+ title: kleur5.bold("Cursor config"),
2910
+ titleAlignment: "left"
2911
+ })}
2912
+ `
2913
+ );
2914
+ } else if (client === "claude") {
2915
+ const oneliner = [
2916
+ "claude mcp add --transport sse codegraph \\",
2917
+ ` ${url} \\`,
2918
+ ` --header "Authorization: Bearer ${bearerToken}"`
2919
+ ].join("\n");
2920
+ const body = [
2921
+ "Run this in your terminal:",
2922
+ "",
2923
+ kleur5.green(oneliner),
2924
+ "",
2925
+ kleur5.dim("Then type ") + kleur5.cyan("/mcp") + kleur5.dim(" in Claude Code to confirm 10 tools are listed.")
2926
+ ].join("\n");
2927
+ process.stdout.write(
2928
+ `${boxen2(body, {
2929
+ padding: 1,
2930
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
2931
+ borderStyle: "round",
2932
+ borderColor: "magenta",
2933
+ title: kleur5.bold("Claude Code config"),
2934
+ titleAlignment: "left"
2935
+ })}
2936
+ `
2937
+ );
2938
+ } else {
2939
+ const snippet = JSON.stringify(
2940
+ {
2941
+ mcpServers: {
2942
+ codegraph: {
2943
+ serverUrl: url,
2944
+ headers: { Authorization: `Bearer ${bearerToken}` }
2945
+ }
2946
+ }
2947
+ },
2948
+ null,
2949
+ 2
2950
+ );
2951
+ const body = [
2952
+ "Edit " + kleur5.cyan("~/.codeium/windsurf/mcp_config.json") + ":",
2953
+ "",
2954
+ kleur5.green(snippet),
2955
+ "",
2956
+ kleur5.dim("Then restart Windsurf or run ") + kleur5.cyan("/refresh-tools") + kleur5.dim(" in Cascade.")
2957
+ ].join("\n");
2958
+ process.stdout.write(
2959
+ `${boxen2(body, {
2960
+ padding: 1,
2961
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
2962
+ borderStyle: "round",
2963
+ borderColor: "magenta",
2964
+ title: kleur5.bold("Windsurf config"),
2965
+ titleAlignment: "left"
2966
+ })}
2967
+ `
2968
+ );
2969
+ }
2970
+ await confirm({
2971
+ message: "Done \u2014 your client is configured?",
2972
+ default: true
2973
+ });
2974
+ }
2975
+ async function runCelebration() {
2976
+ console.log();
2977
+ console.log(kleur5.cyan(`\u2500\u2500\u2500\u2500 ${kleur5.bold("Almost there!")} \u2500\u2500\u2500\u2500`));
2978
+ console.log(
2979
+ kleur5.dim(
2980
+ 'Ask your AI assistant something like: "What files are in this repo?"'
2981
+ )
2982
+ );
2983
+ console.log(
2984
+ kleur5.dim("It should call a `codegraph_*` MCP tool to answer.")
2985
+ );
2986
+ console.log();
2987
+ const choice = await select2({
2988
+ message: "How did it go?",
2989
+ choices: [
2990
+ { name: "It worked!", value: "worked" },
2991
+ { name: "Skip", value: "skip" }
2992
+ ]
2993
+ });
2994
+ const header = choice === "worked" ? kleur5.green().bold("\u2713 CodeGraph is fully connected!") : kleur5.cyan().bold("\u2713 CodeGraph setup complete");
2995
+ const body = [
2996
+ header,
2997
+ "",
2998
+ "Your AI assistant can now call these tools:",
2999
+ ` ${kleur5.cyan("\u2022")} find_callers \u2014 what calls a function`,
3000
+ ` ${kleur5.cyan("\u2022")} get_component_tree \u2014 render dependency tree`,
3001
+ ` ${kleur5.cyan("\u2022")} blast_radius \u2014 impact of a change`,
3002
+ ` ${kleur5.cyan("\u2022")} search_semantic \u2014 symbol search by description`,
3003
+ ` ${kleur5.cyan("\u2022")} nl_query \u2014 natural-language Cypher`,
3004
+ ` ${kleur5.dim("\u2026plus 5 more (search_symbol, find_file, get_file_context,")}`,
3005
+ ` ${kleur5.dim(" get_dependencies, affected_by)")}`,
3006
+ "",
3007
+ kleur5.dim("Helpful next steps:"),
3008
+ ` ${kleur5.cyan("codegraph doctor")} ${kleur5.dim("\u2014 verify environment + LLM + Kuzu")}`,
3009
+ ` ${kleur5.cyan("codegraph status")} ${kleur5.dim("<path>")} ${kleur5.dim("\u2014 node/edge counts + embedding coverage")}`,
3010
+ "",
3011
+ kleur5.dim("Docs: https://github.com/leanlabsinnov/codegraph")
3012
+ ].join("\n");
3013
+ process.stdout.write(
3014
+ `${boxen2(body, {
3015
+ padding: 1,
3016
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
3017
+ borderStyle: "round",
3018
+ borderColor: choice === "worked" ? "green" : "cyan"
3019
+ })}
3020
+ `
3021
+ );
3022
+ }
3023
+ function abbreviateHome(p) {
3024
+ const home = process.env.HOME ?? process.env.USERPROFILE;
3025
+ if (!home) return p;
3026
+ const rel = path7.relative(home, p);
3027
+ if (rel.startsWith("..")) return p;
3028
+ return rel ? `~${path7.sep}${rel}` : "~";
3029
+ }
3030
+
2355
3031
  // src/commands/serve.ts
3032
+ import kleur6 from "kleur";
3033
+ function providerEnvVar2(provider) {
3034
+ if (provider === "openai") return "OPENAI_API_KEY";
3035
+ if (provider === "anthropic") return "ANTHROPIC_API_KEY";
3036
+ if (provider === "google") return "GOOGLE_GENERATIVE_AI_API_KEY";
3037
+ return null;
3038
+ }
2356
3039
  async function runServeCommand(opts = {}) {
3040
+ try {
3041
+ const config = await loadConfig();
3042
+ const missing = [];
3043
+ for (const provider of [
3044
+ config.llm.generation.provider,
3045
+ config.llm.embeddings.provider
3046
+ ]) {
3047
+ const envVar = providerEnvVar2(provider);
3048
+ if (envVar && !process.env[envVar] && !missing.includes(envVar)) {
3049
+ missing.push(envVar);
3050
+ }
3051
+ }
3052
+ if (missing.length > 0) {
3053
+ process.stderr.write(
3054
+ `${kleur6.yellow(
3055
+ `! ${missing.join(", ")} not set in serve's environment. Semantic search and nl_query will fail until the variable is exported before \`codegraph serve\`.`
3056
+ )}
3057
+ `
3058
+ );
3059
+ }
3060
+ } catch {
3061
+ }
2357
3062
  const spinner = makeSpinner("Booting MCP server").start();
2358
3063
  let started;
2359
3064
  try {
@@ -2394,13 +3099,16 @@ shutting down (${signal})...
2394
3099
  // src/program.ts
2395
3100
  function buildProgram() {
2396
3101
  const program = new Command();
2397
- program.name("codegraph").description("Live, queryable knowledge graph for your codebase").version("0.1.2").option("--verbose", "Print full stack traces on error").hook("preAction", (thisCommand) => {
3102
+ program.name("codegraph").description("Live, queryable knowledge graph for your codebase").version("0.1.3").option("--verbose", "Print full stack traces on error").hook("preAction", (thisCommand) => {
2398
3103
  const opts = thisCommand.optsWithGlobals();
2399
3104
  if (opts.verbose) process.env.CODEGRAPH_VERBOSE = "1";
2400
3105
  });
2401
3106
  program.on("--help", () => {
2402
3107
  printBanner();
2403
3108
  });
3109
+ program.command("init").description("Interactive setup wizard \u2014 LLM, credentials, index, MCP, client connect").action(async () => {
3110
+ await runInitCommand();
3111
+ });
2404
3112
  program.command("index").description("Parse a JS/TS repo into the local embedded graph").argument("<path>", "Path to the repo root").option("--no-embed", "Skip the embedding pass (faster, no LLM calls)").action(async (repoPath, opts) => {
2405
3113
  await runIndexCommand({ repoPath, noEmbed: opts.embed === false });
2406
3114
  });
@@ -2417,6 +3125,12 @@ function buildProgram() {
2417
3125
  program.command("doctor").description("Check environment, config, LLM credentials, and Kuzu DB health").action(async () => {
2418
3126
  await runDoctorCommand();
2419
3127
  });
3128
+ program.command("wipe").description("Delete a single repo's slice (with [path]) or the entire on-disk graph").argument("[path]", "Repo path. Omit to wipe the whole graph directory.").option("--yes", "Skip the confirmation prompt", false).action(async (repoPath, opts) => {
3129
+ await runWipeCommand({
3130
+ ...repoPath ? { repoPath } : {},
3131
+ yes: opts.yes === true
3132
+ });
3133
+ });
2420
3134
  const configCmd = program.command("config").description("Manage ~/.codegraph/config.json");
2421
3135
  configCmd.command("show").description("Print the resolved config").action(async () => {
2422
3136
  await runConfigShow();
@@ -2440,4 +3154,4 @@ export {
2440
3154
  renderError,
2441
3155
  buildProgram
2442
3156
  };
2443
- //# sourceMappingURL=chunk-36AWRLQ6.js.map
3157
+ //# sourceMappingURL=chunk-5WYXRWEY.js.map