@mgamil/mapx 0.2.5 → 0.2.6
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/VERSION +1 -1
- package/dist/cli.js +202 -153
- package/dist/core/config.js +53 -5
- package/dist/core/scanner.js +35 -19
- package/package.json +4 -4
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.6
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,6 @@ import { resolve, join, dirname, relative, basename } from "node:path";
|
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync, readdirSync, rmSync } from "node:fs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import * as readline from "node:readline";
|
|
7
6
|
import { Store } from "./core/store.js";
|
|
8
7
|
import { MapxGraph } from "./core/graph.js";
|
|
9
8
|
import { Scanner, buildMatcher } from "./core/scanner.js";
|
|
@@ -21,6 +20,7 @@ import { getChangedFiles, isGitRepo } from "./core/git-tracker.js";
|
|
|
21
20
|
import { getBuiltinLanguages } from "./languages/registry.js";
|
|
22
21
|
import { isLanguageInstalled, installLanguage, uninstallLanguage } from "./languages/installer.js";
|
|
23
22
|
import { RouteRegistry } from "./frameworks/route-registry.js";
|
|
23
|
+
import * as clack from "@clack/prompts";
|
|
24
24
|
const dynamicRequire = createRequire(import.meta.url);
|
|
25
25
|
function readVersion() {
|
|
26
26
|
const base = dirname(fileURLToPath(import.meta.url));
|
|
@@ -44,49 +44,81 @@ const PHASE_LABELS = {
|
|
|
44
44
|
detect: { active: "Detecting changes", done: "Detected changes" },
|
|
45
45
|
cluster: { active: "Detecting clusters", done: "Detected clusters" }
|
|
46
46
|
};
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
function truncatePath(path, maxLength) {
|
|
48
|
+
if (path.length <= maxLength) return path;
|
|
49
|
+
if (maxLength <= 3) return "...";
|
|
50
|
+
return "..." + path.slice(path.length - maxLength + 3);
|
|
51
|
+
}
|
|
49
52
|
function createProgressRenderer() {
|
|
50
53
|
let lastPhase = null;
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
const renderBar = (current, total, width = 20) => {
|
|
58
|
-
if (total === 0) {
|
|
59
|
-
const frame = SPINNER_FRAMES[spinnerIdx++ % SPINNER_FRAMES.length];
|
|
60
|
-
return `${frame} `;
|
|
61
|
-
}
|
|
62
|
-
const filled = Math.min(width, Math.max(0, Math.round(current / total * width)));
|
|
63
|
-
const empty = width - filled;
|
|
64
|
-
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
65
|
-
const pct = Math.round(current / total * 100);
|
|
66
|
-
return `${bar} ${pct}%`;
|
|
67
|
-
};
|
|
68
|
-
return (progress) => {
|
|
69
|
-
const { phase, current, total, file } = progress;
|
|
54
|
+
let p = null;
|
|
55
|
+
let s = null;
|
|
56
|
+
let lastCurrent = 0;
|
|
57
|
+
const callback = (progressData) => {
|
|
58
|
+
const { phase, current, total, file } = progressData;
|
|
70
59
|
const label = PHASE_LABELS[phase];
|
|
60
|
+
if (!label) return;
|
|
71
61
|
const isNewPhase = phase !== lastPhase;
|
|
72
|
-
if (isNewPhase
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`);
|
|
62
|
+
if (isNewPhase) {
|
|
63
|
+
if (s) {
|
|
64
|
+
const prevLabel = lastPhase ? PHASE_LABELS[lastPhase] : null;
|
|
65
|
+
s.stop(prevLabel ? `\u2714 ${prevLabel.done}` : "\u2714 Done");
|
|
66
|
+
s = null;
|
|
67
|
+
}
|
|
68
|
+
if (p) {
|
|
69
|
+
const prevLabel = lastPhase ? PHASE_LABELS[lastPhase] : null;
|
|
70
|
+
p.stop(prevLabel ? `\u2714 ${prevLabel.done}` : "\u2714 Done");
|
|
71
|
+
p = null;
|
|
72
|
+
}
|
|
73
|
+
lastPhase = phase;
|
|
74
|
+
lastCurrent = 0;
|
|
75
|
+
if (total > 0) {
|
|
76
|
+
p = clack.progress({
|
|
77
|
+
style: "heavy",
|
|
78
|
+
max: total,
|
|
79
|
+
size: 40
|
|
80
|
+
});
|
|
81
|
+
p.start(label.active);
|
|
82
|
+
} else {
|
|
83
|
+
s = clack.spinner();
|
|
84
|
+
s.start(label.active);
|
|
85
|
+
}
|
|
76
86
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const counter = total > 0 ? `${current}/${total}` : `${current}`;
|
|
80
|
-
let line = ` ${label.active} ${bar} ${counter}`;
|
|
87
|
+
const cols = process.stdout.columns || 80;
|
|
88
|
+
let fileLabel = "";
|
|
81
89
|
if (file) {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
const prefixText = p ? `${label.active} (${current}/${total}) - ` : `${label.active} (${current}) - `;
|
|
91
|
+
const clackDecorationLength = p ? 45 : 5;
|
|
92
|
+
const reserved = prefixText.length + clackDecorationLength;
|
|
93
|
+
const maxFileLen = Math.max(10, cols - reserved - 3);
|
|
94
|
+
fileLabel = ` - ${truncatePath(file, maxFileLen)}`;
|
|
95
|
+
}
|
|
96
|
+
if (p) {
|
|
97
|
+
const diff = current - lastCurrent;
|
|
98
|
+
const msg = `${label.active} (${current}/${total})${fileLabel}`;
|
|
99
|
+
if (diff > 0) {
|
|
100
|
+
p.advance(diff, msg);
|
|
101
|
+
lastCurrent = current;
|
|
102
|
+
} else {
|
|
103
|
+
p.message(msg);
|
|
86
104
|
}
|
|
105
|
+
} else if (s) {
|
|
106
|
+
s.message(`${label.active} (${current})${fileLabel}`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
callback.stop = (title) => {
|
|
110
|
+
if (s) {
|
|
111
|
+
const prevLabel = lastPhase ? PHASE_LABELS[lastPhase] : null;
|
|
112
|
+
s.stop(title || (prevLabel ? `\u2714 ${prevLabel.done}` : "\u2714 Done"));
|
|
113
|
+
s = null;
|
|
114
|
+
}
|
|
115
|
+
if (p) {
|
|
116
|
+
const prevLabel = lastPhase ? PHASE_LABELS[lastPhase] : null;
|
|
117
|
+
p.stop(title || (prevLabel ? `\u2714 ${prevLabel.done}` : "\u2714 Done"));
|
|
118
|
+
p = null;
|
|
87
119
|
}
|
|
88
|
-
writeLine(line);
|
|
89
120
|
};
|
|
121
|
+
return callback;
|
|
90
122
|
}
|
|
91
123
|
const MAPX_MARKER_START = "<!-- mapx -->";
|
|
92
124
|
const MAPX_MARKER_END = "<!-- /mapx -->";
|
|
@@ -177,47 +209,20 @@ function replaceBetweenMarkers(existing, block) {
|
|
|
177
209
|
}
|
|
178
210
|
return existing.slice(0, startIdx) + block + existing.slice(endIdx + MAPX_MARKER_END.length);
|
|
179
211
|
}
|
|
180
|
-
function prompt(question, options) {
|
|
181
|
-
return new Promise((res) => {
|
|
182
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
183
|
-
const labels = options.map((o, i) => ` ${i + 1}) ${o}`);
|
|
184
|
-
process.stderr.write(question + "\n" + labels.join("\n") + "\n> ");
|
|
185
|
-
rl.question("", (answer) => {
|
|
186
|
-
rl.close();
|
|
187
|
-
const num = parseInt(answer.trim(), 10);
|
|
188
|
-
res(num >= 1 && num <= options.length ? num - 1 : options.length - 1);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
function askQuestion(query) {
|
|
193
|
-
const rl = readline.createInterface({
|
|
194
|
-
input: process.stdin,
|
|
195
|
-
output: process.stdout
|
|
196
|
-
});
|
|
197
|
-
return new Promise((resolve2) => rl.question(query, (ans) => {
|
|
198
|
-
rl.close();
|
|
199
|
-
resolve2(ans);
|
|
200
|
-
}));
|
|
201
|
-
}
|
|
202
212
|
async function selectProvidersInteractive() {
|
|
203
213
|
const generator = new AgentGenerator();
|
|
204
214
|
const providers = generator.listProviders();
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
const selected = await clack.multiselect({
|
|
216
|
+
message: "Which LLM/agent tools do you use in this project?",
|
|
217
|
+
options: providers.map((p) => ({ value: p, label: p })),
|
|
218
|
+
required: false
|
|
209
219
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return ["generic"];
|
|
214
|
-
}
|
|
215
|
-
if (input === "all") {
|
|
216
|
-
return providers;
|
|
220
|
+
if (clack.isCancel(selected)) {
|
|
221
|
+
clack.cancel("Operation cancelled.");
|
|
222
|
+
process.exit(0);
|
|
217
223
|
}
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
return selected.length === 0 ? ["generic"] : selected;
|
|
224
|
+
const result = selected;
|
|
225
|
+
return result.length === 0 ? ["generic"] : result;
|
|
221
226
|
}
|
|
222
227
|
function buildCLI() {
|
|
223
228
|
const program = new Command();
|
|
@@ -260,8 +265,15 @@ function buildCLI() {
|
|
|
260
265
|
console.log(" \u2713 public/** (web assets)");
|
|
261
266
|
console.log(" \u2713 resources/views/** (Blade templates \u2014 not yet supported)");
|
|
262
267
|
console.log(" \u2713 **/*.blade.php (Blade files)");
|
|
263
|
-
const answer = await
|
|
264
|
-
|
|
268
|
+
const answer = await clack.confirm({
|
|
269
|
+
message: "Add these to .mapx/config.json?",
|
|
270
|
+
initialValue: true
|
|
271
|
+
});
|
|
272
|
+
if (clack.isCancel(answer)) {
|
|
273
|
+
clack.cancel("Operation cancelled.");
|
|
274
|
+
process.exit(0);
|
|
275
|
+
}
|
|
276
|
+
return answer;
|
|
265
277
|
}
|
|
266
278
|
program.command("init").description("Initialize mapx for a project").argument("[path]", "Target directory").option("--name <name>", "Repository name").option("--no-agents", "Skip AGENTS.md creation").option("--no-suggestions", "Skip interactive framework suggestions").option("--no-mcp-configs", "Skip auto-generating MCP config files for detected agent tools").action(async (path, opts) => {
|
|
267
279
|
const dir = path ? resolve(path) : resolveDir(opts, program.opts());
|
|
@@ -274,19 +286,19 @@ function buildCLI() {
|
|
|
274
286
|
if (opts.agents !== false) {
|
|
275
287
|
if (process.stdin.isTTY && opts.suggestions !== false) {
|
|
276
288
|
const selected = await selectProvidersInteractive();
|
|
277
|
-
|
|
289
|
+
clack.log.step(`Generating integration files for: ${selected.join(", ")}...`);
|
|
278
290
|
const generator = new AgentGenerator();
|
|
279
291
|
const actions = generator.plan(selected, { dir });
|
|
280
292
|
for (const action of actions) {
|
|
281
293
|
generator.execute(action);
|
|
282
|
-
|
|
294
|
+
clack.log.success(`Generated ${action.filename} (${action.status})`);
|
|
283
295
|
}
|
|
284
296
|
} else {
|
|
285
297
|
const generator = new AgentGenerator();
|
|
286
298
|
const actions = generator.plan(["generic"], { dir });
|
|
287
299
|
for (const action of actions) {
|
|
288
300
|
generator.execute(action);
|
|
289
|
-
|
|
301
|
+
clack.log.success(`Generated ${action.filename} (${action.status})`);
|
|
290
302
|
}
|
|
291
303
|
}
|
|
292
304
|
}
|
|
@@ -299,7 +311,7 @@ function buildCLI() {
|
|
|
299
311
|
if (action.status === "up_to_date") continue;
|
|
300
312
|
generator.executeMcpConfig(action);
|
|
301
313
|
const verb = action.status === "merge" ? "merged into" : action.status === "create" ? "created" : "updated";
|
|
302
|
-
|
|
314
|
+
clack.log.success(`MCP config ${verb} ${action.filename} (${action.tool})`);
|
|
303
315
|
}
|
|
304
316
|
}
|
|
305
317
|
}
|
|
@@ -312,19 +324,22 @@ function buildCLI() {
|
|
|
312
324
|
if (!lines.includes(".mapx/") && !lines.includes(".mapx")) {
|
|
313
325
|
const entry = content.length > 0 && !content.endsWith("\n") ? "\n.mapx/\n" : ".mapx/\n";
|
|
314
326
|
writeFileSync(gitignorePath, content + entry);
|
|
315
|
-
|
|
327
|
+
clack.log.success(`Added .mapx/ to .gitignore`);
|
|
316
328
|
}
|
|
317
329
|
}
|
|
318
|
-
|
|
319
|
-
|
|
330
|
+
clack.log.success(`Initialized mapx in ${dir}/.mapx/`);
|
|
331
|
+
clack.log.info(`Repo: ${config.repo.name}`);
|
|
320
332
|
});
|
|
321
333
|
program.command("uninit").description("Remove .mapx/ directory and reverse project integration changes").argument("[path]", "Target directory").option("-f, --force", "Skip confirmation prompt").action(async (path, opts) => {
|
|
322
334
|
const dir = path ? resolve(path) : resolveDir(opts, program.opts());
|
|
323
335
|
const hasMapx = existsSync(join(dir, ".mapx"));
|
|
324
336
|
if (!opts.force && process.stdin.isTTY) {
|
|
325
|
-
const answer = await
|
|
326
|
-
|
|
327
|
-
|
|
337
|
+
const answer = await clack.confirm({
|
|
338
|
+
message: `Are you sure you want to remove .mapx/ and reverse all mapx integrations in ${dir}?`,
|
|
339
|
+
initialValue: false
|
|
340
|
+
});
|
|
341
|
+
if (clack.isCancel(answer) || !answer) {
|
|
342
|
+
clack.cancel("Aborted.");
|
|
328
343
|
return;
|
|
329
344
|
}
|
|
330
345
|
}
|
|
@@ -347,21 +362,21 @@ function buildCLI() {
|
|
|
347
362
|
});
|
|
348
363
|
if (removed) {
|
|
349
364
|
writeFileSync(gitignorePath, filteredLines.join("\n"), "utf-8");
|
|
350
|
-
|
|
365
|
+
clack.log.success(`Removed .mapx/ from .gitignore`);
|
|
351
366
|
}
|
|
352
367
|
} catch (err) {
|
|
353
|
-
|
|
368
|
+
clack.log.error(`Failed to update .gitignore: ${err.message}`);
|
|
354
369
|
}
|
|
355
370
|
}
|
|
356
371
|
if (hasMapx) {
|
|
357
372
|
try {
|
|
358
373
|
rmSync(join(dir, ".mapx"), { recursive: true, force: true });
|
|
359
|
-
|
|
374
|
+
clack.log.success(`Removed .mapx/ directory`);
|
|
360
375
|
} catch (err) {
|
|
361
|
-
|
|
376
|
+
clack.log.error(`Failed to remove .mapx/ directory: ${err.message}`);
|
|
362
377
|
}
|
|
363
378
|
}
|
|
364
|
-
|
|
379
|
+
clack.log.success(`Successfully uninitialized mapx for project: ${dir}`);
|
|
365
380
|
});
|
|
366
381
|
program.command("scan").description("Full scan: parse all files, build graph").argument("[path]", "Target directory").option("--exclude <glob>", "Exclude glob pattern(s)", collectPatterns, []).option("--include <glob>", "Include glob pattern(s)", collectPatterns, []).option("--repo <name>", "Scan only a specific registered repository").option("--all", "Scan all registered repositories").option("--force", "Force re-parsing of all files (bypass cache)", false).action(async (path, opts) => {
|
|
367
382
|
const dir = path ? resolve(path) : resolveDir({}, program.opts());
|
|
@@ -373,6 +388,7 @@ function buildCLI() {
|
|
|
373
388
|
});
|
|
374
389
|
const onSigInt = () => {
|
|
375
390
|
scanner.abort();
|
|
391
|
+
onProgress.stop("Canceled");
|
|
376
392
|
process.stderr.write("\n");
|
|
377
393
|
};
|
|
378
394
|
process.once("SIGINT", onSigInt);
|
|
@@ -383,21 +399,22 @@ function buildCLI() {
|
|
|
383
399
|
repoNames = ["all"];
|
|
384
400
|
}
|
|
385
401
|
const result = await scanner.scanFull(repoNames, { force: !!opts.force }).catch((err) => {
|
|
402
|
+
onProgress.stop();
|
|
386
403
|
if (err.message.includes("Another scan is already running")) {
|
|
387
|
-
|
|
404
|
+
clack.log.error(`Error: ${err.message}`);
|
|
388
405
|
process.exit(1);
|
|
389
406
|
}
|
|
390
407
|
throw err;
|
|
391
408
|
});
|
|
392
409
|
process.removeListener("SIGINT", onSigInt);
|
|
393
|
-
|
|
410
|
+
onProgress.stop();
|
|
394
411
|
if (result.interrupted) {
|
|
395
|
-
|
|
412
|
+
clack.log.warn(`Scan interrupted after ${result.filesScanned}/${result.totalFiles} files. Progress saved \u2014 run \`scan\` again to resume.`);
|
|
396
413
|
} else {
|
|
397
|
-
|
|
414
|
+
clack.log.success(`Scanned ${result.filesScanned} files in ${result.durationMs}ms`);
|
|
398
415
|
}
|
|
399
|
-
|
|
400
|
-
|
|
416
|
+
clack.log.info(`Languages: ${Object.entries(result.languageBreakdown).map(([l, c]) => `${l}: ${c}`).join(", ")}`);
|
|
417
|
+
clack.log.info(`Found ${result.symbolsFound} symbols, ${result.edgesFound} edges`);
|
|
401
418
|
});
|
|
402
419
|
program.command("update").alias("sync").description("Incremental scan: re-scan only changed files").argument("[path]", "Target directory").option("--exclude <glob>", "Exclude glob pattern(s)", collectPatterns, []).option("--include <glob>", "Include glob pattern(s)", collectPatterns, []).option("--repo <name>", "Update only a specific registered repository").option("--all", "Update all registered repositories").action(async (path, opts) => {
|
|
403
420
|
const dir = path ? resolve(path) : resolveDir({}, program.opts());
|
|
@@ -420,14 +437,23 @@ function buildCLI() {
|
|
|
420
437
|
excludes: opts.exclude,
|
|
421
438
|
includes: opts.include
|
|
422
439
|
});
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
440
|
+
const onSigInt = () => {
|
|
441
|
+
scanner.abort();
|
|
442
|
+
onProgress.stop("Canceled");
|
|
443
|
+
};
|
|
444
|
+
process.once("SIGINT", onSigInt);
|
|
445
|
+
const result = await scanner.scanIncremental(repoNames).catch((err) => {
|
|
446
|
+
onProgress.stop();
|
|
447
|
+
handleLockError(err);
|
|
448
|
+
throw err;
|
|
449
|
+
});
|
|
450
|
+
process.removeListener("SIGINT", onSigInt);
|
|
451
|
+
onProgress.stop();
|
|
426
452
|
if (result.interrupted) {
|
|
427
|
-
|
|
453
|
+
clack.log.warn(`Update interrupted after ${result.filesScanned} files.`);
|
|
428
454
|
} else {
|
|
429
|
-
|
|
430
|
-
|
|
455
|
+
clack.log.success(`Updated ${result.filesScanned} files in ${result.durationMs}ms`);
|
|
456
|
+
clack.log.info(`${result.symbolsFound} symbols updated, ${result.edgesFound} edges updated`);
|
|
431
457
|
}
|
|
432
458
|
});
|
|
433
459
|
program.command("status").description("Show scan status, collected metrics, and changed files").argument("[path]", "Target directory").option("--exclude <glob>", "Exclude glob pattern(s)", collectPatterns, []).option("--include <glob>", "Include glob pattern(s)", collectPatterns, []).action(async (path, opts) => {
|
|
@@ -1165,21 +1191,21 @@ Trace: ${start} ${dirSymbol} depth\u2264${parsedDepth}`);
|
|
|
1165
1191
|
});
|
|
1166
1192
|
langCmd.command("install <lang>").description("Install grammar and query files for an installable language").action(async (lang) => {
|
|
1167
1193
|
try {
|
|
1168
|
-
|
|
1194
|
+
clack.log.step(`Installing language '${lang}'...`);
|
|
1169
1195
|
await installLanguage(lang);
|
|
1170
|
-
|
|
1196
|
+
clack.log.success(`Successfully installed language '${lang}'.`);
|
|
1171
1197
|
} catch (err) {
|
|
1172
|
-
|
|
1198
|
+
clack.log.error(`Error installing language '${lang}': ${err.message}`);
|
|
1173
1199
|
process.exit(1);
|
|
1174
1200
|
}
|
|
1175
1201
|
});
|
|
1176
1202
|
langCmd.command("uninstall <lang>").description("Uninstall grammar and query files for an installable language").action(async (lang) => {
|
|
1177
1203
|
try {
|
|
1178
|
-
|
|
1204
|
+
clack.log.step(`Uninstalling language '${lang}'...`);
|
|
1179
1205
|
await uninstallLanguage(lang);
|
|
1180
|
-
|
|
1206
|
+
clack.log.success(`Successfully uninstalled language '${lang}'.`);
|
|
1181
1207
|
} catch (err) {
|
|
1182
|
-
|
|
1208
|
+
clack.log.error(`Error uninstalling language '${lang}': ${err.message}`);
|
|
1183
1209
|
process.exit(1);
|
|
1184
1210
|
}
|
|
1185
1211
|
});
|
|
@@ -1450,34 +1476,36 @@ Detected Hooks (${hooks.length}):`);
|
|
|
1450
1476
|
}
|
|
1451
1477
|
}
|
|
1452
1478
|
if (targets.length === 0) {
|
|
1453
|
-
|
|
1479
|
+
clack.log.error("No valid providers specified.");
|
|
1454
1480
|
process.exit(1);
|
|
1455
1481
|
}
|
|
1456
1482
|
const actions = generator.plan(targets, { dir, mcpPort: parseInt(opts.mcpPort, 10) });
|
|
1457
1483
|
for (const action of actions) {
|
|
1458
1484
|
if (action.status === "up_to_date") {
|
|
1459
|
-
|
|
1485
|
+
clack.log.info(`${action.filename}: Up to date. Skipping.`);
|
|
1460
1486
|
continue;
|
|
1461
1487
|
}
|
|
1462
1488
|
if (action.status === "update_conflict" || action.status === "no_sentinel") {
|
|
1463
|
-
|
|
1464
|
-
\u26A0\uFE0F Conflict/Modification detected in ${action.filename}:`);
|
|
1489
|
+
clack.log.warn(`Conflict/Modification detected in ${action.filename}:`);
|
|
1465
1490
|
if (action.diff) {
|
|
1466
1491
|
console.log(action.diff);
|
|
1467
1492
|
}
|
|
1468
1493
|
if (!opts.force) {
|
|
1469
|
-
const confirm = await
|
|
1470
|
-
|
|
1471
|
-
|
|
1494
|
+
const confirm = await clack.confirm({
|
|
1495
|
+
message: `Overwrite ${action.filename}?`,
|
|
1496
|
+
initialValue: false
|
|
1497
|
+
});
|
|
1498
|
+
if (clack.isCancel(confirm) || !confirm) {
|
|
1499
|
+
clack.log.warn(`Skipped ${action.filename}.`);
|
|
1472
1500
|
continue;
|
|
1473
1501
|
}
|
|
1474
1502
|
}
|
|
1475
1503
|
}
|
|
1476
1504
|
if (opts.dryRun) {
|
|
1477
|
-
|
|
1505
|
+
clack.log.info(`[DRY RUN] Would write to ${action.filepath} (status: ${action.status})`);
|
|
1478
1506
|
} else {
|
|
1479
1507
|
generator.execute(action);
|
|
1480
|
-
|
|
1508
|
+
clack.log.success(`Wrote to ${action.filename} (status: ${action.status})`);
|
|
1481
1509
|
}
|
|
1482
1510
|
}
|
|
1483
1511
|
});
|
|
@@ -1490,7 +1518,7 @@ Detected Hooks (${hooks.length}):`);
|
|
|
1490
1518
|
return temp && existsSync(join(dir, temp.filename));
|
|
1491
1519
|
});
|
|
1492
1520
|
if (existingProviders.length === 0) {
|
|
1493
|
-
|
|
1521
|
+
clack.log.info("No existing LLM integration files found to update.");
|
|
1494
1522
|
return;
|
|
1495
1523
|
}
|
|
1496
1524
|
const actions = generator.plan(existingProviders, { dir, mcpPort: parseInt(opts.mcpPort, 10) });
|
|
@@ -1500,29 +1528,31 @@ Detected Hooks (${hooks.length}):`);
|
|
|
1500
1528
|
continue;
|
|
1501
1529
|
}
|
|
1502
1530
|
if (action.status === "update_conflict") {
|
|
1503
|
-
|
|
1504
|
-
\u26A0\uFE0F Customized content detected in ${action.filename}:`);
|
|
1531
|
+
clack.log.warn(`Customized content detected in ${action.filename}:`);
|
|
1505
1532
|
if (action.diff) {
|
|
1506
1533
|
console.log(action.diff);
|
|
1507
1534
|
}
|
|
1508
1535
|
if (!opts.force) {
|
|
1509
|
-
const confirm = await
|
|
1510
|
-
|
|
1511
|
-
|
|
1536
|
+
const confirm = await clack.confirm({
|
|
1537
|
+
message: `Overwrite customizations in ${action.filename}?`,
|
|
1538
|
+
initialValue: false
|
|
1539
|
+
});
|
|
1540
|
+
if (clack.isCancel(confirm) || !confirm) {
|
|
1541
|
+
clack.log.warn(`Skipped ${action.filename}.`);
|
|
1512
1542
|
continue;
|
|
1513
1543
|
}
|
|
1514
1544
|
}
|
|
1515
1545
|
}
|
|
1516
1546
|
if (opts.dryRun) {
|
|
1517
|
-
|
|
1547
|
+
clack.log.info(`[DRY RUN] Would update ${action.filepath}`);
|
|
1518
1548
|
} else {
|
|
1519
1549
|
generator.execute(action);
|
|
1520
|
-
|
|
1550
|
+
clack.log.success(`Updated ${action.filename}`);
|
|
1521
1551
|
updatedCount++;
|
|
1522
1552
|
}
|
|
1523
1553
|
}
|
|
1524
1554
|
if (updatedCount === 0 && !opts.dryRun) {
|
|
1525
|
-
|
|
1555
|
+
clack.log.success("All integration files are already up to date.");
|
|
1526
1556
|
}
|
|
1527
1557
|
});
|
|
1528
1558
|
agentsCmd.command("mcp").description("Auto-detect agent tools and generate/update MCP config files").option("--tools <list>", "Comma-separated list of tools to generate configs for (opencode, gemini-cli, cursor-mcp, vscode-mcp)").option("--all", "Generate MCP configs for all supported tools").option("--detect", "Only detect agent tools without writing files").option("--dry-run", "Show actions without writing files").action(async (opts) => {
|
|
@@ -1540,39 +1570,37 @@ Detected Hooks (${hooks.length}):`);
|
|
|
1540
1570
|
}
|
|
1541
1571
|
if (opts.detect) {
|
|
1542
1572
|
if (targets.length === 0) {
|
|
1543
|
-
|
|
1573
|
+
clack.log.info("No agent tools detected in this project.");
|
|
1544
1574
|
} else {
|
|
1545
|
-
|
|
1546
|
-
Detected agent tools (${targets.length}):`);
|
|
1575
|
+
clack.log.info(`Detected agent tools (${targets.length}):`);
|
|
1547
1576
|
for (const t of targets) {
|
|
1548
|
-
|
|
1577
|
+
clack.log.success(`${t.name.padEnd(15)} \u2192 ${t.filename}`);
|
|
1549
1578
|
}
|
|
1550
1579
|
}
|
|
1551
|
-
|
|
1552
|
-
All available targets:`);
|
|
1580
|
+
clack.log.info(`All available targets:`);
|
|
1553
1581
|
for (const c of allConfigs) {
|
|
1554
1582
|
const detected = targets.includes(c);
|
|
1555
1583
|
const icon = detected ? "\u2713" : "\xB7";
|
|
1556
|
-
|
|
1584
|
+
clack.log.info(`${icon} ${c.name.padEnd(15)} \u2192 ${c.filename}`);
|
|
1557
1585
|
}
|
|
1558
1586
|
return;
|
|
1559
1587
|
}
|
|
1560
1588
|
if (targets.length === 0) {
|
|
1561
|
-
|
|
1589
|
+
clack.log.warn("No agent tools detected. Use --all or --tools to specify targets.");
|
|
1562
1590
|
return;
|
|
1563
1591
|
}
|
|
1564
1592
|
const actions = generator.generateMcpConfigs(targets, { dir });
|
|
1565
1593
|
for (const action of actions) {
|
|
1566
1594
|
if (action.status === "up_to_date") {
|
|
1567
|
-
|
|
1595
|
+
clack.log.info(`${action.filename}: Up to date.`);
|
|
1568
1596
|
continue;
|
|
1569
1597
|
}
|
|
1570
1598
|
if (opts.dryRun) {
|
|
1571
|
-
|
|
1599
|
+
clack.log.info(`[DRY RUN] Would ${action.status} ${action.filename} (${action.tool})`);
|
|
1572
1600
|
} else {
|
|
1573
1601
|
generator.executeMcpConfig(action);
|
|
1574
1602
|
const verb = action.status === "merge" ? "merged into" : action.status === "create" ? "created" : "updated";
|
|
1575
|
-
|
|
1603
|
+
clack.log.success(`MCP config ${verb} ${action.filename} (${action.tool})`);
|
|
1576
1604
|
}
|
|
1577
1605
|
}
|
|
1578
1606
|
});
|
|
@@ -1623,43 +1651,53 @@ All available targets:`);
|
|
|
1623
1651
|
const { config, store, graph } = await loadContext(dir);
|
|
1624
1652
|
const absPath = resolve(dir, repoPath);
|
|
1625
1653
|
if (!existsSync(absPath)) {
|
|
1626
|
-
|
|
1654
|
+
clack.log.error(`Path ${repoPath} does not exist.`);
|
|
1627
1655
|
process.exit(1);
|
|
1628
1656
|
}
|
|
1629
1657
|
if (!isGitRepo(absPath)) {
|
|
1630
|
-
|
|
1658
|
+
clack.log.error(`Path ${repoPath} is not a git repository.`);
|
|
1631
1659
|
process.exit(1);
|
|
1632
1660
|
}
|
|
1633
1661
|
const relPath = relative(dir, absPath);
|
|
1634
1662
|
const name = opts.name || basename(absPath);
|
|
1635
1663
|
if (config.repos.some((r) => r.name === name || r.path === relPath)) {
|
|
1636
|
-
|
|
1664
|
+
clack.log.warn(`Repository already registered: ${name} (${relPath})`);
|
|
1637
1665
|
return;
|
|
1638
1666
|
}
|
|
1639
1667
|
config.addRepo(name, relPath);
|
|
1640
1668
|
await config.save();
|
|
1641
|
-
|
|
1642
|
-
|
|
1669
|
+
clack.log.success(`Registered repository: ${name} -> ${relPath}`);
|
|
1670
|
+
clack.log.step("Running initial full scan for the new repository...");
|
|
1643
1671
|
const onProgress = createProgressRenderer();
|
|
1644
1672
|
const scanner = new Scanner(store, config, graph, onProgress);
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1673
|
+
const onSigInt = () => {
|
|
1674
|
+
scanner.abort();
|
|
1675
|
+
onProgress.stop("Canceled");
|
|
1676
|
+
};
|
|
1677
|
+
process.once("SIGINT", onSigInt);
|
|
1678
|
+
const result = await scanner.scanFull([name]).catch((err) => {
|
|
1679
|
+
onProgress.stop();
|
|
1680
|
+
throw err;
|
|
1681
|
+
});
|
|
1682
|
+
process.removeListener("SIGINT", onSigInt);
|
|
1683
|
+
onProgress.stop();
|
|
1684
|
+
clack.log.success(`Scanned ${result.filesScanned} files, ${result.symbolsFound} symbols, ${result.edgesFound} edges in ${result.durationMs}ms`);
|
|
1647
1685
|
});
|
|
1648
1686
|
workspacesCmd.command("remove <name>").description("Unregister a repository by name or path").action(async (name) => {
|
|
1649
1687
|
const dir = resolveDir({}, program.opts());
|
|
1650
1688
|
const { config, store } = await loadContext(dir);
|
|
1651
1689
|
const repo = config.repos.find((r) => r.name === name || r.path === name);
|
|
1652
1690
|
if (!repo) {
|
|
1653
|
-
|
|
1691
|
+
clack.log.error(`Repository ${name} is not registered.`);
|
|
1654
1692
|
process.exit(1);
|
|
1655
1693
|
}
|
|
1656
1694
|
const repoName = repo.name;
|
|
1657
1695
|
config.removeRepo(name);
|
|
1658
1696
|
await config.save();
|
|
1659
|
-
|
|
1660
|
-
|
|
1697
|
+
clack.log.success(`Unregistered repository: ${repoName}`);
|
|
1698
|
+
clack.log.step(`Cleaning up stored data for repository: ${repoName}...`);
|
|
1661
1699
|
store.deleteRepo(repoName);
|
|
1662
|
-
|
|
1700
|
+
clack.log.success(`Done.`);
|
|
1663
1701
|
});
|
|
1664
1702
|
workspacesCmd.command("discover").description("Discover unregistered submodules, peer repos, and VS Code workspace folders (read-only)").action(async () => {
|
|
1665
1703
|
const dir = resolveDir({}, program.opts());
|
|
@@ -1750,20 +1788,31 @@ ${found} unregistered repositor${found === 1 ? "y" : "ies"} discovered. Use \`ma
|
|
|
1750
1788
|
}
|
|
1751
1789
|
}
|
|
1752
1790
|
if (toAdd.length === 0) {
|
|
1753
|
-
|
|
1791
|
+
clack.log.info("No new repositories discovered to sync.");
|
|
1754
1792
|
return;
|
|
1755
1793
|
}
|
|
1756
|
-
|
|
1757
|
-
const scanner = new Scanner(store, config, graph, createProgressRenderer());
|
|
1794
|
+
clack.log.step(`Syncing ${toAdd.length} newly discovered repositories:`);
|
|
1758
1795
|
for (const item of toAdd) {
|
|
1759
1796
|
config.addRepo(item.name, item.path);
|
|
1760
|
-
|
|
1797
|
+
clack.log.success(`Registered: ${item.name} -> ${item.path}`);
|
|
1761
1798
|
}
|
|
1762
1799
|
await config.save();
|
|
1763
|
-
|
|
1800
|
+
clack.log.step("Running initial full scan for new repositories...");
|
|
1764
1801
|
const newNames = toAdd.map((item) => item.name);
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1802
|
+
const onProgress = createProgressRenderer();
|
|
1803
|
+
const scanner = new Scanner(store, config, graph, onProgress);
|
|
1804
|
+
const onSigInt = () => {
|
|
1805
|
+
scanner.abort();
|
|
1806
|
+
onProgress.stop("Canceled");
|
|
1807
|
+
};
|
|
1808
|
+
process.once("SIGINT", onSigInt);
|
|
1809
|
+
const result = await scanner.scanFull(newNames).catch((err) => {
|
|
1810
|
+
onProgress.stop();
|
|
1811
|
+
throw err;
|
|
1812
|
+
});
|
|
1813
|
+
process.removeListener("SIGINT", onSigInt);
|
|
1814
|
+
onProgress.stop();
|
|
1815
|
+
clack.log.success(`Scanned ${result.filesScanned} files, ${result.symbolsFound} symbols, ${result.edgesFound} edges in ${result.durationMs}ms`);
|
|
1767
1816
|
});
|
|
1768
1817
|
return program;
|
|
1769
1818
|
}
|
package/dist/core/config.js
CHANGED
|
@@ -9,15 +9,63 @@ const DEFAULT_CONFIG = {
|
|
|
9
9
|
settings: {
|
|
10
10
|
maxTokenBudget: 16384,
|
|
11
11
|
excludePatterns: [
|
|
12
|
-
"node_modules/**",
|
|
13
|
-
"vendor/**",
|
|
14
12
|
".git/**",
|
|
15
|
-
"dist/**",
|
|
16
13
|
".mapx/**",
|
|
14
|
+
"node_modules/**",
|
|
15
|
+
"**/node_modules/**",
|
|
16
|
+
"dist/**",
|
|
17
|
+
"**/dist/**",
|
|
18
|
+
"vendor/**",
|
|
19
|
+
"**/vendor/**",
|
|
20
|
+
"Vendor/**",
|
|
21
|
+
"**/Vendor/**",
|
|
22
|
+
"__pycache__/**",
|
|
23
|
+
"**/__pycache__/**",
|
|
24
|
+
".venv/**",
|
|
25
|
+
".next/**",
|
|
26
|
+
"res/**",
|
|
27
|
+
"**/res/**",
|
|
28
|
+
"gradle/**",
|
|
29
|
+
"**/gradle/**",
|
|
30
|
+
"build/**",
|
|
31
|
+
"**/build/**",
|
|
17
32
|
"*.min.js",
|
|
18
|
-
"
|
|
33
|
+
"monaco/vs/**/*.js",
|
|
34
|
+
"bootstrap-*/bootstrap.js",
|
|
35
|
+
"bootstrap-*/bootstrap.min.js",
|
|
36
|
+
"bootstrap-*/bootstrap.bundle.js",
|
|
37
|
+
"bootstrap-*/bootstrap.bundle.min.js",
|
|
38
|
+
"vue.global.js",
|
|
39
|
+
"vue.esm-browser.js",
|
|
40
|
+
"vue.runtime.esm-browser.js",
|
|
41
|
+
"vue.runtime.esm-browser.prod.js",
|
|
42
|
+
"react.development.js",
|
|
43
|
+
"react.production.min.js",
|
|
44
|
+
"react-dom.development.js",
|
|
45
|
+
"react-dom.production.min.js",
|
|
46
|
+
"package.json",
|
|
19
47
|
"package-lock.json",
|
|
20
|
-
"
|
|
48
|
+
"yarn.lock",
|
|
49
|
+
"pnpm-lock.yaml",
|
|
50
|
+
"composer.json",
|
|
51
|
+
"composer.lock",
|
|
52
|
+
"pyproject.toml",
|
|
53
|
+
"Pipfile",
|
|
54
|
+
"Pipfile.lock",
|
|
55
|
+
"poetry.lock",
|
|
56
|
+
"go.mod",
|
|
57
|
+
"go.sum",
|
|
58
|
+
"Cargo.toml",
|
|
59
|
+
"Cargo.lock",
|
|
60
|
+
"bun.lock",
|
|
61
|
+
"AndroidManifest.xml",
|
|
62
|
+
"AndroidManifest.xml",
|
|
63
|
+
"**/*.log",
|
|
64
|
+
"**/*.lock",
|
|
65
|
+
"**/*.tmp",
|
|
66
|
+
"**/*.swp",
|
|
67
|
+
"**/*.swo",
|
|
68
|
+
"**/*.DS_Store"
|
|
21
69
|
],
|
|
22
70
|
includePatterns: []
|
|
23
71
|
}
|
package/dist/core/scanner.js
CHANGED
|
@@ -245,7 +245,21 @@ class Scanner {
|
|
|
245
245
|
const activeDetectors = await registry.detectActiveFrameworks(repoRoot, filePaths);
|
|
246
246
|
const activeFrameworks = activeDetectors.map((d) => d.name);
|
|
247
247
|
const ignoredSymbols = buildIgnoredSymbols(activeFrameworks);
|
|
248
|
-
|
|
248
|
+
let parsedFilesCount = 0;
|
|
249
|
+
const parseResults = await this.parseFilesParallel(
|
|
250
|
+
toParse,
|
|
251
|
+
workspaceRoot,
|
|
252
|
+
ignoredSymbols,
|
|
253
|
+
(relPath) => {
|
|
254
|
+
parsedFilesCount++;
|
|
255
|
+
this.onProgress?.({
|
|
256
|
+
phase: "parse",
|
|
257
|
+
current: unchangedFiles.length + resumedCompleted.size + parsedFilesCount,
|
|
258
|
+
total: discovered.length,
|
|
259
|
+
file: relPath
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
);
|
|
249
263
|
const fileMap = this.workspaceFileMap.size > 0 ? this.workspaceFileMap : (() => {
|
|
250
264
|
const allTrackedFiles = this.store.getAllFiles();
|
|
251
265
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -266,12 +280,6 @@ class Scanner {
|
|
|
266
280
|
totalEdges += parseResults[i].references.length;
|
|
267
281
|
completed.add(toParse[i].relativePath);
|
|
268
282
|
}
|
|
269
|
-
this.onProgress?.({
|
|
270
|
-
phase: "parse",
|
|
271
|
-
current: completed.size,
|
|
272
|
-
total: discovered.length,
|
|
273
|
-
file: toParse[batchEnd - 1].relativePath
|
|
274
|
-
});
|
|
275
283
|
this.saveResumeState(repo.name, {
|
|
276
284
|
totalFiles: discovered.length,
|
|
277
285
|
completedFiles: [...completed],
|
|
@@ -441,10 +449,20 @@ class Scanner {
|
|
|
441
449
|
const activeDetectors = await registry.detectActiveFrameworks(repoRoot, allFilePaths);
|
|
442
450
|
const activeFrameworks = activeDetectors.map((d) => d.name);
|
|
443
451
|
const ignoredSymbols = buildIgnoredSymbols(activeFrameworks);
|
|
452
|
+
let parsedFilesCount = 0;
|
|
444
453
|
const parseResults = await this.parseFilesParallel(
|
|
445
454
|
toReindex.map((r) => r.fileInfo),
|
|
446
455
|
workspaceRoot,
|
|
447
|
-
ignoredSymbols
|
|
456
|
+
ignoredSymbols,
|
|
457
|
+
(relPath) => {
|
|
458
|
+
parsedFilesCount++;
|
|
459
|
+
this.onProgress?.({
|
|
460
|
+
phase: "parse",
|
|
461
|
+
current: parsedFilesCount,
|
|
462
|
+
total: toReindex.length,
|
|
463
|
+
file: relPath
|
|
464
|
+
});
|
|
465
|
+
}
|
|
448
466
|
);
|
|
449
467
|
let totalSymbols = 0;
|
|
450
468
|
let totalEdges = 0;
|
|
@@ -456,12 +474,6 @@ class Scanner {
|
|
|
456
474
|
totalSymbols += result.symbols.length;
|
|
457
475
|
totalEdges += result.references.length;
|
|
458
476
|
langBreakdown[fileInfo.language] = (langBreakdown[fileInfo.language] || 0) + 1;
|
|
459
|
-
this.onProgress?.({
|
|
460
|
-
phase: "parse",
|
|
461
|
-
current: i + 1,
|
|
462
|
-
total: changes.length,
|
|
463
|
-
file: relPath
|
|
464
|
-
});
|
|
465
477
|
}
|
|
466
478
|
if (!this.aborted) {
|
|
467
479
|
const commitSha = getCurrentCommitSha(repoRoot);
|
|
@@ -564,14 +576,14 @@ class Scanner {
|
|
|
564
576
|
}
|
|
565
577
|
return deleted;
|
|
566
578
|
}
|
|
567
|
-
async parseFilesParallel(files, workspaceRoot, ignoredSymbols) {
|
|
579
|
+
async parseFilesParallel(files, workspaceRoot, ignoredSymbols, onFileParsed) {
|
|
568
580
|
if (files.length === 0) return [];
|
|
569
|
-
return this.parseOnMainThread(files, workspaceRoot, ignoredSymbols);
|
|
581
|
+
return this.parseOnMainThread(files, workspaceRoot, ignoredSymbols, onFileParsed);
|
|
570
582
|
}
|
|
571
|
-
async parseWithWorkers(files, workspaceRoot, ignoredSymbols) {
|
|
572
|
-
return this.parseOnMainThread(files, workspaceRoot, ignoredSymbols);
|
|
583
|
+
async parseWithWorkers(files, workspaceRoot, ignoredSymbols, onFileParsed) {
|
|
584
|
+
return this.parseOnMainThread(files, workspaceRoot, ignoredSymbols, onFileParsed);
|
|
573
585
|
}
|
|
574
|
-
async parseOnMainThread(files, workspaceRoot, ignoredSymbols) {
|
|
586
|
+
async parseOnMainThread(files, workspaceRoot, ignoredSymbols, onFileParsed) {
|
|
575
587
|
const results = new Array(files.length);
|
|
576
588
|
const sources = await Promise.all(
|
|
577
589
|
files.map(async (f) => {
|
|
@@ -592,6 +604,8 @@ class Scanner {
|
|
|
592
604
|
const relPath = relative(workspaceRoot, fileInfo.absolutePath).replace(/\\/g, "/");
|
|
593
605
|
if (sources[i] === null) {
|
|
594
606
|
results[i] = { symbols: [], references: [], errors: [{ message: `Failed to read ${relPath}` }] };
|
|
607
|
+
onFileParsed?.(relPath);
|
|
608
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
595
609
|
continue;
|
|
596
610
|
}
|
|
597
611
|
try {
|
|
@@ -603,6 +617,8 @@ class Scanner {
|
|
|
603
617
|
} catch {
|
|
604
618
|
results[i] = { symbols: [], references: [], errors: [{ message: `Failed to parse ${relPath}` }] };
|
|
605
619
|
}
|
|
620
|
+
onFileParsed?.(relPath);
|
|
621
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
606
622
|
}
|
|
607
623
|
};
|
|
608
624
|
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, files.length) }, runWorker));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mgamil/mapx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Multi-language code graph memory system for LLMs",
|
|
6
6
|
"author": {
|
|
@@ -94,6 +94,8 @@
|
|
|
94
94
|
"test": "tsx src/main.ts scan && tsx src/main.ts export"
|
|
95
95
|
},
|
|
96
96
|
"dependencies": {
|
|
97
|
+
"@clack/prompts": "^1.4.0",
|
|
98
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
97
99
|
"better-sqlite3": "^12.10.0",
|
|
98
100
|
"commander": "^13.1.0",
|
|
99
101
|
"cytoscape": "^3.33.4",
|
|
@@ -110,10 +112,8 @@
|
|
|
110
112
|
"tree-sitter-typescript": "^0.23.2",
|
|
111
113
|
"uplot": "^1.6.32",
|
|
112
114
|
"web-tree-sitter": "^0.26.9",
|
|
113
|
-
"zod": "^3.25.0"
|
|
114
|
-
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
115
|
+
"zod": "^3.25.0"
|
|
115
116
|
},
|
|
116
|
-
"optionalDependencies": {},
|
|
117
117
|
"devDependencies": {
|
|
118
118
|
"@types/better-sqlite3": "^7.6.13",
|
|
119
119
|
"@types/cytoscape": "^3.21.9",
|