@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.
- package/dist/bin.js +2 -2
- package/dist/{chunk-36AWRLQ6.js → chunk-5WYXRWEY.js} +795 -81
- package/dist/chunk-5WYXRWEY.js.map +1 -0
- package/dist/{chunk-2TORJYBO.js → chunk-AVP24SX5.js} +21 -3
- package/dist/chunk-AVP24SX5.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/{src-PDNTANJD.js → src-M7HSEMBT.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-2TORJYBO.js.map +0 -1
- package/dist/chunk-36AWRLQ6.js.map +0 -1
- /package/dist/{src-PDNTANJD.js.map → src-M7HSEMBT.js.map} +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
GraphDb,
|
|
7
7
|
defaultDbPath
|
|
8
|
-
} from "./chunk-
|
|
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,
|
|
377
|
+
async function checkWritable(name, path8) {
|
|
378
378
|
try {
|
|
379
|
-
await mkdir2(dirname(
|
|
379
|
+
await mkdir2(dirname(path8), { recursive: true });
|
|
380
380
|
try {
|
|
381
|
-
await access(
|
|
381
|
+
await access(path8, constants.W_OK);
|
|
382
382
|
} catch {
|
|
383
|
-
await access(dirname(
|
|
383
|
+
await access(dirname(path8), constants.W_OK);
|
|
384
384
|
}
|
|
385
|
-
return { name, status: "ok", detail:
|
|
385
|
+
return { name, status: "ok", detail: path8 };
|
|
386
386
|
} catch (err) {
|
|
387
387
|
return {
|
|
388
388
|
name,
|
|
389
389
|
status: "fail",
|
|
390
|
-
detail: `${
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
1173
|
-
|
|
1174
|
-
|
|
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
|
|
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/
|
|
1230
|
-
import { readFile as readFile3
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1428
|
+
const file = path4.join(absDir, ".gitignore");
|
|
1279
1429
|
try {
|
|
1280
|
-
const text = await
|
|
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
|
|
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
|
|
1532
|
+
import path5 from "path";
|
|
1381
1533
|
function repoIdFromPath(absPath) {
|
|
1382
|
-
const base =
|
|
1383
|
-
const sha = createHash3("sha1").update(
|
|
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 =
|
|
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
|
-
|
|
1430
|
-
|
|
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 (!
|
|
1443
|
-
|
|
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 (!
|
|
1452
|
-
|
|
1605
|
+
if (!bars.embed) bars.embed = new ProgressBar("embedding", event.total);
|
|
1606
|
+
bars.embed.update(event.embedded);
|
|
1453
1607
|
}
|
|
1454
1608
|
}
|
|
1455
1609
|
});
|
|
1456
|
-
|
|
1457
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
1591
|
-
const where =
|
|
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:
|
|
1806
|
+
{ symbol, path: path8 ?? null, limit }
|
|
1601
1807
|
);
|
|
1602
|
-
return { symbol, path:
|
|
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:
|
|
1620
|
-
const where =
|
|
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:
|
|
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:
|
|
1842
|
+
{ symbol, path: path8 ?? null, sampleSize }
|
|
1637
1843
|
);
|
|
1638
1844
|
return {
|
|
1639
1845
|
symbol,
|
|
1640
|
-
path:
|
|
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
|
|
1708
|
-
`MATCH (root:Symbol
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
1978
|
+
{ path: path8, limit }
|
|
1742
1979
|
);
|
|
1743
|
-
return { path:
|
|
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:
|
|
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:
|
|
2000
|
+
{ path: path8 }
|
|
1764
2001
|
);
|
|
1765
2002
|
if (fileRows.length === 0) {
|
|
1766
|
-
return { path:
|
|
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:
|
|
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
|
|
1780
|
-
{ path:
|
|
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:
|
|
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
|
|
1793
|
-
{ path:
|
|
2032
|
+
RETURN DISTINCT other.path AS path ORDER BY path`,
|
|
2033
|
+
{ path: path8 }
|
|
1794
2034
|
)
|
|
1795
2035
|
]);
|
|
1796
2036
|
return {
|
|
1797
|
-
path:
|
|
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(
|
|
2451
|
+
async function readCodegraphConfig(path8 = defaultConfigPath()) {
|
|
2212
2452
|
try {
|
|
2213
|
-
const raw = await
|
|
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,
|
|
2221
|
-
await mkdir3(dirname3(
|
|
2222
|
-
await writeFile2(
|
|
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
|
|
2227
|
-
const fileConfig = await readCodegraphConfig(
|
|
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
|
-
|
|
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:
|
|
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-
|
|
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.
|
|
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-
|
|
3157
|
+
//# sourceMappingURL=chunk-5WYXRWEY.js.map
|