typegraph-mcp 0.9.0 → 0.9.2

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/cli.js ADDED
@@ -0,0 +1,2842 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // config.ts
13
+ import * as path from "path";
14
+ function resolveConfig(toolDir) {
15
+ const cwd = process.cwd();
16
+ const projectRoot2 = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path.basename(path.dirname(toolDir)) === "plugins" ? path.resolve(toolDir, "../..") : cwd;
17
+ const tsconfigPath2 = process.env["TYPEGRAPH_TSCONFIG"] || "./tsconfig.json";
18
+ const toolIsEmbedded = toolDir.startsWith(projectRoot2 + path.sep);
19
+ const toolRelPath = toolIsEmbedded ? path.relative(projectRoot2, toolDir) : toolDir;
20
+ return { projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir, toolIsEmbedded, toolRelPath };
21
+ }
22
+ var init_config = __esm({
23
+ "config.ts"() {
24
+ }
25
+ });
26
+
27
+ // check.ts
28
+ var check_exports = {};
29
+ __export(check_exports, {
30
+ main: () => main
31
+ });
32
+ import * as fs from "fs";
33
+ import * as path2 from "path";
34
+ import { createRequire } from "module";
35
+ import { spawn } from "child_process";
36
+ function findFirstTsFile(dir) {
37
+ const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
38
+ try {
39
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
40
+ if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
41
+ return path2.join(dir, entry.name);
42
+ }
43
+ }
44
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
45
+ if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
46
+ const found = findFirstTsFile(path2.join(dir, entry.name));
47
+ if (found) return found;
48
+ }
49
+ }
50
+ } catch {
51
+ }
52
+ return null;
53
+ }
54
+ function testTsserver(projectRoot2) {
55
+ return new Promise((resolve8) => {
56
+ let tsserverPath;
57
+ try {
58
+ const require2 = createRequire(path2.resolve(projectRoot2, "package.json"));
59
+ tsserverPath = require2.resolve("typescript/lib/tsserver.js");
60
+ } catch {
61
+ resolve8(false);
62
+ return;
63
+ }
64
+ const child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
65
+ cwd: projectRoot2,
66
+ stdio: ["pipe", "pipe", "pipe"]
67
+ });
68
+ const timeout = setTimeout(() => {
69
+ child.kill();
70
+ resolve8(false);
71
+ }, 1e4);
72
+ let buffer = "";
73
+ child.stdout.on("data", (chunk) => {
74
+ buffer += chunk.toString();
75
+ if (buffer.includes('"success":true')) {
76
+ clearTimeout(timeout);
77
+ child.kill();
78
+ resolve8(true);
79
+ }
80
+ });
81
+ child.on("error", () => {
82
+ clearTimeout(timeout);
83
+ resolve8(false);
84
+ });
85
+ child.on("exit", () => {
86
+ clearTimeout(timeout);
87
+ });
88
+ const request = JSON.stringify({
89
+ seq: 1,
90
+ type: "request",
91
+ command: "configure",
92
+ arguments: {
93
+ preferences: { disableSuggestions: true }
94
+ }
95
+ });
96
+ child.stdin.write(request + "\n");
97
+ });
98
+ }
99
+ async function main(configOverride) {
100
+ const { projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
101
+ let passed = 0;
102
+ let failed = 0;
103
+ let warned = 0;
104
+ function pass(msg) {
105
+ console.log(` \u2713 ${msg}`);
106
+ passed++;
107
+ }
108
+ function fail(msg, fix) {
109
+ console.log(` \u2717 ${msg}`);
110
+ console.log(` Fix: ${fix}`);
111
+ failed++;
112
+ }
113
+ function warn(msg, note2) {
114
+ console.log(` ! ${msg}`);
115
+ console.log(` ${note2}`);
116
+ warned++;
117
+ }
118
+ function skip(msg) {
119
+ console.log(` - ${msg} (skipped)`);
120
+ }
121
+ console.log("");
122
+ console.log("typegraph-mcp Health Check");
123
+ console.log("=======================");
124
+ console.log(`Project root: ${projectRoot2}`);
125
+ console.log("");
126
+ const nodeVersion = process.version;
127
+ const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
128
+ if (nodeMajor >= 18) {
129
+ pass(`Node.js ${nodeVersion} (>= 18 required)`);
130
+ } else {
131
+ fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 18");
132
+ }
133
+ const tsxInRoot = fs.existsSync(path2.join(projectRoot2, "node_modules/.bin/tsx"));
134
+ const tsxInTool = fs.existsSync(path2.join(toolDir, "node_modules/.bin/tsx"));
135
+ if (tsxInRoot || tsxInTool) {
136
+ pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
137
+ } else {
138
+ pass("tsx available (via npx/global)");
139
+ }
140
+ let tsVersion = null;
141
+ try {
142
+ const require2 = createRequire(path2.resolve(projectRoot2, "package.json"));
143
+ const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
144
+ const tsPkgPath = path2.resolve(path2.dirname(tsserverPath), "..", "package.json");
145
+ const tsPkg = JSON.parse(fs.readFileSync(tsPkgPath, "utf-8"));
146
+ tsVersion = tsPkg.version;
147
+ pass(`TypeScript found (v${tsVersion})`);
148
+ } catch {
149
+ fail(
150
+ "TypeScript not found in project",
151
+ "Add `typescript` to devDependencies and run `pnpm install`"
152
+ );
153
+ }
154
+ const tsconfigAbs = path2.resolve(projectRoot2, tsconfigPath2);
155
+ if (fs.existsSync(tsconfigAbs)) {
156
+ pass(`tsconfig.json exists at ${tsconfigPath2}`);
157
+ } else {
158
+ fail(`tsconfig.json not found at ${tsconfigPath2}`, `Create a tsconfig.json at ${tsconfigPath2}`);
159
+ }
160
+ const pluginMcpPath = path2.join(toolDir, ".mcp.json");
161
+ const hasPluginMcp = fs.existsSync(pluginMcpPath) && fs.existsSync(path2.join(toolDir, ".claude-plugin/plugin.json"));
162
+ if (process.env.CLAUDE_PLUGIN_ROOT) {
163
+ pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
164
+ } else if (hasPluginMcp) {
165
+ pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
166
+ } else {
167
+ const mcpJsonPath = path2.resolve(projectRoot2, ".claude/mcp.json");
168
+ if (fs.existsSync(mcpJsonPath)) {
169
+ try {
170
+ const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
171
+ const tsNav = mcpJson?.mcpServers?.["typegraph"];
172
+ if (tsNav) {
173
+ const hasCommand = tsNav.command === "npx";
174
+ const hasArgs = Array.isArray(tsNav.args) && tsNav.args.includes("tsx");
175
+ const hasEnv = tsNav.env?.["TYPEGRAPH_PROJECT_ROOT"] && tsNav.env?.["TYPEGRAPH_TSCONFIG"];
176
+ if (hasCommand && hasArgs && hasEnv) {
177
+ pass("MCP registered in .claude/mcp.json");
178
+ } else {
179
+ const issues = [];
180
+ if (!hasCommand) issues.push("command should be 'npx'");
181
+ if (!hasArgs) issues.push("args should include 'tsx'");
182
+ if (!hasEnv) issues.push("env should set TYPEGRAPH_PROJECT_ROOT and TYPEGRAPH_TSCONFIG");
183
+ fail(
184
+ `MCP registration incomplete: ${issues.join(", ")}`,
185
+ "See README for correct .claude/mcp.json format"
186
+ );
187
+ }
188
+ } else {
189
+ const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path2.resolve(toolDir, "server.ts");
190
+ fail(
191
+ "MCP entry 'typegraph' not found in .claude/mcp.json",
192
+ `Add to .claude/mcp.json:
193
+ {
194
+ "mcpServers": {
195
+ "typegraph": {
196
+ "command": "npx",
197
+ "args": ["tsx", "${serverPath}"],
198
+ "env": { "TYPEGRAPH_PROJECT_ROOT": ".", "TYPEGRAPH_TSCONFIG": "./tsconfig.json" }
199
+ }
200
+ }
201
+ }`
202
+ );
203
+ }
204
+ } catch (err) {
205
+ fail(
206
+ "Failed to parse .claude/mcp.json",
207
+ `Check JSON syntax: ${err instanceof Error ? err.message : String(err)}`
208
+ );
209
+ }
210
+ } else {
211
+ fail(".claude/mcp.json not found", `Create .claude/mcp.json with typegraph server registration`);
212
+ }
213
+ }
214
+ const toolNodeModules = path2.join(toolDir, "node_modules");
215
+ if (fs.existsSync(toolNodeModules)) {
216
+ const requiredPkgs = ["@modelcontextprotocol/sdk", "oxc-parser", "oxc-resolver", "zod"];
217
+ const missing = requiredPkgs.filter(
218
+ (pkg) => !fs.existsSync(path2.join(toolNodeModules, ...pkg.split("/")))
219
+ );
220
+ if (missing.length === 0) {
221
+ pass(`Dependencies installed (${requiredPkgs.length} packages)`);
222
+ } else {
223
+ fail(`Missing packages: ${missing.join(", ")}`, `Run \`cd ${toolRelPath} && pnpm install\``);
224
+ }
225
+ } else {
226
+ fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && pnpm install\``);
227
+ }
228
+ try {
229
+ const oxcParserReq = createRequire(path2.join(toolDir, "package.json"));
230
+ const { parseSync: parseSync2 } = await import(oxcParserReq.resolve("oxc-parser"));
231
+ const result = parseSync2("test.ts", 'import { x } from "./y";');
232
+ if (result.module?.staticImports?.length === 1) {
233
+ pass("oxc-parser working");
234
+ } else {
235
+ fail(
236
+ "oxc-parser parseSync returned unexpected result",
237
+ `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && pnpm install\``
238
+ );
239
+ }
240
+ } catch (err) {
241
+ fail(
242
+ `oxc-parser failed: ${err instanceof Error ? err.message : String(err)}`,
243
+ `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && pnpm install\``
244
+ );
245
+ }
246
+ try {
247
+ const oxcResolverReq = createRequire(path2.join(toolDir, "package.json"));
248
+ const { ResolverFactory: ResolverFactory2 } = await import(oxcResolverReq.resolve("oxc-resolver"));
249
+ const resolver = new ResolverFactory2({
250
+ tsconfig: { configFile: tsconfigAbs, references: "auto" },
251
+ extensions: [".ts", ".tsx", ".js"],
252
+ extensionAlias: { ".js": [".ts", ".tsx", ".js"] }
253
+ });
254
+ let resolveOk = false;
255
+ const testFile = findFirstTsFile(projectRoot2);
256
+ if (testFile) {
257
+ const dir = path2.dirname(testFile);
258
+ const base = "./" + path2.basename(testFile);
259
+ const result = resolver.sync(dir, base);
260
+ resolveOk = !!result.path;
261
+ }
262
+ if (resolveOk) {
263
+ pass("oxc-resolver working");
264
+ } else {
265
+ warn(
266
+ "oxc-resolver loaded but couldn't resolve a test import",
267
+ "Check tsconfig.json is valid and has correct `references`"
268
+ );
269
+ }
270
+ } catch (err) {
271
+ fail(
272
+ `oxc-resolver failed: ${err instanceof Error ? err.message : String(err)}`,
273
+ `Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && pnpm install\``
274
+ );
275
+ }
276
+ if (tsVersion) {
277
+ try {
278
+ const ok = await testTsserver(projectRoot2);
279
+ if (ok) {
280
+ pass("tsserver responds to configure");
281
+ } else {
282
+ fail(
283
+ "tsserver did not respond",
284
+ "Verify `typescript` is installed and tsconfig.json is valid"
285
+ );
286
+ }
287
+ } catch (err) {
288
+ fail(
289
+ `tsserver failed to start: ${err instanceof Error ? err.message : String(err)}`,
290
+ "Verify `typescript` is installed and tsconfig.json is valid"
291
+ );
292
+ }
293
+ } else {
294
+ skip("tsserver test (TypeScript not found)");
295
+ }
296
+ try {
297
+ const { buildGraph: buildGraph2 } = await import(path2.resolve(toolDir, "module-graph.js"));
298
+ const start2 = performance.now();
299
+ const { graph } = await buildGraph2(projectRoot2, tsconfigPath2);
300
+ const elapsed = (performance.now() - start2).toFixed(0);
301
+ const edgeCount = [...graph.forward.values()].reduce(
302
+ (s, e) => s + e.length,
303
+ 0
304
+ );
305
+ if (graph.files.size > 0 && edgeCount > 0) {
306
+ pass(`Module graph: ${graph.files.size} files, ${edgeCount} edges [${elapsed}ms]`);
307
+ } else if (graph.files.size > 0) {
308
+ warn(
309
+ `Module graph: ${graph.files.size} files but 0 edges`,
310
+ "Files found but no internal imports resolved. Check tsconfig references."
311
+ );
312
+ } else {
313
+ fail(
314
+ "Module graph: 0 files discovered",
315
+ "Check tsconfig.json includes source files and project root is correct"
316
+ );
317
+ }
318
+ } catch (err) {
319
+ fail(
320
+ `Module graph build failed: ${err instanceof Error ? err.message : String(err)}`,
321
+ "Check that oxc-parser and oxc-resolver are installed correctly"
322
+ );
323
+ }
324
+ if (toolIsEmbedded) {
325
+ const eslintConfigPath = path2.resolve(projectRoot2, "eslint.config.mjs");
326
+ if (fs.existsSync(eslintConfigPath)) {
327
+ const eslintContent = fs.readFileSync(eslintConfigPath, "utf-8");
328
+ const parentDir = path2.basename(path2.dirname(toolDir));
329
+ const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
330
+ const hasParentIgnore = parentIgnorePattern.test(eslintContent);
331
+ if (hasParentIgnore) {
332
+ pass(`ESLint ignores ${parentDir}/`);
333
+ } else {
334
+ fail(
335
+ `ESLint missing ignore: "${parentDir}/**"`,
336
+ `Add to the ignores array in eslint.config.mjs:
337
+ "${parentDir}/**",`
338
+ );
339
+ }
340
+ } else {
341
+ skip("ESLint config check (no eslint.config.mjs)");
342
+ }
343
+ } else {
344
+ skip("ESLint config check (typegraph-mcp is external to project)");
345
+ }
346
+ const gitignorePath = path2.resolve(projectRoot2, ".gitignore");
347
+ if (fs.existsSync(gitignorePath)) {
348
+ const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
349
+ const lines = gitignoreContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
350
+ const ignoresClaude = lines.some(
351
+ (l) => l === ".claude/" || l === ".claude" || l === "/.claude"
352
+ );
353
+ const parentDir = toolIsEmbedded ? path2.basename(path2.dirname(toolDir)) : null;
354
+ const ignoresParent = parentDir && lines.some((l) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
355
+ if (!ignoresParent && !ignoresClaude) {
356
+ pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));
357
+ } else {
358
+ const excluded = [];
359
+ if (ignoresParent) excluded.push(`${parentDir}/`);
360
+ if (ignoresClaude) excluded.push(".claude/");
361
+ warn(
362
+ `.gitignore excludes ${excluded.join(" and ")}`,
363
+ "Remove these entries so MCP config and tool source are tracked in git"
364
+ );
365
+ }
366
+ } else {
367
+ skip(".gitignore check (no .gitignore)");
368
+ }
369
+ console.log("");
370
+ const total = passed + failed;
371
+ if (failed === 0) {
372
+ console.log(
373
+ `${passed}/${total} checks passed` + (warned > 0 ? ` (${warned} warning${warned > 1 ? "s" : ""})` : "") + " -- typegraph-mcp is ready"
374
+ );
375
+ } else {
376
+ console.log(
377
+ `${passed}/${total} checks passed, ${failed} failed` + (warned > 0 ? `, ${warned} warning${warned > 1 ? "s" : ""}` : "") + " -- fix issues above"
378
+ );
379
+ }
380
+ console.log("");
381
+ return { passed, failed, warned };
382
+ }
383
+ var isDirectRun;
384
+ var init_check = __esm({
385
+ "check.ts"() {
386
+ init_config();
387
+ isDirectRun = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(new URL(import.meta.url).pathname);
388
+ if (isDirectRun) {
389
+ main().then((result) => process.exit(result.failed > 0 ? 1 : 0)).catch((err) => {
390
+ console.error("Fatal error:", err);
391
+ process.exit(1);
392
+ });
393
+ }
394
+ }
395
+ });
396
+
397
+ // tsserver-client.ts
398
+ import { spawn as spawn2 } from "child_process";
399
+ import * as path3 from "path";
400
+ import * as fs2 from "fs";
401
+ import { createRequire as createRequire2 } from "module";
402
+ var log, REQUEST_TIMEOUT_MS, TsServerClient;
403
+ var init_tsserver_client = __esm({
404
+ "tsserver-client.ts"() {
405
+ log = (...args2) => console.error("[typegraph/tsserver]", ...args2);
406
+ REQUEST_TIMEOUT_MS = 1e4;
407
+ TsServerClient = class {
408
+ constructor(projectRoot2, tsconfigPath2 = "./tsconfig.json") {
409
+ this.projectRoot = projectRoot2;
410
+ this.tsconfigPath = tsconfigPath2;
411
+ }
412
+ child = null;
413
+ seq = 0;
414
+ pending = /* @__PURE__ */ new Map();
415
+ openFiles = /* @__PURE__ */ new Set();
416
+ buffer = Buffer.alloc(0);
417
+ ready = false;
418
+ shuttingDown = false;
419
+ restartCount = 0;
420
+ maxRestarts = 3;
421
+ // ─── Path Resolution ────────────────────────────────────────────────────
422
+ resolvePath(file) {
423
+ return path3.isAbsolute(file) ? file : path3.resolve(this.projectRoot, file);
424
+ }
425
+ relativePath(file) {
426
+ return path3.relative(this.projectRoot, file);
427
+ }
428
+ /** Read a line from a file (1-based line number). Returns trimmed content. */
429
+ readLine(file, line) {
430
+ try {
431
+ const absPath2 = this.resolvePath(file);
432
+ const content = fs2.readFileSync(absPath2, "utf-8");
433
+ const lines = content.split("\n");
434
+ return lines[line - 1]?.trim() ?? "";
435
+ } catch {
436
+ return "";
437
+ }
438
+ }
439
+ // ─── Lifecycle ──────────────────────────────────────────────────────────
440
+ async start() {
441
+ if (this.child) return;
442
+ const require2 = createRequire2(path3.resolve(this.projectRoot, "package.json"));
443
+ const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
444
+ log(`Spawning tsserver: ${tsserverPath}`);
445
+ log(`Project root: ${this.projectRoot}`);
446
+ log(`tsconfig: ${this.tsconfigPath}`);
447
+ this.child = spawn2("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
448
+ cwd: this.projectRoot,
449
+ stdio: ["pipe", "pipe", "pipe"],
450
+ env: { ...process.env, TSS_LOG: void 0 }
451
+ });
452
+ this.child.stdout.on("data", (chunk) => this.onData(chunk));
453
+ this.child.stderr.on("data", (chunk) => {
454
+ const text = chunk.toString().trim();
455
+ if (text) log(`[stderr] ${text}`);
456
+ });
457
+ this.child.on("close", (code) => {
458
+ log(`tsserver exited with code ${code}`);
459
+ this.child = null;
460
+ this.rejectAllPending(new Error(`tsserver exited with code ${code}`));
461
+ this.tryRestart();
462
+ });
463
+ this.child.on("error", (err) => {
464
+ log(`tsserver error: ${err.message}`);
465
+ this.rejectAllPending(err);
466
+ });
467
+ await this.sendRequest("configure", {
468
+ preferences: {
469
+ disableSuggestions: true
470
+ }
471
+ });
472
+ const warmStart = performance.now();
473
+ const tsconfigAbs = this.resolvePath(this.tsconfigPath);
474
+ if (fs2.existsSync(tsconfigAbs)) {
475
+ await this.sendRequest("compilerOptionsForInferredProjects", {
476
+ options: { allowJs: true, checkJs: false }
477
+ });
478
+ }
479
+ this.ready = true;
480
+ log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
481
+ }
482
+ shutdown() {
483
+ this.shuttingDown = true;
484
+ if (this.child) {
485
+ this.child.kill("SIGTERM");
486
+ this.child = null;
487
+ }
488
+ this.rejectAllPending(new Error("Client shutdown"));
489
+ }
490
+ tryRestart() {
491
+ if (this.shuttingDown) return;
492
+ if (this.restartCount >= this.maxRestarts) {
493
+ log(`Max restarts (${this.maxRestarts}) reached, not restarting`);
494
+ return;
495
+ }
496
+ this.restartCount++;
497
+ log(`Restarting tsserver (attempt ${this.restartCount})...`);
498
+ this.buffer = Buffer.alloc(0);
499
+ const filesToReopen = [...this.openFiles];
500
+ this.openFiles.clear();
501
+ this.start().then(async () => {
502
+ for (const file of filesToReopen) {
503
+ await this.ensureOpen(file).catch(() => {
504
+ });
505
+ }
506
+ });
507
+ }
508
+ rejectAllPending(err) {
509
+ for (const [seq, pending] of this.pending) {
510
+ clearTimeout(pending.timer);
511
+ pending.reject(err);
512
+ }
513
+ this.pending.clear();
514
+ }
515
+ // ─── Protocol: Parsing ──────────────────────────────────────────────────
516
+ onData(chunk) {
517
+ this.buffer = Buffer.concat([this.buffer, chunk]);
518
+ this.processBuffer();
519
+ }
520
+ processBuffer() {
521
+ while (true) {
522
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
523
+ if (headerEnd === -1) return;
524
+ const header = this.buffer.subarray(0, headerEnd).toString("utf-8");
525
+ const match = header.match(/Content-Length:\s*(\d+)/i);
526
+ if (!match) {
527
+ this.buffer = this.buffer.subarray(headerEnd + 4);
528
+ continue;
529
+ }
530
+ const contentLength = parseInt(match[1], 10);
531
+ const bodyStart = headerEnd + 4;
532
+ if (this.buffer.length < bodyStart + contentLength) {
533
+ return;
534
+ }
535
+ const bodyBytes = this.buffer.subarray(bodyStart, bodyStart + contentLength);
536
+ this.buffer = this.buffer.subarray(bodyStart + contentLength);
537
+ try {
538
+ const message = JSON.parse(bodyBytes.toString("utf-8"));
539
+ this.onMessage(message);
540
+ } catch {
541
+ log("Failed to parse tsserver message");
542
+ }
543
+ }
544
+ }
545
+ onMessage(message) {
546
+ if (message.type === "response" && message.request_seq !== void 0) {
547
+ const pending = this.pending.get(message.request_seq);
548
+ if (pending) {
549
+ clearTimeout(pending.timer);
550
+ this.pending.delete(message.request_seq);
551
+ if (message.success) {
552
+ pending.resolve(message.body);
553
+ } else {
554
+ pending.reject(
555
+ new Error(`tsserver ${pending.command} failed: ${message.message ?? "unknown error"}`)
556
+ );
557
+ }
558
+ }
559
+ }
560
+ }
561
+ // ─── Protocol: Sending ──────────────────────────────────────────────────
562
+ sendRequest(command2, args2) {
563
+ if (!this.child?.stdin?.writable) {
564
+ return Promise.reject(new Error("tsserver not running"));
565
+ }
566
+ const seq = ++this.seq;
567
+ const request = {
568
+ seq,
569
+ type: "request",
570
+ command: command2,
571
+ arguments: args2
572
+ };
573
+ return new Promise((resolve8, reject) => {
574
+ const timer = setTimeout(() => {
575
+ this.pending.delete(seq);
576
+ reject(new Error(`tsserver ${command2} timed out after ${REQUEST_TIMEOUT_MS}ms`));
577
+ }, REQUEST_TIMEOUT_MS);
578
+ this.pending.set(seq, { resolve: resolve8, reject, timer, command: command2 });
579
+ this.child.stdin.write(JSON.stringify(request) + "\n");
580
+ });
581
+ }
582
+ // Fire-and-forget — for commands like `open` that may not send a response
583
+ sendNotification(command2, args2) {
584
+ if (!this.child?.stdin?.writable) return;
585
+ const seq = ++this.seq;
586
+ const request = { seq, type: "request", command: command2, arguments: args2 };
587
+ this.child.stdin.write(JSON.stringify(request) + "\n");
588
+ }
589
+ // ─── File Management ───────────────────────────────────────────────────
590
+ async ensureOpen(file) {
591
+ const absPath2 = this.resolvePath(file);
592
+ if (this.openFiles.has(absPath2)) return;
593
+ this.openFiles.add(absPath2);
594
+ this.sendNotification("open", { file: absPath2 });
595
+ await new Promise((r) => setTimeout(r, 50));
596
+ }
597
+ // ─── Public API ────────────────────────────────────────────────────────
598
+ async definition(file, line, offset) {
599
+ const absPath2 = this.resolvePath(file);
600
+ await this.ensureOpen(absPath2);
601
+ const body = await this.sendRequest("definition", {
602
+ file: absPath2,
603
+ line,
604
+ offset
605
+ });
606
+ if (!body || !Array.isArray(body)) return [];
607
+ return body.map((d) => ({
608
+ ...d,
609
+ file: this.relativePath(d.file)
610
+ }));
611
+ }
612
+ async references(file, line, offset) {
613
+ const absPath2 = this.resolvePath(file);
614
+ await this.ensureOpen(absPath2);
615
+ const body = await this.sendRequest("references", {
616
+ file: absPath2,
617
+ line,
618
+ offset
619
+ });
620
+ if (!body?.refs) return [];
621
+ return body.refs.map((r) => ({
622
+ ...r,
623
+ file: this.relativePath(r.file)
624
+ }));
625
+ }
626
+ async quickinfo(file, line, offset) {
627
+ const absPath2 = this.resolvePath(file);
628
+ await this.ensureOpen(absPath2);
629
+ try {
630
+ const body = await this.sendRequest("quickinfo", {
631
+ file: absPath2,
632
+ line,
633
+ offset
634
+ });
635
+ return body ?? null;
636
+ } catch {
637
+ return null;
638
+ }
639
+ }
640
+ async navto(searchValue, maxResults = 10, file) {
641
+ if (file) await this.ensureOpen(file);
642
+ const args2 = {
643
+ searchValue,
644
+ maxResultCount: maxResults
645
+ };
646
+ if (file) args2["file"] = this.resolvePath(file);
647
+ const body = await this.sendRequest("navto", args2);
648
+ if (!body || !Array.isArray(body)) return [];
649
+ return body.map((item) => ({
650
+ ...item,
651
+ file: this.relativePath(item.file)
652
+ }));
653
+ }
654
+ async navbar(file) {
655
+ const absPath2 = this.resolvePath(file);
656
+ await this.ensureOpen(absPath2);
657
+ const body = await this.sendRequest("navbar", {
658
+ file: absPath2
659
+ });
660
+ return body ?? [];
661
+ }
662
+ };
663
+ }
664
+ });
665
+
666
+ // module-graph.ts
667
+ import { parseSync } from "oxc-parser";
668
+ import { ResolverFactory } from "oxc-resolver";
669
+ import * as fs3 from "fs";
670
+ import * as path4 from "path";
671
+ function discoverFiles(rootDir) {
672
+ const files = [];
673
+ function walk(dir) {
674
+ let entries;
675
+ try {
676
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
677
+ } catch {
678
+ return;
679
+ }
680
+ for (const entry of entries) {
681
+ if (entry.isDirectory()) {
682
+ if (SKIP_DIRS.has(entry.name)) continue;
683
+ if (entry.name.startsWith(".") && dir !== rootDir) continue;
684
+ walk(path4.join(dir, entry.name));
685
+ } else if (entry.isFile()) {
686
+ const name = entry.name;
687
+ if (SKIP_FILES.has(name)) continue;
688
+ if (name.endsWith(".d.ts") || name.endsWith(".d.mts") || name.endsWith(".d.cts")) continue;
689
+ const ext = path4.extname(name);
690
+ if (TS_EXTENSIONS.has(ext)) {
691
+ files.push(path4.join(dir, name));
692
+ }
693
+ }
694
+ }
695
+ }
696
+ walk(rootDir);
697
+ return files;
698
+ }
699
+ function parseFileImports(filePath, source) {
700
+ const result = parseSync(filePath, source);
701
+ const imports = [];
702
+ for (const imp of result.module.staticImports) {
703
+ const specifier = imp.moduleRequest.value;
704
+ const names = [];
705
+ let allTypeOnly = true;
706
+ for (const entry of imp.entries) {
707
+ const kind = entry.importName.kind;
708
+ const name = kind === "Default" ? "default" : kind === "All" || kind === "AllButDefault" || kind === "NamespaceObject" ? "*" : entry.importName.name ?? entry.localName.value;
709
+ names.push(name);
710
+ if (!entry.isType) allTypeOnly = false;
711
+ }
712
+ if (names.length === 0) {
713
+ imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: false });
714
+ } else {
715
+ imports.push({ specifier, names, isTypeOnly: allTypeOnly, isDynamic: false });
716
+ }
717
+ }
718
+ for (const exp of result.module.staticExports) {
719
+ for (const entry of exp.entries) {
720
+ const moduleRequest = entry.moduleRequest;
721
+ if (!moduleRequest) continue;
722
+ const specifier = moduleRequest.value;
723
+ const entryKind = entry.importName.kind;
724
+ const name = entryKind === "AllButDefault" || entryKind === "All" || entryKind === "NamespaceObject" ? "*" : entry.importName.name ?? "*";
725
+ const existing = imports.find((i) => i.specifier === specifier && !i.isDynamic);
726
+ if (existing) {
727
+ if (!existing.names.includes(name)) existing.names.push(name);
728
+ } else {
729
+ imports.push({ specifier, names: [name], isTypeOnly: false, isDynamic: false });
730
+ }
731
+ }
732
+ }
733
+ for (const di of result.module.dynamicImports) {
734
+ if (di.moduleRequest) {
735
+ const sliced = source.slice(di.moduleRequest.start, di.moduleRequest.end);
736
+ if (sliced.startsWith("'") || sliced.startsWith('"')) {
737
+ const specifier = sliced.slice(1, -1);
738
+ imports.push({ specifier, names: ["*"], isTypeOnly: false, isDynamic: true });
739
+ }
740
+ }
741
+ }
742
+ return imports;
743
+ }
744
+ function distToSource(resolvedPath, projectRoot2) {
745
+ if (!resolvedPath.startsWith(projectRoot2)) return resolvedPath;
746
+ const rel2 = path4.relative(projectRoot2, resolvedPath);
747
+ const distIdx = rel2.indexOf("dist" + path4.sep);
748
+ if (distIdx === -1) return resolvedPath;
749
+ const prefix = rel2.slice(0, distIdx);
750
+ const afterDist = rel2.slice(distIdx + 5);
751
+ const withoutExt = afterDist.replace(/\.(m?j|c)s$/, "");
752
+ for (const ext of SOURCE_EXTS) {
753
+ const candidate = path4.resolve(projectRoot2, prefix, "src", withoutExt + ext);
754
+ if (fs3.existsSync(candidate)) return candidate;
755
+ }
756
+ for (const ext of SOURCE_EXTS) {
757
+ const candidate = path4.resolve(projectRoot2, prefix, withoutExt + ext);
758
+ if (fs3.existsSync(candidate)) return candidate;
759
+ }
760
+ if (withoutExt.endsWith("/index")) {
761
+ const dirPath = withoutExt.slice(0, -6);
762
+ for (const ext of SOURCE_EXTS) {
763
+ const candidate = path4.resolve(projectRoot2, prefix, "src", dirPath + ext);
764
+ if (fs3.existsSync(candidate)) return candidate;
765
+ }
766
+ }
767
+ return resolvedPath;
768
+ }
769
+ function resolveImport(resolver, fromDir, specifier, projectRoot2) {
770
+ try {
771
+ const result = resolver.sync(fromDir, specifier);
772
+ if (result.path && !result.path.includes("node_modules")) {
773
+ const mapped = distToSource(result.path, projectRoot2);
774
+ const ext = path4.extname(mapped);
775
+ if (!TS_EXTENSIONS.has(ext)) return null;
776
+ if (SKIP_FILES.has(path4.basename(mapped))) return null;
777
+ return mapped;
778
+ }
779
+ } catch {
780
+ }
781
+ return null;
782
+ }
783
+ function createResolver(projectRoot2, tsconfigPath2) {
784
+ return new ResolverFactory({
785
+ tsconfig: {
786
+ configFile: path4.resolve(projectRoot2, tsconfigPath2),
787
+ references: "auto"
788
+ },
789
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
790
+ extensionAlias: {
791
+ ".js": [".ts", ".tsx", ".js"],
792
+ ".jsx": [".tsx", ".jsx"],
793
+ ".mjs": [".mts", ".mjs"],
794
+ ".cjs": [".cts", ".cjs"]
795
+ },
796
+ conditionNames: ["import", "require"],
797
+ mainFields: ["module", "main"]
798
+ });
799
+ }
800
+ function buildForwardEdges(files, resolver, projectRoot2) {
801
+ const forward = /* @__PURE__ */ new Map();
802
+ const parseFailures = [];
803
+ for (const filePath of files) {
804
+ let source;
805
+ try {
806
+ source = fs3.readFileSync(filePath, "utf-8");
807
+ } catch {
808
+ continue;
809
+ }
810
+ let rawImports;
811
+ try {
812
+ rawImports = parseFileImports(filePath, source);
813
+ } catch (err) {
814
+ parseFailures.push(filePath);
815
+ continue;
816
+ }
817
+ const edges = [];
818
+ const fromDir = path4.dirname(filePath);
819
+ for (const raw of rawImports) {
820
+ const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot2);
821
+ if (target) {
822
+ edges.push({
823
+ target,
824
+ specifiers: raw.names,
825
+ isTypeOnly: raw.isTypeOnly,
826
+ isDynamic: raw.isDynamic
827
+ });
828
+ }
829
+ }
830
+ forward.set(filePath, edges);
831
+ }
832
+ return { forward, parseFailures };
833
+ }
834
+ function buildReverseMap(forward) {
835
+ const reverse = /* @__PURE__ */ new Map();
836
+ for (const [source, edges] of forward) {
837
+ for (const edge of edges) {
838
+ let revEdges = reverse.get(edge.target);
839
+ if (!revEdges) {
840
+ revEdges = [];
841
+ reverse.set(edge.target, revEdges);
842
+ }
843
+ revEdges.push({
844
+ target: source,
845
+ // reverse: the "target" is the file that imports
846
+ specifiers: edge.specifiers,
847
+ isTypeOnly: edge.isTypeOnly,
848
+ isDynamic: edge.isDynamic
849
+ });
850
+ }
851
+ }
852
+ return reverse;
853
+ }
854
+ async function buildGraph(projectRoot2, tsconfigPath2) {
855
+ const startTime = performance.now();
856
+ const resolver = createResolver(projectRoot2, tsconfigPath2);
857
+ const fileList = discoverFiles(projectRoot2);
858
+ log2(`Discovered ${fileList.length} source files`);
859
+ const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot2);
860
+ const reverse = buildReverseMap(forward);
861
+ const files = new Set(fileList);
862
+ const edgeCount = [...forward.values()].reduce((sum, edges) => sum + edges.length, 0);
863
+ const elapsed = (performance.now() - startTime).toFixed(0);
864
+ log2(`Graph built: ${files.size} files, ${edgeCount} edges [${elapsed}ms]`);
865
+ if (parseFailures.length > 0) {
866
+ log2(`Parse failures: ${parseFailures.length} files`);
867
+ }
868
+ return {
869
+ graph: { forward, reverse, files },
870
+ resolver
871
+ };
872
+ }
873
+ function updateFile(graph, filePath, resolver, projectRoot2) {
874
+ const oldEdges = graph.forward.get(filePath) ?? [];
875
+ for (const edge of oldEdges) {
876
+ const revEdges = graph.reverse.get(edge.target);
877
+ if (revEdges) {
878
+ const idx = revEdges.findIndex((r) => r.target === filePath);
879
+ if (idx !== -1) revEdges.splice(idx, 1);
880
+ if (revEdges.length === 0) graph.reverse.delete(edge.target);
881
+ }
882
+ }
883
+ let source;
884
+ try {
885
+ source = fs3.readFileSync(filePath, "utf-8");
886
+ } catch {
887
+ removeFile(graph, filePath);
888
+ return;
889
+ }
890
+ let rawImports;
891
+ try {
892
+ rawImports = parseFileImports(filePath, source);
893
+ } catch {
894
+ log2(`Parse error on update: ${filePath}`);
895
+ graph.forward.set(filePath, []);
896
+ return;
897
+ }
898
+ const fromDir = path4.dirname(filePath);
899
+ const newEdges = [];
900
+ for (const raw of rawImports) {
901
+ const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot2);
902
+ if (target) {
903
+ newEdges.push({
904
+ target,
905
+ specifiers: raw.names,
906
+ isTypeOnly: raw.isTypeOnly,
907
+ isDynamic: raw.isDynamic
908
+ });
909
+ }
910
+ }
911
+ graph.forward.set(filePath, newEdges);
912
+ graph.files.add(filePath);
913
+ for (const edge of newEdges) {
914
+ let revEdges = graph.reverse.get(edge.target);
915
+ if (!revEdges) {
916
+ revEdges = [];
917
+ graph.reverse.set(edge.target, revEdges);
918
+ }
919
+ revEdges.push({
920
+ target: filePath,
921
+ specifiers: edge.specifiers,
922
+ isTypeOnly: edge.isTypeOnly,
923
+ isDynamic: edge.isDynamic
924
+ });
925
+ }
926
+ }
927
+ function removeFile(graph, filePath) {
928
+ const edges = graph.forward.get(filePath) ?? [];
929
+ for (const edge of edges) {
930
+ const revEdges2 = graph.reverse.get(edge.target);
931
+ if (revEdges2) {
932
+ const idx = revEdges2.findIndex((r) => r.target === filePath);
933
+ if (idx !== -1) revEdges2.splice(idx, 1);
934
+ if (revEdges2.length === 0) graph.reverse.delete(edge.target);
935
+ }
936
+ }
937
+ const revEdges = graph.reverse.get(filePath) ?? [];
938
+ for (const revEdge of revEdges) {
939
+ const fwdEdges = graph.forward.get(revEdge.target);
940
+ if (fwdEdges) {
941
+ const idx = fwdEdges.findIndex((e) => e.target === filePath);
942
+ if (idx !== -1) fwdEdges.splice(idx, 1);
943
+ }
944
+ }
945
+ graph.forward.delete(filePath);
946
+ graph.reverse.delete(filePath);
947
+ graph.files.delete(filePath);
948
+ }
949
+ function startWatcher(projectRoot2, graph, resolver) {
950
+ try {
951
+ const watcher = fs3.watch(
952
+ projectRoot2,
953
+ { recursive: true },
954
+ (_eventType, filename) => {
955
+ if (!filename) return;
956
+ const ext = path4.extname(filename);
957
+ if (!TS_EXTENSIONS.has(ext)) return;
958
+ const parts = filename.split(path4.sep);
959
+ if (parts.some((p2) => SKIP_DIRS.has(p2))) return;
960
+ if (SKIP_FILES.has(path4.basename(filename))) return;
961
+ if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
962
+ return;
963
+ const absPath2 = path4.resolve(projectRoot2, filename);
964
+ if (fs3.existsSync(absPath2)) {
965
+ updateFile(graph, absPath2, resolver, projectRoot2);
966
+ } else {
967
+ removeFile(graph, absPath2);
968
+ }
969
+ }
970
+ );
971
+ process.on("SIGINT", () => watcher.close());
972
+ process.on("SIGTERM", () => watcher.close());
973
+ log2("File watcher started");
974
+ } catch (err) {
975
+ log2("Failed to start file watcher:", err);
976
+ }
977
+ }
978
+ var log2, TS_EXTENSIONS, SKIP_DIRS, SKIP_FILES, SOURCE_EXTS;
979
+ var init_module_graph = __esm({
980
+ "module-graph.ts"() {
981
+ log2 = (...args2) => console.error("[typegraph/graph]", ...args2);
982
+ TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
983
+ SKIP_DIRS = /* @__PURE__ */ new Set([
984
+ "node_modules",
985
+ "dist",
986
+ "build",
987
+ "out",
988
+ ".wrangler",
989
+ ".mf",
990
+ ".git",
991
+ ".next",
992
+ ".turbo",
993
+ "coverage"
994
+ ]);
995
+ SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
996
+ SOURCE_EXTS = [".ts", ".tsx", ".mts", ".cts"];
997
+ }
998
+ });
999
+
1000
+ // graph-queries.ts
1001
+ import * as fs4 from "fs";
1002
+ import * as path5 from "path";
1003
+ function shouldIncludeEdge(edge, includeTypeOnly) {
1004
+ if (!includeTypeOnly && edge.isTypeOnly) return false;
1005
+ return true;
1006
+ }
1007
+ function dependencyTree(graph, file, opts = {}) {
1008
+ const { depth = Infinity, includeTypeOnly = false } = opts;
1009
+ const visited = /* @__PURE__ */ new Set();
1010
+ const result = [];
1011
+ let frontier = [file];
1012
+ visited.add(file);
1013
+ let currentDepth = 0;
1014
+ while (frontier.length > 0 && currentDepth < depth) {
1015
+ const nextFrontier = [];
1016
+ for (const f of frontier) {
1017
+ const edges = graph.forward.get(f) ?? [];
1018
+ for (const edge of edges) {
1019
+ if (!shouldIncludeEdge(edge, includeTypeOnly)) continue;
1020
+ if (visited.has(edge.target)) continue;
1021
+ visited.add(edge.target);
1022
+ result.push(edge.target);
1023
+ nextFrontier.push(edge.target);
1024
+ }
1025
+ }
1026
+ frontier = nextFrontier;
1027
+ currentDepth++;
1028
+ }
1029
+ return { root: file, nodes: result.length, files: result };
1030
+ }
1031
+ function findPackageName(filePath) {
1032
+ let dir = path5.dirname(filePath);
1033
+ while (dir !== path5.dirname(dir)) {
1034
+ if (packageNameCache.has(dir)) return packageNameCache.get(dir);
1035
+ const pkgJsonPath = path5.join(dir, "package.json");
1036
+ try {
1037
+ if (fs4.existsSync(pkgJsonPath)) {
1038
+ const pkg = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
1039
+ const name = pkg.name ?? path5.basename(dir);
1040
+ packageNameCache.set(dir, name);
1041
+ return name;
1042
+ }
1043
+ } catch {
1044
+ }
1045
+ dir = path5.dirname(dir);
1046
+ }
1047
+ return "<root>";
1048
+ }
1049
+ function dependents(graph, file, opts = {}) {
1050
+ const { depth = Infinity, includeTypeOnly = false } = opts;
1051
+ const visited = /* @__PURE__ */ new Set();
1052
+ const result = [];
1053
+ let directCount = 0;
1054
+ let frontier = [file];
1055
+ visited.add(file);
1056
+ let currentDepth = 0;
1057
+ while (frontier.length > 0 && currentDepth < depth) {
1058
+ const nextFrontier = [];
1059
+ for (const f of frontier) {
1060
+ const edges = graph.reverse.get(f) ?? [];
1061
+ for (const edge of edges) {
1062
+ if (!shouldIncludeEdge(edge, includeTypeOnly)) continue;
1063
+ if (visited.has(edge.target)) continue;
1064
+ visited.add(edge.target);
1065
+ result.push(edge.target);
1066
+ if (currentDepth === 0) directCount++;
1067
+ nextFrontier.push(edge.target);
1068
+ }
1069
+ }
1070
+ frontier = nextFrontier;
1071
+ currentDepth++;
1072
+ }
1073
+ const byPackage = {};
1074
+ for (const f of result) {
1075
+ const pkgName = findPackageName(f);
1076
+ if (!byPackage[pkgName]) byPackage[pkgName] = [];
1077
+ byPackage[pkgName].push(f);
1078
+ }
1079
+ return { root: file, nodes: result.length, directCount, files: result, byPackage };
1080
+ }
1081
+ function importCycles(graph, opts = {}) {
1082
+ const { file, package: pkgDir } = opts;
1083
+ let index = 0;
1084
+ const stack = [];
1085
+ const onStack = /* @__PURE__ */ new Set();
1086
+ const indices = /* @__PURE__ */ new Map();
1087
+ const lowlinks = /* @__PURE__ */ new Map();
1088
+ const sccs = [];
1089
+ function strongconnect(v) {
1090
+ indices.set(v, index);
1091
+ lowlinks.set(v, index);
1092
+ index++;
1093
+ stack.push(v);
1094
+ onStack.add(v);
1095
+ const edges = graph.forward.get(v) ?? [];
1096
+ for (const edge of edges) {
1097
+ const w = edge.target;
1098
+ if (!graph.files.has(w)) continue;
1099
+ if (!indices.has(w)) {
1100
+ strongconnect(w);
1101
+ lowlinks.set(v, Math.min(lowlinks.get(v), lowlinks.get(w)));
1102
+ } else if (onStack.has(w)) {
1103
+ lowlinks.set(v, Math.min(lowlinks.get(v), indices.get(w)));
1104
+ }
1105
+ }
1106
+ if (lowlinks.get(v) === indices.get(v)) {
1107
+ const scc = [];
1108
+ let w;
1109
+ do {
1110
+ w = stack.pop();
1111
+ onStack.delete(w);
1112
+ scc.push(w);
1113
+ } while (w !== v);
1114
+ if (scc.length > 1) {
1115
+ sccs.push(scc);
1116
+ }
1117
+ }
1118
+ }
1119
+ for (const f of graph.files) {
1120
+ if (!indices.has(f)) {
1121
+ strongconnect(f);
1122
+ }
1123
+ }
1124
+ let cycles = sccs;
1125
+ if (file) {
1126
+ cycles = cycles.filter((scc) => scc.includes(file));
1127
+ }
1128
+ if (pkgDir) {
1129
+ const absPkgDir = path5.resolve(pkgDir);
1130
+ cycles = cycles.filter(
1131
+ (scc) => scc.every((f) => f.startsWith(absPkgDir))
1132
+ );
1133
+ }
1134
+ return { count: cycles.length, cycles };
1135
+ }
1136
+ function shortestPath(graph, from, to, opts = {}) {
1137
+ const { includeTypeOnly = false } = opts;
1138
+ if (from === to) {
1139
+ return { path: [from], hops: 0, chain: [{ file: from, imports: [] }] };
1140
+ }
1141
+ const visited = /* @__PURE__ */ new Set();
1142
+ const parent = /* @__PURE__ */ new Map();
1143
+ visited.add(from);
1144
+ let frontier = [from];
1145
+ while (frontier.length > 0) {
1146
+ const nextFrontier = [];
1147
+ for (const f of frontier) {
1148
+ const edges = graph.forward.get(f) ?? [];
1149
+ for (const edge of edges) {
1150
+ if (!shouldIncludeEdge(edge, includeTypeOnly)) continue;
1151
+ if (visited.has(edge.target)) continue;
1152
+ visited.add(edge.target);
1153
+ parent.set(edge.target, { from: f, specifiers: edge.specifiers });
1154
+ if (edge.target === to) {
1155
+ const filePath = [to];
1156
+ let current = to;
1157
+ while (parent.has(current)) {
1158
+ current = parent.get(current).from;
1159
+ filePath.unshift(current);
1160
+ }
1161
+ const chain = [];
1162
+ for (let i = 0; i < filePath.length; i++) {
1163
+ const p2 = parent.get(filePath[i]);
1164
+ chain.push({
1165
+ file: filePath[i],
1166
+ imports: p2?.specifiers ?? []
1167
+ });
1168
+ }
1169
+ return { path: filePath, hops: filePath.length - 1, chain };
1170
+ }
1171
+ nextFrontier.push(edge.target);
1172
+ }
1173
+ }
1174
+ frontier = nextFrontier;
1175
+ }
1176
+ return { path: null, hops: -1, chain: [] };
1177
+ }
1178
+ function subgraph(graph, files, opts = {}) {
1179
+ const { depth = 1, direction = "both" } = opts;
1180
+ const visited = new Set(files);
1181
+ let frontier = [...files];
1182
+ for (let d = 0; d < depth && frontier.length > 0; d++) {
1183
+ const nextFrontier = [];
1184
+ for (const f of frontier) {
1185
+ if (direction === "imports" || direction === "both") {
1186
+ for (const edge of graph.forward.get(f) ?? []) {
1187
+ if (!visited.has(edge.target)) {
1188
+ visited.add(edge.target);
1189
+ nextFrontier.push(edge.target);
1190
+ }
1191
+ }
1192
+ }
1193
+ if (direction === "dependents" || direction === "both") {
1194
+ for (const edge of graph.reverse.get(f) ?? []) {
1195
+ if (!visited.has(edge.target)) {
1196
+ visited.add(edge.target);
1197
+ nextFrontier.push(edge.target);
1198
+ }
1199
+ }
1200
+ }
1201
+ }
1202
+ frontier = nextFrontier;
1203
+ }
1204
+ const nodes = [...visited];
1205
+ const edges = [];
1206
+ for (const f of nodes) {
1207
+ for (const edge of graph.forward.get(f) ?? []) {
1208
+ if (visited.has(edge.target)) {
1209
+ edges.push({
1210
+ from: f,
1211
+ to: edge.target,
1212
+ specifiers: edge.specifiers,
1213
+ isTypeOnly: edge.isTypeOnly
1214
+ });
1215
+ }
1216
+ }
1217
+ }
1218
+ return { nodes, edges, stats: { nodeCount: nodes.length, edgeCount: edges.length } };
1219
+ }
1220
+ function moduleBoundary(graph, files) {
1221
+ const fileSet = new Set(files);
1222
+ let internalEdges = 0;
1223
+ const incomingEdges = [];
1224
+ const outgoingEdges = [];
1225
+ const outgoingTargets = /* @__PURE__ */ new Set();
1226
+ for (const f of files) {
1227
+ for (const edge of graph.forward.get(f) ?? []) {
1228
+ if (fileSet.has(edge.target)) {
1229
+ internalEdges++;
1230
+ } else {
1231
+ outgoingEdges.push({
1232
+ from: f,
1233
+ to: edge.target,
1234
+ specifiers: edge.specifiers
1235
+ });
1236
+ outgoingTargets.add(edge.target);
1237
+ }
1238
+ }
1239
+ }
1240
+ for (const f of files) {
1241
+ for (const edge of graph.reverse.get(f) ?? []) {
1242
+ if (!fileSet.has(edge.target)) {
1243
+ incomingEdges.push({
1244
+ from: edge.target,
1245
+ to: f,
1246
+ specifiers: edge.specifiers
1247
+ });
1248
+ }
1249
+ }
1250
+ }
1251
+ const depCounts = /* @__PURE__ */ new Map();
1252
+ for (const f of files) {
1253
+ for (const edge of graph.forward.get(f) ?? []) {
1254
+ if (!fileSet.has(edge.target)) {
1255
+ depCounts.set(edge.target, (depCounts.get(edge.target) ?? 0) + 1);
1256
+ }
1257
+ }
1258
+ }
1259
+ const sharedDependencies = [...depCounts.entries()].filter(([, count]) => count > 1).map(([dep]) => dep);
1260
+ const total = internalEdges + incomingEdges.length + outgoingEdges.length;
1261
+ const isolationScore = total === 0 ? 1 : internalEdges / total;
1262
+ return {
1263
+ internalEdges,
1264
+ incomingEdges,
1265
+ outgoingEdges,
1266
+ sharedDependencies,
1267
+ isolationScore
1268
+ };
1269
+ }
1270
+ var packageNameCache;
1271
+ var init_graph_queries = __esm({
1272
+ "graph-queries.ts"() {
1273
+ packageNameCache = /* @__PURE__ */ new Map();
1274
+ }
1275
+ });
1276
+
1277
+ // smoke-test.ts
1278
+ var smoke_test_exports = {};
1279
+ __export(smoke_test_exports, {
1280
+ main: () => main2
1281
+ });
1282
+ import * as fs5 from "fs";
1283
+ import * as path6 from "path";
1284
+ function rel(absPath2, projectRoot2) {
1285
+ return path6.relative(projectRoot2, absPath2);
1286
+ }
1287
+ function findInNavBar(items, predicate) {
1288
+ for (const item of items) {
1289
+ if (predicate(item)) return item;
1290
+ if (item.childItems?.length > 0) {
1291
+ const found = findInNavBar(item.childItems, predicate);
1292
+ if (found) return found;
1293
+ }
1294
+ }
1295
+ return null;
1296
+ }
1297
+ function findTestFile(rootDir) {
1298
+ const candidates = [];
1299
+ function walk(dir, depth) {
1300
+ if (depth > 5 || candidates.length >= 30) return;
1301
+ let entries;
1302
+ try {
1303
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
1304
+ } catch {
1305
+ return;
1306
+ }
1307
+ for (const entry of entries) {
1308
+ if (entry.isDirectory()) {
1309
+ if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
1310
+ walk(path6.join(dir, entry.name), depth + 1);
1311
+ } else if (entry.isFile()) {
1312
+ const name = entry.name;
1313
+ if (name.endsWith(".d.ts") || name.endsWith(".test.ts") || name.endsWith(".spec.ts"))
1314
+ continue;
1315
+ if (!name.endsWith(".ts") && !name.endsWith(".tsx")) continue;
1316
+ try {
1317
+ const stat = fs5.statSync(path6.join(dir, name));
1318
+ if (stat.size > 200 && stat.size < 5e4) {
1319
+ candidates.push({ file: path6.join(dir, name), size: stat.size });
1320
+ }
1321
+ } catch {
1322
+ }
1323
+ }
1324
+ }
1325
+ }
1326
+ walk(rootDir, 0);
1327
+ const preferred = candidates.find(
1328
+ (c) => /service|handler|controller|repository|provider/i.test(path6.basename(c.file))
1329
+ );
1330
+ const fallback = candidates.sort((a, b) => b.size - a.size)[0];
1331
+ return preferred?.file ?? fallback?.file ?? null;
1332
+ }
1333
+ function findImporter(graph, file) {
1334
+ const revEdges = graph.reverse.get(file);
1335
+ if (!revEdges || revEdges.length === 0) return null;
1336
+ const preferred = revEdges.find(
1337
+ (e) => !e.target.includes(".test.") && !e.target.endsWith("index.ts")
1338
+ );
1339
+ return (preferred ?? revEdges[0]).target;
1340
+ }
1341
+ async function main2(configOverride) {
1342
+ const { projectRoot: projectRoot2, tsconfigPath: tsconfigPath2 } = configOverride ?? resolveConfig(import.meta.dirname);
1343
+ let passed = 0;
1344
+ let failed = 0;
1345
+ let skipped = 0;
1346
+ function pass(name, detail, ms) {
1347
+ console.log(` \u2713 ${name} [${ms.toFixed(0)}ms]`);
1348
+ console.log(` ${detail}`);
1349
+ passed++;
1350
+ }
1351
+ function fail(name, detail, ms) {
1352
+ console.log(` \u2717 ${name} [${ms.toFixed(0)}ms]`);
1353
+ console.log(` ${detail}`);
1354
+ failed++;
1355
+ }
1356
+ function skip(name, reason) {
1357
+ console.log(` - ${name} (skipped: ${reason})`);
1358
+ skipped++;
1359
+ }
1360
+ console.log("");
1361
+ console.log("typegraph-mcp Smoke Test");
1362
+ console.log("=====================");
1363
+ console.log(`Project root: ${projectRoot2}`);
1364
+ console.log("");
1365
+ const testFile = findTestFile(projectRoot2);
1366
+ if (!testFile) {
1367
+ console.log(" No suitable .ts file found in project. Cannot run smoke tests.");
1368
+ return { passed, failed: failed + 1, skipped };
1369
+ }
1370
+ const testFileRel = rel(testFile, projectRoot2);
1371
+ console.log(`Test subject: ${testFileRel}`);
1372
+ console.log("");
1373
+ console.log("\u2500\u2500 Module Graph \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1374
+ let graph;
1375
+ let t0;
1376
+ t0 = performance.now();
1377
+ try {
1378
+ const result = await buildGraph(projectRoot2, tsconfigPath2);
1379
+ graph = result.graph;
1380
+ const ms = performance.now() - t0;
1381
+ const edgeCount = [...graph.forward.values()].reduce((s, e) => s + e.length, 0);
1382
+ if (graph.files.size > 0) {
1383
+ pass("graph build", `${graph.files.size} files, ${edgeCount} edges`, ms);
1384
+ } else {
1385
+ fail("graph build", "0 files discovered", ms);
1386
+ console.log("\nCannot continue without module graph.");
1387
+ return { passed, failed, skipped };
1388
+ }
1389
+ } catch (err) {
1390
+ fail(
1391
+ "graph build",
1392
+ `Error: ${err instanceof Error ? err.message : String(err)}`,
1393
+ performance.now() - t0
1394
+ );
1395
+ console.log("\nCannot continue without module graph.");
1396
+ return { passed, failed, skipped };
1397
+ }
1398
+ t0 = performance.now();
1399
+ if (graph.files.has(testFile)) {
1400
+ const result = dependencyTree(graph, testFile);
1401
+ pass(
1402
+ "dependency_tree",
1403
+ `${result.nodes} transitive deps from ${testFileRel}`,
1404
+ performance.now() - t0
1405
+ );
1406
+ } else {
1407
+ skip("dependency_tree", `${testFileRel} not in graph`);
1408
+ }
1409
+ t0 = performance.now();
1410
+ if (graph.files.has(testFile)) {
1411
+ const result = dependents(graph, testFile);
1412
+ pass(
1413
+ "dependents",
1414
+ `${result.nodes} dependents (${result.directCount} direct)`,
1415
+ performance.now() - t0
1416
+ );
1417
+ } else {
1418
+ skip("dependents", `${testFileRel} not in graph`);
1419
+ }
1420
+ t0 = performance.now();
1421
+ const cycles = importCycles(graph);
1422
+ pass("import_cycles", `${cycles.count} cycle(s) detected`, performance.now() - t0);
1423
+ t0 = performance.now();
1424
+ const importer = findImporter(graph, testFile);
1425
+ if (importer && graph.files.has(testFile)) {
1426
+ const result = shortestPath(graph, importer, testFile);
1427
+ const ms = performance.now() - t0;
1428
+ if (result.path) {
1429
+ pass("shortest_path", `${result.hops} hops: ${result.path.map((p2) => rel(p2, projectRoot2)).join(" -> ")}`, ms);
1430
+ } else {
1431
+ pass("shortest_path", `No path from ${rel(importer, projectRoot2)} (may be type-only)`, ms);
1432
+ }
1433
+ } else {
1434
+ skip("shortest_path", "No importer found for test file");
1435
+ }
1436
+ t0 = performance.now();
1437
+ if (graph.files.has(testFile)) {
1438
+ const result = subgraph(graph, [testFile], { depth: 1, direction: "both" });
1439
+ pass(
1440
+ "subgraph",
1441
+ `${result.stats.nodeCount} nodes, ${result.stats.edgeCount} edges (depth 1)`,
1442
+ performance.now() - t0
1443
+ );
1444
+ } else {
1445
+ skip("subgraph", `${testFileRel} not in graph`);
1446
+ }
1447
+ t0 = performance.now();
1448
+ const dir = path6.dirname(testFile);
1449
+ const siblings = [...graph.files].filter((f) => path6.dirname(f) === dir);
1450
+ if (siblings.length >= 2) {
1451
+ const result = moduleBoundary(graph, siblings);
1452
+ pass(
1453
+ "module_boundary",
1454
+ `${siblings.length} files in ${rel(dir, projectRoot2)}/: ${result.internalEdges} internal, ${result.incomingEdges.length} in, ${result.outgoingEdges.length} out`,
1455
+ performance.now() - t0
1456
+ );
1457
+ } else {
1458
+ skip("module_boundary", `Only ${siblings.length} file(s) in ${rel(dir, projectRoot2)}/`);
1459
+ }
1460
+ console.log("");
1461
+ console.log("\u2500\u2500 tsserver \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1462
+ const client2 = new TsServerClient(projectRoot2, tsconfigPath2);
1463
+ t0 = performance.now();
1464
+ await client2.start();
1465
+ console.log(` (started in ${(performance.now() - t0).toFixed(0)}ms)`);
1466
+ t0 = performance.now();
1467
+ const bar = await client2.navbar(testFileRel);
1468
+ const navbarMs = performance.now() - t0;
1469
+ const symbolKinds = /* @__PURE__ */ new Set([
1470
+ "function",
1471
+ "const",
1472
+ "class",
1473
+ "interface",
1474
+ "type",
1475
+ "enum",
1476
+ "var",
1477
+ "let",
1478
+ "method"
1479
+ ]);
1480
+ const allSymbols = [];
1481
+ function collectSymbols(items) {
1482
+ for (const item of items) {
1483
+ if (symbolKinds.has(item.kind) && item.text !== "<function>" && item.spans.length > 0) {
1484
+ allSymbols.push(item);
1485
+ }
1486
+ if (item.childItems?.length > 0) collectSymbols(item.childItems);
1487
+ }
1488
+ }
1489
+ collectSymbols(bar);
1490
+ if (allSymbols.length > 0) {
1491
+ pass("navbar", `${allSymbols.length} symbols in ${testFileRel}`, navbarMs);
1492
+ } else {
1493
+ fail("navbar", `No symbols found in ${testFileRel}`, navbarMs);
1494
+ }
1495
+ const concreteKinds = /* @__PURE__ */ new Set(["const", "function", "class", "var", "let", "enum"]);
1496
+ const sym = allSymbols.find((s) => concreteKinds.has(s.kind)) ?? allSymbols[0];
1497
+ if (!sym) {
1498
+ const toolNames = [
1499
+ "find_symbol",
1500
+ "definition",
1501
+ "references",
1502
+ "type_info",
1503
+ "navigate_to",
1504
+ "blast_radius",
1505
+ "module_exports",
1506
+ "trace_chain"
1507
+ ];
1508
+ for (const name of toolNames) skip(name, "No symbol discovered");
1509
+ } else {
1510
+ const span = sym.spans[0];
1511
+ t0 = performance.now();
1512
+ const found = findInNavBar(bar, (item) => item.text === sym.text && item.kind === sym.kind);
1513
+ if (found && found.spans.length > 0) {
1514
+ pass(
1515
+ "find_symbol",
1516
+ `${sym.text} [${sym.kind}] at line ${found.spans[0].start.line}`,
1517
+ performance.now() - t0
1518
+ );
1519
+ } else {
1520
+ fail("find_symbol", `Could not re-find ${sym.text}`, performance.now() - t0);
1521
+ }
1522
+ t0 = performance.now();
1523
+ const defs = await client2.definition(testFileRel, span.start.line, span.start.offset);
1524
+ if (defs.length > 0) {
1525
+ const def = defs[0];
1526
+ pass("definition", `${sym.text} -> ${def.file}:${def.start.line}`, performance.now() - t0);
1527
+ } else {
1528
+ pass("definition", `${sym.text} is its own definition`, performance.now() - t0);
1529
+ }
1530
+ t0 = performance.now();
1531
+ const refs = await client2.references(testFileRel, span.start.line, span.start.offset);
1532
+ const refFiles = new Set(refs.map((r) => r.file));
1533
+ pass(
1534
+ "references",
1535
+ `${refs.length} ref(s) across ${refFiles.size} file(s)`,
1536
+ performance.now() - t0
1537
+ );
1538
+ t0 = performance.now();
1539
+ const info = await client2.quickinfo(testFileRel, span.start.line, span.start.offset);
1540
+ if (info) {
1541
+ const typeStr = info.displayString.length > 80 ? info.displayString.slice(0, 80) + "..." : info.displayString;
1542
+ pass("type_info", typeStr, performance.now() - t0);
1543
+ } else {
1544
+ fail("type_info", `No type info for ${sym.text}`, performance.now() - t0);
1545
+ }
1546
+ t0 = performance.now();
1547
+ const navItems = await client2.navto(sym.text, 5);
1548
+ if (navItems.length > 0) {
1549
+ const files = new Set(navItems.map((i) => i.file));
1550
+ pass(
1551
+ "navigate_to",
1552
+ `${navItems.length} match(es) for "${sym.text}" in ${files.size} file(s)`,
1553
+ performance.now() - t0
1554
+ );
1555
+ } else {
1556
+ pass(
1557
+ "navigate_to",
1558
+ `"${sym.text}" not indexed by navto (expected for some kinds)`,
1559
+ performance.now() - t0
1560
+ );
1561
+ }
1562
+ t0 = performance.now();
1563
+ const callers = refs.filter((r) => !r.isDefinition);
1564
+ const callerFiles = new Set(callers.map((r) => r.file));
1565
+ pass(
1566
+ "blast_radius",
1567
+ `${callers.length} usage(s) across ${callerFiles.size} file(s)`,
1568
+ performance.now() - t0
1569
+ );
1570
+ t0 = performance.now();
1571
+ const moduleItem = bar.find((item) => item.kind === "module");
1572
+ const topItems = moduleItem?.childItems ?? bar;
1573
+ const exportSymbols = topItems.filter((item) => symbolKinds.has(item.kind));
1574
+ pass("module_exports", `${exportSymbols.length} top-level symbol(s)`, performance.now() - t0);
1575
+ t0 = performance.now();
1576
+ const source = fs5.readFileSync(testFile, "utf-8");
1577
+ const importMatch = source.match(/^import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["']/m);
1578
+ if (importMatch) {
1579
+ const firstName = importMatch[1].split(",")[0].replace(/^type\s+/, "").trim();
1580
+ const importSym = findInNavBar(bar, (item) => item.text === firstName);
1581
+ if (importSym && importSym.spans.length > 0) {
1582
+ const chain = [testFileRel];
1583
+ let cur = {
1584
+ file: testFileRel,
1585
+ line: importSym.spans[0].start.line,
1586
+ offset: importSym.spans[0].start.offset
1587
+ };
1588
+ for (let i = 0; i < 5; i++) {
1589
+ const hopDefs = await client2.definition(cur.file, cur.line, cur.offset);
1590
+ if (hopDefs.length === 0) break;
1591
+ const hop = hopDefs[0];
1592
+ if (hop.file === cur.file && hop.start.line === cur.line) break;
1593
+ if (hop.file.includes("node_modules")) break;
1594
+ chain.push(`${hop.file}:${hop.start.line}`);
1595
+ cur = { file: hop.file, line: hop.start.line, offset: hop.start.offset };
1596
+ }
1597
+ if (chain.length > 1) {
1598
+ pass(
1599
+ "trace_chain",
1600
+ `${chain.length - 1} hop(s): ${chain.join(" -> ")}`,
1601
+ performance.now() - t0
1602
+ );
1603
+ } else {
1604
+ pass(
1605
+ "trace_chain",
1606
+ `"${firstName}" resolved in-file (0 external hops)`,
1607
+ performance.now() - t0
1608
+ );
1609
+ }
1610
+ } else {
1611
+ pass(
1612
+ "trace_chain",
1613
+ `"${firstName}" not in navbar (may be type-only)`,
1614
+ performance.now() - t0
1615
+ );
1616
+ }
1617
+ } else {
1618
+ skip("trace_chain", "No brace imports in test file");
1619
+ }
1620
+ }
1621
+ client2.shutdown();
1622
+ console.log("");
1623
+ const total = passed + failed;
1624
+ if (failed === 0) {
1625
+ console.log(
1626
+ `${passed}/${total} passed` + (skipped > 0 ? ` (${skipped} skipped)` : "") + " -- all tools working"
1627
+ );
1628
+ } else {
1629
+ console.log(
1630
+ `${passed}/${total} passed, ${failed} failed` + (skipped > 0 ? `, ${skipped} skipped` : "") + " -- some tools may not work correctly"
1631
+ );
1632
+ }
1633
+ console.log("");
1634
+ return { passed, failed, skipped };
1635
+ }
1636
+ var SKIP_DIRS2, isDirectRun2;
1637
+ var init_smoke_test = __esm({
1638
+ "smoke-test.ts"() {
1639
+ init_tsserver_client();
1640
+ init_module_graph();
1641
+ init_graph_queries();
1642
+ init_config();
1643
+ SKIP_DIRS2 = /* @__PURE__ */ new Set([
1644
+ "node_modules",
1645
+ "dist",
1646
+ "build",
1647
+ ".git",
1648
+ ".wrangler",
1649
+ "coverage",
1650
+ "out"
1651
+ ]);
1652
+ isDirectRun2 = process.argv[1] && fs5.realpathSync(process.argv[1]) === fs5.realpathSync(new URL(import.meta.url).pathname);
1653
+ if (isDirectRun2) {
1654
+ main2().then((result) => process.exit(result.failed > 0 ? 1 : 0)).catch((err) => {
1655
+ console.error("Fatal:", err);
1656
+ process.exit(1);
1657
+ });
1658
+ }
1659
+ }
1660
+ });
1661
+
1662
+ // server.ts
1663
+ var server_exports = {};
1664
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1665
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1666
+ import { z } from "zod";
1667
+ import * as fs6 from "fs";
1668
+ import * as path7 from "path";
1669
+ function readPreview(file, line) {
1670
+ try {
1671
+ const absPath2 = client.resolvePath(file);
1672
+ const content = fs6.readFileSync(absPath2, "utf-8");
1673
+ return content.split("\n")[line - 1]?.trim() ?? "";
1674
+ } catch {
1675
+ return "";
1676
+ }
1677
+ }
1678
+ function findInNavBar2(items, symbol) {
1679
+ for (const item of items) {
1680
+ if (item.text === symbol && item.spans.length > 0) {
1681
+ const span = item.spans[0];
1682
+ return { line: span.start.line, offset: span.start.offset, kind: item.kind };
1683
+ }
1684
+ if (item.childItems?.length > 0) {
1685
+ const found = findInNavBar2(item.childItems, symbol);
1686
+ if (found) return found;
1687
+ }
1688
+ }
1689
+ return null;
1690
+ }
1691
+ async function resolveSymbol(file, symbol) {
1692
+ const bar = await client.navbar(file);
1693
+ const found = findInNavBar2(bar, symbol);
1694
+ if (found) {
1695
+ return {
1696
+ file,
1697
+ line: found.line,
1698
+ column: found.offset,
1699
+ kind: found.kind,
1700
+ preview: readPreview(file, found.line)
1701
+ };
1702
+ }
1703
+ const items = await client.navto(symbol, 10, file);
1704
+ const inFile = items.find((i) => i.name === symbol && i.file === file);
1705
+ const best = inFile ?? items.find((i) => i.name === symbol) ?? items[0];
1706
+ if (best) {
1707
+ return {
1708
+ file: best.file,
1709
+ line: best.start.line,
1710
+ column: best.start.offset,
1711
+ kind: best.kind,
1712
+ preview: readPreview(best.file, best.start.line)
1713
+ };
1714
+ }
1715
+ return null;
1716
+ }
1717
+ async function resolveParams(params) {
1718
+ if (params.line !== void 0 && params.column !== void 0) {
1719
+ return { file: params.file, line: params.line, column: params.column };
1720
+ }
1721
+ if (params.symbol) {
1722
+ const resolved = await resolveSymbol(params.file, params.symbol);
1723
+ if (!resolved) {
1724
+ return { error: `Symbol "${params.symbol}" not found in ${params.file}` };
1725
+ }
1726
+ return { file: resolved.file, line: resolved.line, column: resolved.column };
1727
+ }
1728
+ return { error: "Either line+column or symbol must be provided" };
1729
+ }
1730
+ function relPath(absPath2) {
1731
+ return path7.relative(projectRoot, absPath2);
1732
+ }
1733
+ function absPath(file) {
1734
+ return path7.isAbsolute(file) ? file : path7.resolve(projectRoot, file);
1735
+ }
1736
+ async function main3() {
1737
+ log3("Starting TypeGraph MCP server...");
1738
+ log3(`Project root: ${projectRoot}`);
1739
+ log3(`tsconfig: ${tsconfigPath}`);
1740
+ const [, graphResult] = await Promise.all([
1741
+ client.start(),
1742
+ buildGraph(projectRoot, tsconfigPath)
1743
+ ]);
1744
+ moduleGraph = graphResult.graph;
1745
+ startWatcher(projectRoot, moduleGraph, graphResult.resolver);
1746
+ const transport = new StdioServerTransport();
1747
+ await mcpServer.connect(transport);
1748
+ log3("MCP server connected and ready");
1749
+ }
1750
+ var projectRoot, tsconfigPath, log3, client, moduleGraph, mcpServer, locationOrSymbol;
1751
+ var init_server = __esm({
1752
+ "server.ts"() {
1753
+ init_tsserver_client();
1754
+ init_module_graph();
1755
+ init_graph_queries();
1756
+ init_config();
1757
+ ({ projectRoot, tsconfigPath } = resolveConfig(import.meta.dirname));
1758
+ log3 = (...args2) => console.error("[typegraph]", ...args2);
1759
+ client = new TsServerClient(projectRoot, tsconfigPath);
1760
+ mcpServer = new McpServer({
1761
+ name: "typegraph",
1762
+ version: "1.0.0"
1763
+ });
1764
+ locationOrSymbol = {
1765
+ file: z.string().describe("File path (relative to project root or absolute)"),
1766
+ line: z.number().int().positive().optional().describe("Line number (1-based). Required if symbol is not provided."),
1767
+ column: z.number().int().positive().optional().describe("Column/offset (1-based). Required if symbol is not provided."),
1768
+ symbol: z.string().optional().describe("Symbol name to find. Alternative to line+column.")
1769
+ };
1770
+ mcpServer.tool(
1771
+ "ts_find_symbol",
1772
+ "Find a symbol's location in a file by name. Entry point for navigating without exact coordinates.",
1773
+ {
1774
+ file: z.string().describe("File to search in (relative or absolute path)"),
1775
+ symbol: z.string().describe("Symbol name to find")
1776
+ },
1777
+ async ({ file, symbol }) => {
1778
+ const result = await resolveSymbol(file, symbol);
1779
+ if (!result) {
1780
+ return {
1781
+ content: [
1782
+ {
1783
+ type: "text",
1784
+ text: JSON.stringify({ error: `Symbol "${symbol}" not found in ${file}` })
1785
+ }
1786
+ ]
1787
+ };
1788
+ }
1789
+ return {
1790
+ content: [{ type: "text", text: JSON.stringify(result) }]
1791
+ };
1792
+ }
1793
+ );
1794
+ mcpServer.tool(
1795
+ "ts_definition",
1796
+ "Go to definition. Resolves through imports, re-exports, barrel files, interfaces, generics. Provide either line+column coordinates or a symbol name.",
1797
+ locationOrSymbol,
1798
+ async (params) => {
1799
+ const loc = await resolveParams(params);
1800
+ if ("error" in loc) {
1801
+ return { content: [{ type: "text", text: JSON.stringify(loc) }] };
1802
+ }
1803
+ const defs = await client.definition(loc.file, loc.line, loc.column);
1804
+ if (defs.length === 0) {
1805
+ return {
1806
+ content: [
1807
+ {
1808
+ type: "text",
1809
+ text: JSON.stringify({ definitions: [], source: readPreview(loc.file, loc.line) })
1810
+ }
1811
+ ]
1812
+ };
1813
+ }
1814
+ const results = defs.map((d) => ({
1815
+ file: d.file,
1816
+ line: d.start.line,
1817
+ column: d.start.offset,
1818
+ preview: readPreview(d.file, d.start.line)
1819
+ }));
1820
+ return {
1821
+ content: [{ type: "text", text: JSON.stringify({ definitions: results }) }]
1822
+ };
1823
+ }
1824
+ );
1825
+ mcpServer.tool(
1826
+ "ts_references",
1827
+ "Find all references to a symbol. Returns semantic code references only (not string matches). Provide either line+column or symbol name.",
1828
+ locationOrSymbol,
1829
+ async (params) => {
1830
+ const loc = await resolveParams(params);
1831
+ if ("error" in loc) {
1832
+ return { content: [{ type: "text", text: JSON.stringify(loc) }] };
1833
+ }
1834
+ const refs = await client.references(loc.file, loc.line, loc.column);
1835
+ const results = refs.map((r) => ({
1836
+ file: r.file,
1837
+ line: r.start.line,
1838
+ column: r.start.offset,
1839
+ preview: r.lineText.trim(),
1840
+ isDefinition: r.isDefinition
1841
+ }));
1842
+ return {
1843
+ content: [
1844
+ {
1845
+ type: "text",
1846
+ text: JSON.stringify({ references: results, count: results.length })
1847
+ }
1848
+ ]
1849
+ };
1850
+ }
1851
+ );
1852
+ mcpServer.tool(
1853
+ "ts_type_info",
1854
+ "Get the TypeScript type and documentation for a symbol. Returns the same info you see when hovering in VS Code. Provide either line+column or symbol name.",
1855
+ locationOrSymbol,
1856
+ async (params) => {
1857
+ const loc = await resolveParams(params);
1858
+ if ("error" in loc) {
1859
+ return { content: [{ type: "text", text: JSON.stringify(loc) }] };
1860
+ }
1861
+ const info = await client.quickinfo(loc.file, loc.line, loc.column);
1862
+ if (!info) {
1863
+ return {
1864
+ content: [
1865
+ {
1866
+ type: "text",
1867
+ text: JSON.stringify({
1868
+ type: null,
1869
+ documentation: null,
1870
+ source: readPreview(loc.file, loc.line)
1871
+ })
1872
+ }
1873
+ ]
1874
+ };
1875
+ }
1876
+ return {
1877
+ content: [
1878
+ {
1879
+ type: "text",
1880
+ text: JSON.stringify({
1881
+ type: info.displayString,
1882
+ documentation: info.documentation || null,
1883
+ kind: info.kind
1884
+ })
1885
+ }
1886
+ ]
1887
+ };
1888
+ }
1889
+ );
1890
+ mcpServer.tool(
1891
+ "ts_navigate_to",
1892
+ "Search for a symbol across the entire project without knowing which file it's in. Returns matching declarations. Optionally provide a file hint to also search that file's navbar (useful for object literal keys like RPC handlers that navto doesn't index).",
1893
+ {
1894
+ symbol: z.string().describe("Symbol name to search for"),
1895
+ file: z.string().optional().describe(
1896
+ "Optional file to also search via navbar (covers object literal keys not indexed by navto)"
1897
+ ),
1898
+ maxResults: z.number().int().positive().optional().default(10).describe("Maximum results (default 10)")
1899
+ },
1900
+ async ({ symbol, file, maxResults }) => {
1901
+ const items = await client.navto(symbol, maxResults);
1902
+ const results = items.map((item) => ({
1903
+ file: item.file,
1904
+ line: item.start.line,
1905
+ column: item.start.offset,
1906
+ kind: item.kind,
1907
+ containerName: item.containerName,
1908
+ matchKind: item.matchKind
1909
+ }));
1910
+ if (file) {
1911
+ const navbarHit = await resolveSymbol(file, symbol);
1912
+ if (navbarHit) {
1913
+ const alreadyFound = results.some(
1914
+ (r) => r.file === navbarHit.file && r.line === navbarHit.line
1915
+ );
1916
+ if (!alreadyFound) {
1917
+ results.unshift({
1918
+ file: navbarHit.file,
1919
+ line: navbarHit.line,
1920
+ column: navbarHit.column,
1921
+ kind: navbarHit.kind,
1922
+ containerName: "",
1923
+ matchKind: "navbar"
1924
+ });
1925
+ }
1926
+ }
1927
+ }
1928
+ return {
1929
+ content: [
1930
+ {
1931
+ type: "text",
1932
+ text: JSON.stringify({ results, count: results.length })
1933
+ }
1934
+ ]
1935
+ };
1936
+ }
1937
+ );
1938
+ mcpServer.tool(
1939
+ "ts_trace_chain",
1940
+ "Automatically follow go-to-definition hops from a symbol, building a call chain from entry point to implementation. Stops when it reaches the bottom or a cycle.",
1941
+ {
1942
+ file: z.string().describe("Starting file"),
1943
+ symbol: z.string().describe("Starting symbol name"),
1944
+ maxHops: z.number().int().positive().optional().default(5).describe("Maximum hops to follow (default 5)")
1945
+ },
1946
+ async ({ file, symbol, maxHops }) => {
1947
+ const start2 = await resolveSymbol(file, symbol);
1948
+ if (!start2) {
1949
+ return {
1950
+ content: [
1951
+ {
1952
+ type: "text",
1953
+ text: JSON.stringify({ error: `Symbol "${symbol}" not found in ${file}` })
1954
+ }
1955
+ ]
1956
+ };
1957
+ }
1958
+ const chain = [
1959
+ {
1960
+ file: start2.file,
1961
+ line: start2.line,
1962
+ column: start2.column,
1963
+ preview: start2.preview
1964
+ }
1965
+ ];
1966
+ let current = { file: start2.file, line: start2.line, offset: start2.column };
1967
+ for (let i = 0; i < maxHops; i++) {
1968
+ const defs = await client.definition(current.file, current.line, current.offset);
1969
+ if (defs.length === 0) break;
1970
+ const def = defs[0];
1971
+ if (def.file === current.file && def.start.line === current.line) break;
1972
+ if (def.file.includes("node_modules")) break;
1973
+ const preview = readPreview(def.file, def.start.line);
1974
+ chain.push({
1975
+ file: def.file,
1976
+ line: def.start.line,
1977
+ column: def.start.offset,
1978
+ preview
1979
+ });
1980
+ current = { file: def.file, line: def.start.line, offset: def.start.offset };
1981
+ }
1982
+ return {
1983
+ content: [
1984
+ {
1985
+ type: "text",
1986
+ text: JSON.stringify({ chain, hops: chain.length - 1 })
1987
+ }
1988
+ ]
1989
+ };
1990
+ }
1991
+ );
1992
+ mcpServer.tool(
1993
+ "ts_blast_radius",
1994
+ "Analyze the impact of changing a symbol. Finds all references, filters to usage sites, and reports affected files.",
1995
+ {
1996
+ file: z.string().describe("File containing the symbol"),
1997
+ symbol: z.string().describe("Symbol to analyze")
1998
+ },
1999
+ async ({ file, symbol }) => {
2000
+ const start2 = await resolveSymbol(file, symbol);
2001
+ if (!start2) {
2002
+ return {
2003
+ content: [
2004
+ {
2005
+ type: "text",
2006
+ text: JSON.stringify({ error: `Symbol "${symbol}" not found in ${file}` })
2007
+ }
2008
+ ]
2009
+ };
2010
+ }
2011
+ const refs = await client.references(start2.file, start2.line, start2.column);
2012
+ const callers = refs.filter((r) => !r.isDefinition);
2013
+ const filesAffected = [...new Set(callers.map((r) => r.file))];
2014
+ const callerList = callers.map((r) => ({
2015
+ file: r.file,
2016
+ line: r.start.line,
2017
+ preview: r.lineText.trim()
2018
+ }));
2019
+ return {
2020
+ content: [
2021
+ {
2022
+ type: "text",
2023
+ text: JSON.stringify({
2024
+ directCallers: callers.length,
2025
+ filesAffected,
2026
+ callers: callerList
2027
+ })
2028
+ }
2029
+ ]
2030
+ };
2031
+ }
2032
+ );
2033
+ mcpServer.tool(
2034
+ "ts_module_exports",
2035
+ "List all exported symbols from a module with their resolved types. Gives an at-a-glance understanding of what a file provides.",
2036
+ {
2037
+ file: z.string().describe("File to inspect")
2038
+ },
2039
+ async ({ file }) => {
2040
+ const bar = await client.navbar(file);
2041
+ if (bar.length === 0) {
2042
+ return {
2043
+ content: [
2044
+ {
2045
+ type: "text",
2046
+ text: JSON.stringify({ error: `No symbols found in ${file}` })
2047
+ }
2048
+ ]
2049
+ };
2050
+ }
2051
+ const moduleItem = bar.find((item) => item.kind === "module");
2052
+ const topItems = moduleItem?.childItems ?? bar;
2053
+ const exportKinds = /* @__PURE__ */ new Set([
2054
+ "function",
2055
+ "const",
2056
+ "class",
2057
+ "interface",
2058
+ "type",
2059
+ "enum",
2060
+ "var",
2061
+ "let",
2062
+ "method"
2063
+ ]);
2064
+ const candidates = topItems.filter((item) => exportKinds.has(item.kind));
2065
+ const exports = [];
2066
+ for (const item of candidates) {
2067
+ if (!item.spans[0]) continue;
2068
+ const span = item.spans[0];
2069
+ const info = await client.quickinfo(file, span.start.line, span.start.offset);
2070
+ exports.push({
2071
+ symbol: item.text,
2072
+ kind: item.kind,
2073
+ line: span.start.line,
2074
+ type: info?.displayString ?? null
2075
+ });
2076
+ }
2077
+ return {
2078
+ content: [
2079
+ {
2080
+ type: "text",
2081
+ text: JSON.stringify({ file, exports, count: exports.length })
2082
+ }
2083
+ ]
2084
+ };
2085
+ }
2086
+ );
2087
+ mcpServer.tool(
2088
+ "ts_dependency_tree",
2089
+ "Get the transitive dependency tree (imports) of a file. Shows what a file depends on, directly and transitively.",
2090
+ {
2091
+ file: z.string().describe("File to analyze (relative or absolute path)"),
2092
+ depth: z.number().int().positive().optional().describe("Max traversal depth (default: unlimited)"),
2093
+ includeTypeOnly: z.boolean().optional().default(false).describe("Include type-only imports (default: false)")
2094
+ },
2095
+ async ({ file, depth, includeTypeOnly }) => {
2096
+ const result = dependencyTree(moduleGraph, absPath(file), { depth, includeTypeOnly });
2097
+ return {
2098
+ content: [
2099
+ {
2100
+ type: "text",
2101
+ text: JSON.stringify({
2102
+ root: relPath(result.root),
2103
+ nodes: result.nodes,
2104
+ files: result.files.map(relPath)
2105
+ })
2106
+ }
2107
+ ]
2108
+ };
2109
+ }
2110
+ );
2111
+ mcpServer.tool(
2112
+ "ts_dependents",
2113
+ "Find all files that depend on (import) a given file, directly and transitively. Groups results by package.",
2114
+ {
2115
+ file: z.string().describe("File to analyze (relative or absolute path)"),
2116
+ depth: z.number().int().positive().optional().describe("Max traversal depth (default: unlimited)"),
2117
+ includeTypeOnly: z.boolean().optional().default(false).describe("Include type-only imports (default: false)")
2118
+ },
2119
+ async ({ file, depth, includeTypeOnly }) => {
2120
+ const result = dependents(moduleGraph, absPath(file), { depth, includeTypeOnly });
2121
+ const byPackageRel = {};
2122
+ for (const [pkg, files] of Object.entries(result.byPackage)) {
2123
+ byPackageRel[pkg] = files.map(relPath);
2124
+ }
2125
+ return {
2126
+ content: [
2127
+ {
2128
+ type: "text",
2129
+ text: JSON.stringify({
2130
+ root: relPath(result.root),
2131
+ nodes: result.nodes,
2132
+ directCount: result.directCount,
2133
+ files: result.files.map(relPath),
2134
+ byPackage: byPackageRel
2135
+ })
2136
+ }
2137
+ ]
2138
+ };
2139
+ }
2140
+ );
2141
+ mcpServer.tool(
2142
+ "ts_import_cycles",
2143
+ "Detect circular import dependencies in the project. Returns strongly connected components (cycles) in the import graph.",
2144
+ {
2145
+ file: z.string().optional().describe("Filter to cycles containing this file"),
2146
+ package: z.string().optional().describe("Filter to cycles within this directory")
2147
+ },
2148
+ async ({ file, package: pkg }) => {
2149
+ const result = importCycles(moduleGraph, {
2150
+ file: file ? absPath(file) : void 0,
2151
+ package: pkg ? absPath(pkg) : void 0
2152
+ });
2153
+ return {
2154
+ content: [
2155
+ {
2156
+ type: "text",
2157
+ text: JSON.stringify({
2158
+ count: result.count,
2159
+ cycles: result.cycles.map((cycle) => cycle.map(relPath))
2160
+ })
2161
+ }
2162
+ ]
2163
+ };
2164
+ }
2165
+ );
2166
+ mcpServer.tool(
2167
+ "ts_shortest_path",
2168
+ "Find the shortest import path between two files. Shows how one module reaches another through the import graph.",
2169
+ {
2170
+ from: z.string().describe("Source file (relative or absolute path)"),
2171
+ to: z.string().describe("Target file (relative or absolute path)"),
2172
+ includeTypeOnly: z.boolean().optional().default(false).describe("Include type-only imports (default: false)")
2173
+ },
2174
+ async ({ from, to, includeTypeOnly }) => {
2175
+ const result = shortestPath(moduleGraph, absPath(from), absPath(to), { includeTypeOnly });
2176
+ return {
2177
+ content: [
2178
+ {
2179
+ type: "text",
2180
+ text: JSON.stringify({
2181
+ path: result.path?.map(relPath) ?? null,
2182
+ hops: result.hops,
2183
+ chain: result.chain.map((c) => ({
2184
+ file: relPath(c.file),
2185
+ imports: c.imports
2186
+ }))
2187
+ })
2188
+ }
2189
+ ]
2190
+ };
2191
+ }
2192
+ );
2193
+ mcpServer.tool(
2194
+ "ts_subgraph",
2195
+ "Extract a subgraph around seed files. Expands by depth hops in the specified direction (imports, dependents, or both).",
2196
+ {
2197
+ files: z.array(z.string()).describe("Seed files to expand from (relative or absolute paths)"),
2198
+ depth: z.number().int().positive().optional().default(1).describe("Hops to expand (default: 1)"),
2199
+ direction: z.enum(["imports", "dependents", "both"]).optional().default("both").describe("Direction to expand (default: both)")
2200
+ },
2201
+ async ({ files, depth, direction }) => {
2202
+ const result = subgraph(moduleGraph, files.map(absPath), { depth, direction });
2203
+ return {
2204
+ content: [
2205
+ {
2206
+ type: "text",
2207
+ text: JSON.stringify({
2208
+ nodes: result.nodes.map(relPath),
2209
+ edges: result.edges.map((e) => ({
2210
+ from: relPath(e.from),
2211
+ to: relPath(e.to),
2212
+ specifiers: e.specifiers,
2213
+ isTypeOnly: e.isTypeOnly
2214
+ })),
2215
+ stats: result.stats
2216
+ })
2217
+ }
2218
+ ]
2219
+ };
2220
+ }
2221
+ );
2222
+ mcpServer.tool(
2223
+ "ts_module_boundary",
2224
+ "Analyze the boundary of a set of files: incoming/outgoing edges, shared dependencies, and an isolation score. Useful for understanding module coupling.",
2225
+ {
2226
+ files: z.array(z.string()).describe("Files defining the module boundary (relative or absolute paths)")
2227
+ },
2228
+ async ({ files }) => {
2229
+ const result = moduleBoundary(moduleGraph, files.map(absPath));
2230
+ return {
2231
+ content: [
2232
+ {
2233
+ type: "text",
2234
+ text: JSON.stringify({
2235
+ internalEdges: result.internalEdges,
2236
+ incomingEdges: result.incomingEdges.map((e) => ({
2237
+ from: relPath(e.from),
2238
+ to: relPath(e.to),
2239
+ specifiers: e.specifiers
2240
+ })),
2241
+ outgoingEdges: result.outgoingEdges.map((e) => ({
2242
+ from: relPath(e.from),
2243
+ to: relPath(e.to),
2244
+ specifiers: e.specifiers
2245
+ })),
2246
+ sharedDependencies: result.sharedDependencies.map(relPath),
2247
+ isolationScore: Math.round(result.isolationScore * 1e3) / 1e3
2248
+ })
2249
+ }
2250
+ ]
2251
+ };
2252
+ }
2253
+ );
2254
+ process.on("SIGINT", () => {
2255
+ log3("Shutting down...");
2256
+ client.shutdown();
2257
+ process.exit(0);
2258
+ });
2259
+ process.on("SIGTERM", () => {
2260
+ client.shutdown();
2261
+ process.exit(0);
2262
+ });
2263
+ main3().catch((err) => {
2264
+ log3("Fatal error:", err);
2265
+ process.exit(1);
2266
+ });
2267
+ }
2268
+ });
2269
+
2270
+ // cli.ts
2271
+ init_config();
2272
+ import * as fs7 from "fs";
2273
+ import * as path8 from "path";
2274
+ import { execSync } from "child_process";
2275
+ import * as p from "@clack/prompts";
2276
+ var AGENT_SNIPPET = `
2277
+ ## TypeScript Navigation (typegraph-mcp)
2278
+
2279
+ Use the \`ts_*\` MCP tools instead of grep/glob for navigating TypeScript code. They resolve through barrel files, re-exports, and project references \u2014 returning precise results, not string matches.
2280
+
2281
+ - **Point queries** (tsserver): \`ts_find_symbol\`, \`ts_definition\`, \`ts_references\`, \`ts_type_info\`, \`ts_navigate_to\`, \`ts_trace_chain\`, \`ts_blast_radius\`, \`ts_module_exports\`
2282
+ - **Graph queries** (import graph): \`ts_dependency_tree\`, \`ts_dependents\`, \`ts_import_cycles\`, \`ts_shortest_path\`, \`ts_subgraph\`, \`ts_module_boundary\`
2283
+ `.trimStart();
2284
+ var SNIPPET_MARKER = "## TypeScript Navigation (typegraph-mcp)";
2285
+ var PLUGIN_DIR_NAME = "plugins/typegraph-mcp";
2286
+ var AGENT_IDS = ["claude-code", "cursor", "codex", "gemini", "copilot"];
2287
+ var AGENTS = {
2288
+ "claude-code": {
2289
+ name: "Claude Code",
2290
+ pluginFiles: [
2291
+ ".claude-plugin/plugin.json",
2292
+ ".mcp.json",
2293
+ "hooks/hooks.json",
2294
+ "scripts/ensure-deps.sh",
2295
+ "commands/check.md",
2296
+ "commands/test.md"
2297
+ ],
2298
+ agentFile: "CLAUDE.md",
2299
+ needsAgentsSkills: false,
2300
+ detect: (root) => fs7.existsSync(path8.join(root, "CLAUDE.md")) || fs7.existsSync(path8.join(root, ".claude"))
2301
+ },
2302
+ cursor: {
2303
+ name: "Cursor",
2304
+ pluginFiles: [".cursor-plugin/plugin.json"],
2305
+ agentFile: null,
2306
+ needsAgentsSkills: false,
2307
+ detect: (root) => fs7.existsSync(path8.join(root, ".cursor"))
2308
+ },
2309
+ codex: {
2310
+ name: "Codex CLI",
2311
+ pluginFiles: [],
2312
+ agentFile: "AGENTS.md",
2313
+ needsAgentsSkills: true,
2314
+ detect: (root) => fs7.existsSync(path8.join(root, "AGENTS.md"))
2315
+ },
2316
+ gemini: {
2317
+ name: "Gemini CLI",
2318
+ pluginFiles: ["gemini-extension.json"],
2319
+ agentFile: "GEMINI.md",
2320
+ needsAgentsSkills: true,
2321
+ detect: (root) => fs7.existsSync(path8.join(root, "GEMINI.md"))
2322
+ },
2323
+ copilot: {
2324
+ name: "GitHub Copilot",
2325
+ pluginFiles: [],
2326
+ agentFile: ".github/copilot-instructions.md",
2327
+ needsAgentsSkills: true,
2328
+ detect: (root) => fs7.existsSync(path8.join(root, ".github/copilot-instructions.md"))
2329
+ }
2330
+ };
2331
+ var CORE_FILES = [
2332
+ "server.ts",
2333
+ "module-graph.ts",
2334
+ "tsserver-client.ts",
2335
+ "graph-queries.ts",
2336
+ "config.ts",
2337
+ "check.ts",
2338
+ "smoke-test.ts",
2339
+ "cli.ts",
2340
+ "package.json",
2341
+ "pnpm-lock.yaml"
2342
+ ];
2343
+ var SKILL_FILES = [
2344
+ "skills/tool-selection/SKILL.md",
2345
+ "skills/impact-analysis/SKILL.md",
2346
+ "skills/refactor-safety/SKILL.md",
2347
+ "skills/dependency-audit/SKILL.md",
2348
+ "skills/code-exploration/SKILL.md"
2349
+ ];
2350
+ var SKILL_NAMES = [
2351
+ "tool-selection",
2352
+ "impact-analysis",
2353
+ "refactor-safety",
2354
+ "dependency-audit",
2355
+ "code-exploration"
2356
+ ];
2357
+ var HELP = `
2358
+ typegraph-mcp \u2014 Type-aware codebase navigation for AI coding agents.
2359
+
2360
+ Usage: typegraph-mcp <command> [options]
2361
+
2362
+ Commands:
2363
+ setup Install typegraph-mcp plugin into the current project
2364
+ remove Uninstall typegraph-mcp from the current project
2365
+ check Run health checks (12 checks)
2366
+ test Run smoke tests (all 14 tools)
2367
+ start Start the MCP server (stdin/stdout)
2368
+
2369
+ Options:
2370
+ --yes Skip confirmation prompts (accept all defaults)
2371
+ --help Show this help
2372
+ `.trim();
2373
+ function copyFile(src, dest) {
2374
+ const destDir = path8.dirname(dest);
2375
+ if (!fs7.existsSync(destDir)) {
2376
+ fs7.mkdirSync(destDir, { recursive: true });
2377
+ }
2378
+ fs7.copyFileSync(src, dest);
2379
+ if (src.endsWith(".sh")) {
2380
+ fs7.chmodSync(dest, 493);
2381
+ }
2382
+ }
2383
+ var MCP_SERVER_ENTRY = {
2384
+ command: "npx",
2385
+ args: ["tsx", "./plugins/typegraph-mcp/server.ts"],
2386
+ env: {
2387
+ TYPEGRAPH_PROJECT_ROOT: ".",
2388
+ TYPEGRAPH_TSCONFIG: "./tsconfig.json"
2389
+ }
2390
+ };
2391
+ function registerMcpServers(projectRoot2, selectedAgents) {
2392
+ if (selectedAgents.includes("cursor")) {
2393
+ registerJsonMcp(projectRoot2, ".cursor/mcp.json", "mcpServers");
2394
+ }
2395
+ if (selectedAgents.includes("codex")) {
2396
+ registerCodexMcp(projectRoot2);
2397
+ }
2398
+ if (selectedAgents.includes("copilot")) {
2399
+ registerJsonMcp(projectRoot2, ".vscode/mcp.json", "servers");
2400
+ }
2401
+ }
2402
+ function deregisterMcpServers(projectRoot2) {
2403
+ deregisterJsonMcp(projectRoot2, ".cursor/mcp.json", "mcpServers");
2404
+ deregisterCodexMcp(projectRoot2);
2405
+ deregisterJsonMcp(projectRoot2, ".vscode/mcp.json", "servers");
2406
+ }
2407
+ function registerJsonMcp(projectRoot2, configPath, rootKey) {
2408
+ const fullPath = path8.resolve(projectRoot2, configPath);
2409
+ let config = {};
2410
+ if (fs7.existsSync(fullPath)) {
2411
+ try {
2412
+ config = JSON.parse(fs7.readFileSync(fullPath, "utf-8"));
2413
+ } catch {
2414
+ p.log.warn(`Could not parse ${configPath} \u2014 skipping MCP registration`);
2415
+ return;
2416
+ }
2417
+ }
2418
+ const servers = config[rootKey] ?? {};
2419
+ const entry = { ...MCP_SERVER_ENTRY };
2420
+ if (rootKey === "servers") {
2421
+ entry.type = "stdio";
2422
+ }
2423
+ servers["typegraph"] = entry;
2424
+ config[rootKey] = servers;
2425
+ const dir = path8.dirname(fullPath);
2426
+ if (!fs7.existsSync(dir)) {
2427
+ fs7.mkdirSync(dir, { recursive: true });
2428
+ }
2429
+ fs7.writeFileSync(fullPath, JSON.stringify(config, null, 2) + "\n");
2430
+ p.log.success(`${configPath}: registered typegraph MCP server`);
2431
+ }
2432
+ function deregisterJsonMcp(projectRoot2, configPath, rootKey) {
2433
+ const fullPath = path8.resolve(projectRoot2, configPath);
2434
+ if (!fs7.existsSync(fullPath)) return;
2435
+ try {
2436
+ const config = JSON.parse(fs7.readFileSync(fullPath, "utf-8"));
2437
+ const servers = config[rootKey];
2438
+ if (!servers || !servers["typegraph"]) return;
2439
+ delete servers["typegraph"];
2440
+ if (Object.keys(servers).length === 0) {
2441
+ delete config[rootKey];
2442
+ }
2443
+ if (Object.keys(config).length === 0) {
2444
+ fs7.unlinkSync(fullPath);
2445
+ } else {
2446
+ fs7.writeFileSync(fullPath, JSON.stringify(config, null, 2) + "\n");
2447
+ }
2448
+ p.log.info(`${configPath}: removed typegraph MCP server`);
2449
+ } catch {
2450
+ }
2451
+ }
2452
+ function registerCodexMcp(projectRoot2) {
2453
+ const configPath = ".codex/config.toml";
2454
+ const fullPath = path8.resolve(projectRoot2, configPath);
2455
+ let content = "";
2456
+ if (fs7.existsSync(fullPath)) {
2457
+ content = fs7.readFileSync(fullPath, "utf-8");
2458
+ if (content.includes("[mcp_servers.typegraph]")) {
2459
+ p.log.info(`${configPath}: typegraph MCP server already registered`);
2460
+ return;
2461
+ }
2462
+ }
2463
+ const block = [
2464
+ "",
2465
+ "[mcp_servers.typegraph]",
2466
+ 'command = "npx"',
2467
+ 'args = ["tsx", "./plugins/typegraph-mcp/server.ts"]',
2468
+ 'env = { TYPEGRAPH_PROJECT_ROOT = ".", TYPEGRAPH_TSCONFIG = "./tsconfig.json" }',
2469
+ ""
2470
+ ].join("\n");
2471
+ const dir = path8.dirname(fullPath);
2472
+ if (!fs7.existsSync(dir)) {
2473
+ fs7.mkdirSync(dir, { recursive: true });
2474
+ }
2475
+ const newContent = content ? content.trimEnd() + "\n" + block : block.trimStart();
2476
+ fs7.writeFileSync(fullPath, newContent);
2477
+ p.log.success(`${configPath}: registered typegraph MCP server`);
2478
+ }
2479
+ function deregisterCodexMcp(projectRoot2) {
2480
+ const configPath = ".codex/config.toml";
2481
+ const fullPath = path8.resolve(projectRoot2, configPath);
2482
+ if (!fs7.existsSync(fullPath)) return;
2483
+ let content = fs7.readFileSync(fullPath, "utf-8");
2484
+ if (!content.includes("[mcp_servers.typegraph]")) return;
2485
+ content = content.replace(
2486
+ /\n?\[mcp_servers\.typegraph\]\n[\s\S]*?(?=\n\[|$)/,
2487
+ ""
2488
+ );
2489
+ content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
2490
+ if (content.trim() === "") {
2491
+ fs7.unlinkSync(fullPath);
2492
+ } else {
2493
+ fs7.writeFileSync(fullPath, content);
2494
+ }
2495
+ p.log.info(`${configPath}: removed typegraph MCP server`);
2496
+ }
2497
+ function detectAgents(projectRoot2) {
2498
+ return AGENT_IDS.filter((id) => AGENTS[id].detect(projectRoot2));
2499
+ }
2500
+ async function selectAgents(projectRoot2, yes2) {
2501
+ const detected = detectAgents(projectRoot2);
2502
+ if (yes2) {
2503
+ const selected2 = detected.length > 0 ? detected : [...AGENT_IDS];
2504
+ p.log.info(`Auto-selected: ${selected2.map((id) => AGENTS[id].name).join(", ")}`);
2505
+ return selected2;
2506
+ }
2507
+ p.log.info("space = toggle | up/down = navigate | enter = confirm");
2508
+ const result = await p.multiselect({
2509
+ message: "Select which AI agents to configure:",
2510
+ options: AGENT_IDS.map((id) => ({
2511
+ value: id,
2512
+ label: AGENTS[id].name,
2513
+ hint: detected.includes(id) ? "detected" : void 0
2514
+ })),
2515
+ initialValues: detected.length > 0 ? detected : [...AGENT_IDS],
2516
+ required: false
2517
+ });
2518
+ if (p.isCancel(result)) {
2519
+ p.cancel("Setup cancelled.");
2520
+ process.exit(0);
2521
+ }
2522
+ const selected = result.length > 0 ? result : detected.length > 0 ? detected : [...AGENT_IDS];
2523
+ p.log.info(`Selected: ${selected.map((id) => AGENTS[id].name).join(", ")}`);
2524
+ return selected;
2525
+ }
2526
+ async function setup(yes2) {
2527
+ const sourceDir = path8.basename(import.meta.dirname) === "dist" ? path8.resolve(import.meta.dirname, "..") : import.meta.dirname;
2528
+ const projectRoot2 = process.cwd();
2529
+ process.stdout.write("\x1Bc");
2530
+ p.intro("TypeGraph MCP Setup");
2531
+ p.log.info(`Project: ${projectRoot2}`);
2532
+ const pkgJsonPath = path8.resolve(projectRoot2, "package.json");
2533
+ const tsconfigPath2 = path8.resolve(projectRoot2, "tsconfig.json");
2534
+ if (!fs7.existsSync(pkgJsonPath)) {
2535
+ p.cancel("No package.json found. Run this from the root of your TypeScript project.");
2536
+ process.exit(1);
2537
+ }
2538
+ if (!fs7.existsSync(tsconfigPath2)) {
2539
+ p.cancel("No tsconfig.json found. typegraph-mcp requires a TypeScript project.");
2540
+ process.exit(1);
2541
+ }
2542
+ p.log.success("Found package.json and tsconfig.json");
2543
+ const targetDir = path8.resolve(projectRoot2, PLUGIN_DIR_NAME);
2544
+ const isUpdate = fs7.existsSync(targetDir);
2545
+ if (isUpdate && !yes2) {
2546
+ const action = await p.select({
2547
+ message: `${PLUGIN_DIR_NAME}/ already exists.`,
2548
+ options: [
2549
+ { value: "update", label: "Update", hint: "reinstall plugin files" },
2550
+ { value: "remove", label: "Remove", hint: "uninstall typegraph-mcp from this project" },
2551
+ { value: "exit", label: "Exit", hint: "keep existing installation" }
2552
+ ]
2553
+ });
2554
+ if (p.isCancel(action)) {
2555
+ p.cancel("Setup cancelled.");
2556
+ process.exit(0);
2557
+ }
2558
+ if (action === "remove") {
2559
+ await removePlugin(projectRoot2, targetDir);
2560
+ return;
2561
+ }
2562
+ if (action === "exit") {
2563
+ p.outro("No changes made.");
2564
+ return;
2565
+ }
2566
+ }
2567
+ const selectedAgents = await selectAgents(projectRoot2, yes2);
2568
+ const needsPluginSkills = selectedAgents.includes("claude-code") || selectedAgents.includes("cursor");
2569
+ const needsAgentsSkills = selectedAgents.some((id) => AGENTS[id].needsAgentsSkills);
2570
+ p.log.step(`Installing to ${PLUGIN_DIR_NAME}/...`);
2571
+ const s = p.spinner();
2572
+ s.start("Copying files...");
2573
+ const filesToCopy = [...CORE_FILES];
2574
+ if (needsPluginSkills || needsAgentsSkills) {
2575
+ filesToCopy.push(...SKILL_FILES);
2576
+ }
2577
+ for (const agentId of selectedAgents) {
2578
+ filesToCopy.push(...AGENTS[agentId].pluginFiles);
2579
+ }
2580
+ let copied = 0;
2581
+ for (const file of filesToCopy) {
2582
+ const src = path8.join(sourceDir, file);
2583
+ const dest = path8.join(targetDir, file);
2584
+ if (fs7.existsSync(src)) {
2585
+ copyFile(src, dest);
2586
+ copied++;
2587
+ } else {
2588
+ p.log.warn(`Source file not found: ${file}`);
2589
+ }
2590
+ }
2591
+ s.message("Installing dependencies...");
2592
+ try {
2593
+ execSync("npm install", { cwd: targetDir, stdio: "pipe" });
2594
+ s.stop(`${isUpdate ? "Updated" : "Installed"} ${copied} files with dependencies`);
2595
+ } catch (err) {
2596
+ s.stop(`${isUpdate ? "Updated" : "Installed"} ${copied} files`);
2597
+ p.log.warn(`Dependency install failed: ${err instanceof Error ? err.message : String(err)}`);
2598
+ p.log.info(`Run manually: cd ${PLUGIN_DIR_NAME} && npm install`);
2599
+ }
2600
+ if (needsAgentsSkills) {
2601
+ const agentsNames = selectedAgents.filter((id) => AGENTS[id].needsAgentsSkills).map((id) => AGENTS[id].name);
2602
+ const agentsSkillsDir = path8.resolve(projectRoot2, ".agents/skills");
2603
+ let copiedSkills = 0;
2604
+ for (const skill of SKILL_NAMES) {
2605
+ const src = path8.join(targetDir, "skills", skill, "SKILL.md");
2606
+ const destDir = path8.join(agentsSkillsDir, skill);
2607
+ const dest = path8.join(destDir, "SKILL.md");
2608
+ if (!fs7.existsSync(src)) continue;
2609
+ if (fs7.existsSync(dest)) {
2610
+ const srcContent = fs7.readFileSync(src, "utf-8");
2611
+ const destContent = fs7.readFileSync(dest, "utf-8");
2612
+ if (srcContent === destContent) continue;
2613
+ }
2614
+ fs7.mkdirSync(destDir, { recursive: true });
2615
+ fs7.copyFileSync(src, dest);
2616
+ copiedSkills++;
2617
+ }
2618
+ if (copiedSkills > 0) {
2619
+ p.log.success(`Copied ${copiedSkills} skills to .agents/skills/ (${agentsNames.join(", ")})`);
2620
+ } else {
2621
+ p.log.info(".agents/skills/ already up to date");
2622
+ }
2623
+ }
2624
+ if (selectedAgents.includes("claude-code")) {
2625
+ const mcpJsonPath = path8.resolve(projectRoot2, ".claude/mcp.json");
2626
+ if (fs7.existsSync(mcpJsonPath)) {
2627
+ try {
2628
+ const mcpJson = JSON.parse(fs7.readFileSync(mcpJsonPath, "utf-8"));
2629
+ if (mcpJson.mcpServers?.["typegraph"]) {
2630
+ delete mcpJson.mcpServers["typegraph"];
2631
+ fs7.writeFileSync(mcpJsonPath, JSON.stringify(mcpJson, null, 2) + "\n");
2632
+ p.log.info("Removed old typegraph entry from .claude/mcp.json");
2633
+ }
2634
+ } catch {
2635
+ }
2636
+ }
2637
+ }
2638
+ await setupAgentInstructions(projectRoot2, selectedAgents);
2639
+ registerMcpServers(projectRoot2, selectedAgents);
2640
+ await runVerification(targetDir, selectedAgents);
2641
+ }
2642
+ async function removePlugin(projectRoot2, pluginDir) {
2643
+ const s = p.spinner();
2644
+ s.start("Removing typegraph-mcp...");
2645
+ if (fs7.existsSync(pluginDir)) {
2646
+ fs7.rmSync(pluginDir, { recursive: true });
2647
+ }
2648
+ const agentsSkillsDir = path8.resolve(projectRoot2, ".agents/skills");
2649
+ for (const skill of SKILL_NAMES) {
2650
+ const skillDir = path8.join(agentsSkillsDir, skill);
2651
+ if (fs7.existsSync(skillDir)) {
2652
+ fs7.rmSync(skillDir, { recursive: true });
2653
+ }
2654
+ }
2655
+ if (fs7.existsSync(agentsSkillsDir) && fs7.readdirSync(agentsSkillsDir).length === 0) {
2656
+ fs7.rmSync(agentsSkillsDir, { recursive: true });
2657
+ const agentsDir = path8.resolve(projectRoot2, ".agents");
2658
+ if (fs7.existsSync(agentsDir) && fs7.readdirSync(agentsDir).length === 0) {
2659
+ fs7.rmSync(agentsDir, { recursive: true });
2660
+ }
2661
+ }
2662
+ const allAgentFiles = AGENT_IDS.map((id) => AGENTS[id].agentFile).filter((f) => f !== null);
2663
+ const seenRealPaths = /* @__PURE__ */ new Set();
2664
+ for (const agentFile of allAgentFiles) {
2665
+ const filePath = path8.resolve(projectRoot2, agentFile);
2666
+ if (!fs7.existsSync(filePath)) continue;
2667
+ const realPath = fs7.realpathSync(filePath);
2668
+ if (seenRealPaths.has(realPath)) continue;
2669
+ seenRealPaths.add(realPath);
2670
+ let content = fs7.readFileSync(realPath, "utf-8");
2671
+ if (content.includes(SNIPPET_MARKER)) {
2672
+ content = content.replace(/\n?## TypeScript Navigation \(typegraph-mcp\)\n[\s\S]*?(?=\n## |\n# |$)/, "");
2673
+ content = content.replace(/\n{3,}$/, "\n");
2674
+ fs7.writeFileSync(realPath, content);
2675
+ }
2676
+ }
2677
+ const claudeMdPath = path8.resolve(projectRoot2, "CLAUDE.md");
2678
+ if (fs7.existsSync(claudeMdPath)) {
2679
+ let content = fs7.readFileSync(claudeMdPath, "utf-8");
2680
+ content = content.replace(/ --plugin-dir \.\/plugins\/typegraph-mcp/g, "");
2681
+ fs7.writeFileSync(claudeMdPath, content);
2682
+ }
2683
+ s.stop("Removed typegraph-mcp");
2684
+ deregisterMcpServers(projectRoot2);
2685
+ p.outro("typegraph-mcp has been uninstalled from this project.");
2686
+ }
2687
+ async function setupAgentInstructions(projectRoot2, selectedAgents) {
2688
+ const agentFiles = selectedAgents.map((id) => AGENTS[id].agentFile).filter((f) => f !== null);
2689
+ if (agentFiles.length === 0) {
2690
+ return;
2691
+ }
2692
+ const seenRealPaths = /* @__PURE__ */ new Map();
2693
+ const existingFiles = [];
2694
+ for (const agentFile of agentFiles) {
2695
+ const filePath = path8.resolve(projectRoot2, agentFile);
2696
+ if (!fs7.existsSync(filePath)) continue;
2697
+ const realPath = fs7.realpathSync(filePath);
2698
+ const previousFile = seenRealPaths.get(realPath);
2699
+ if (previousFile) {
2700
+ p.log.info(`${agentFile}: same file as ${previousFile} (skipped)`);
2701
+ continue;
2702
+ }
2703
+ seenRealPaths.set(realPath, agentFile);
2704
+ const content = fs7.readFileSync(filePath, "utf-8");
2705
+ existingFiles.push({ file: agentFile, realPath, hasSnippet: content.includes(SNIPPET_MARKER) });
2706
+ }
2707
+ if (existingFiles.length === 0) {
2708
+ p.log.warn(`No agent instruction files found (${agentFiles.join(", ")})`);
2709
+ p.note(AGENT_SNIPPET, "Add this snippet to your agent instructions file");
2710
+ } else if (existingFiles.some((f) => f.hasSnippet)) {
2711
+ for (const f of existingFiles) {
2712
+ if (f.hasSnippet) {
2713
+ p.log.info(`${f.file}: already has typegraph-mcp instructions`);
2714
+ }
2715
+ }
2716
+ } else {
2717
+ const target = existingFiles[0];
2718
+ const content = fs7.readFileSync(target.realPath, "utf-8");
2719
+ const appendContent = (content.endsWith("\n") ? "" : "\n") + "\n" + AGENT_SNIPPET;
2720
+ fs7.appendFileSync(target.realPath, appendContent);
2721
+ p.log.success(`${target.file}: appended typegraph-mcp instructions`);
2722
+ }
2723
+ if (selectedAgents.includes("claude-code")) {
2724
+ const claudeMdPath = path8.resolve(projectRoot2, "CLAUDE.md");
2725
+ if (fs7.existsSync(claudeMdPath)) {
2726
+ let content = fs7.readFileSync(claudeMdPath, "utf-8");
2727
+ const pluginDirPattern = /(`claude\s+)((?:--plugin-dir\s+\S+\s*)+)(`)/;
2728
+ const match = content.match(pluginDirPattern);
2729
+ if (match && !match[2].includes("./plugins/typegraph-mcp")) {
2730
+ const existingFlags = match[2].trimEnd();
2731
+ content = content.replace(
2732
+ pluginDirPattern,
2733
+ `$1${existingFlags} --plugin-dir ./plugins/typegraph-mcp$3`
2734
+ );
2735
+ fs7.writeFileSync(claudeMdPath, content);
2736
+ p.log.success("CLAUDE.md: added --plugin-dir ./plugins/typegraph-mcp");
2737
+ } else if (match) {
2738
+ p.log.info("CLAUDE.md: --plugin-dir already includes typegraph-mcp");
2739
+ }
2740
+ }
2741
+ }
2742
+ }
2743
+ async function runVerification(pluginDir, selectedAgents) {
2744
+ const config = resolveConfig(pluginDir);
2745
+ console.log("");
2746
+ const { main: checkMain } = await Promise.resolve().then(() => (init_check(), check_exports));
2747
+ const checkResult = await checkMain(config);
2748
+ console.log("");
2749
+ if (checkResult.failed > 0) {
2750
+ p.cancel("Health check has failures \u2014 fix the issues above before running smoke tests.");
2751
+ process.exit(1);
2752
+ }
2753
+ const { main: testMain } = await Promise.resolve().then(() => (init_smoke_test(), smoke_test_exports));
2754
+ const testResult = await testMain(config);
2755
+ console.log("");
2756
+ if (checkResult.failed === 0 && testResult.failed === 0) {
2757
+ if (selectedAgents.includes("claude-code")) {
2758
+ p.outro("Setup complete! Run: claude --plugin-dir ./plugins/typegraph-mcp");
2759
+ } else {
2760
+ p.outro("Setup complete! typegraph-mcp tools are now available to your agents.");
2761
+ }
2762
+ } else {
2763
+ p.cancel("Setup completed with issues. Fix the failures above and re-run.");
2764
+ process.exit(1);
2765
+ }
2766
+ }
2767
+ async function remove(yes2) {
2768
+ const projectRoot2 = process.cwd();
2769
+ const pluginDir = path8.resolve(projectRoot2, PLUGIN_DIR_NAME);
2770
+ process.stdout.write("\x1Bc");
2771
+ p.intro("TypeGraph MCP Remove");
2772
+ if (!fs7.existsSync(pluginDir)) {
2773
+ p.cancel("typegraph-mcp is not installed in this project.");
2774
+ process.exit(1);
2775
+ }
2776
+ if (!yes2) {
2777
+ const confirmed = await p.confirm({ message: "Remove typegraph-mcp from this project?" });
2778
+ if (p.isCancel(confirmed) || !confirmed) {
2779
+ p.cancel("Removal cancelled.");
2780
+ process.exit(0);
2781
+ }
2782
+ }
2783
+ await removePlugin(projectRoot2, pluginDir);
2784
+ }
2785
+ async function check() {
2786
+ const { main: checkMain } = await Promise.resolve().then(() => (init_check(), check_exports));
2787
+ const result = await checkMain();
2788
+ process.exit(result.failed > 0 ? 1 : 0);
2789
+ }
2790
+ async function test() {
2791
+ const { main: testMain } = await Promise.resolve().then(() => (init_smoke_test(), smoke_test_exports));
2792
+ const result = await testMain();
2793
+ process.exit(result.failed > 0 ? 1 : 0);
2794
+ }
2795
+ async function start() {
2796
+ await Promise.resolve().then(() => (init_server(), server_exports));
2797
+ }
2798
+ var args = process.argv.slice(2);
2799
+ var command = args.find((a) => !a.startsWith("-"));
2800
+ var yes = args.includes("--yes") || args.includes("-y");
2801
+ var help = args.includes("--help") || args.includes("-h");
2802
+ if (help || !command) {
2803
+ console.log(HELP);
2804
+ process.exit(0);
2805
+ }
2806
+ switch (command) {
2807
+ case "setup":
2808
+ setup(yes).catch((err) => {
2809
+ console.error("Fatal:", err);
2810
+ process.exit(1);
2811
+ });
2812
+ break;
2813
+ case "remove":
2814
+ remove(yes).catch((err) => {
2815
+ console.error("Fatal:", err);
2816
+ process.exit(1);
2817
+ });
2818
+ break;
2819
+ case "check":
2820
+ check().catch((err) => {
2821
+ console.error("Fatal:", err);
2822
+ process.exit(1);
2823
+ });
2824
+ break;
2825
+ case "test":
2826
+ test().catch((err) => {
2827
+ console.error("Fatal:", err);
2828
+ process.exit(1);
2829
+ });
2830
+ break;
2831
+ case "start":
2832
+ start().catch((err) => {
2833
+ console.error("Fatal:", err);
2834
+ process.exit(1);
2835
+ });
2836
+ break;
2837
+ default:
2838
+ console.log(`Unknown command: ${command}`);
2839
+ console.log("");
2840
+ console.log(HELP);
2841
+ process.exit(1);
2842
+ }