@sashabogi/argus-mcp 1.2.1

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/mcp.mjs ADDED
@@ -0,0 +1,1467 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/mcp.ts
4
+ import { createInterface } from "readline";
5
+
6
+ // src/core/config.ts
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { join } from "path";
10
+ var DEFAULT_CONFIG = {
11
+ provider: "ollama",
12
+ providers: {
13
+ ollama: {
14
+ baseUrl: "http://localhost:11434",
15
+ model: "qwen2.5-coder:7b"
16
+ }
17
+ },
18
+ defaults: {
19
+ maxTurns: 15,
20
+ turnTimeoutMs: 6e4,
21
+ snapshotExtensions: ["ts", "tsx", "js", "jsx", "rs", "py", "go", "java", "rb", "php", "swift", "kt", "scala", "c", "cpp", "h", "hpp", "cs", "md"],
22
+ excludePatterns: [
23
+ "node_modules",
24
+ ".git",
25
+ "target",
26
+ "dist",
27
+ "build",
28
+ ".next",
29
+ "coverage",
30
+ "__pycache__",
31
+ ".venv",
32
+ "vendor"
33
+ ]
34
+ }
35
+ };
36
+ function getConfigDir() {
37
+ return join(homedir(), ".argus");
38
+ }
39
+ function getConfigPath() {
40
+ return join(getConfigDir(), "config.json");
41
+ }
42
+ function loadConfig() {
43
+ const configPath = getConfigPath();
44
+ if (!existsSync(configPath)) {
45
+ return DEFAULT_CONFIG;
46
+ }
47
+ try {
48
+ const content = readFileSync(configPath, "utf-8");
49
+ const loaded = JSON.parse(content);
50
+ return {
51
+ ...DEFAULT_CONFIG,
52
+ ...loaded,
53
+ providers: {
54
+ ...DEFAULT_CONFIG.providers,
55
+ ...loaded.providers
56
+ },
57
+ defaults: {
58
+ ...DEFAULT_CONFIG.defaults,
59
+ ...loaded.defaults
60
+ }
61
+ };
62
+ } catch {
63
+ return DEFAULT_CONFIG;
64
+ }
65
+ }
66
+ function validateConfig(config2) {
67
+ const errors = [];
68
+ const providerConfig = config2.providers[config2.provider];
69
+ if (!providerConfig) {
70
+ errors.push(`Provider "${config2.provider}" is not configured`);
71
+ return errors;
72
+ }
73
+ if (config2.provider !== "ollama" && !providerConfig.apiKey) {
74
+ errors.push(`API key is required for provider "${config2.provider}"`);
75
+ }
76
+ if (!providerConfig.model) {
77
+ errors.push(`Model is required for provider "${config2.provider}"`);
78
+ }
79
+ return errors;
80
+ }
81
+
82
+ // src/core/snapshot.ts
83
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
84
+ import { join as join2, relative, extname } from "path";
85
+ var DEFAULT_OPTIONS = {
86
+ extensions: ["ts", "tsx", "js", "jsx", "rs", "py", "go", "java", "rb", "php", "swift", "kt", "scala", "c", "cpp", "h", "hpp", "cs", "md", "json"],
87
+ excludePatterns: [
88
+ "node_modules",
89
+ ".git",
90
+ "target",
91
+ "dist",
92
+ "build",
93
+ ".next",
94
+ "coverage",
95
+ "__pycache__",
96
+ ".venv",
97
+ "vendor",
98
+ ".DS_Store",
99
+ "*.lock",
100
+ "package-lock.json",
101
+ "*.min.js",
102
+ "*.min.css"
103
+ ],
104
+ maxFileSize: 1024 * 1024,
105
+ // 1MB
106
+ includeHidden: false
107
+ };
108
+ function shouldExclude(filePath, patterns) {
109
+ const normalizedPath = filePath.replace(/\\/g, "/");
110
+ for (const pattern of patterns) {
111
+ if (pattern.startsWith("*")) {
112
+ const suffix = pattern.slice(1);
113
+ if (normalizedPath.endsWith(suffix)) return true;
114
+ } else if (normalizedPath.includes(`/${pattern}/`) || normalizedPath.endsWith(`/${pattern}`) || normalizedPath === pattern) {
115
+ return true;
116
+ }
117
+ }
118
+ return false;
119
+ }
120
+ function hasValidExtension(filePath, extensions) {
121
+ const ext = extname(filePath).slice(1).toLowerCase();
122
+ return extensions.includes(ext);
123
+ }
124
+ function collectFiles(dir, options, baseDir = dir) {
125
+ const files = [];
126
+ try {
127
+ const entries = readdirSync(dir, { withFileTypes: true });
128
+ for (const entry of entries) {
129
+ const fullPath = join2(dir, entry.name);
130
+ const relativePath = relative(baseDir, fullPath);
131
+ if (!options.includeHidden && entry.name.startsWith(".")) {
132
+ continue;
133
+ }
134
+ if (shouldExclude(relativePath, options.excludePatterns)) {
135
+ continue;
136
+ }
137
+ if (entry.isDirectory()) {
138
+ files.push(...collectFiles(fullPath, options, baseDir));
139
+ } else if (entry.isFile()) {
140
+ if (!hasValidExtension(entry.name, options.extensions)) {
141
+ continue;
142
+ }
143
+ try {
144
+ const stats = statSync(fullPath);
145
+ if (stats.size > options.maxFileSize) {
146
+ continue;
147
+ }
148
+ } catch {
149
+ continue;
150
+ }
151
+ files.push(fullPath);
152
+ }
153
+ }
154
+ } catch (error) {
155
+ }
156
+ return files.sort();
157
+ }
158
+ function createSnapshot(projectPath, outputPath, options = {}) {
159
+ const mergedOptions = {
160
+ ...DEFAULT_OPTIONS,
161
+ ...options
162
+ };
163
+ if (!existsSync2(projectPath)) {
164
+ throw new Error(`Project path does not exist: ${projectPath}`);
165
+ }
166
+ const stats = statSync(projectPath);
167
+ if (!stats.isDirectory()) {
168
+ throw new Error(`Project path is not a directory: ${projectPath}`);
169
+ }
170
+ const files = collectFiles(projectPath, mergedOptions);
171
+ const lines = [];
172
+ lines.push("================================================================================");
173
+ lines.push("CODEBASE SNAPSHOT");
174
+ lines.push(`Project: ${projectPath}`);
175
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
176
+ lines.push(`Extensions: ${mergedOptions.extensions.join(", ")}`);
177
+ lines.push(`Files: ${files.length}`);
178
+ lines.push("================================================================================");
179
+ lines.push("");
180
+ for (const filePath of files) {
181
+ const relativePath = relative(projectPath, filePath);
182
+ lines.push("");
183
+ lines.push("================================================================================");
184
+ lines.push(`FILE: ./${relativePath}`);
185
+ lines.push("================================================================================");
186
+ try {
187
+ const content2 = readFileSync2(filePath, "utf-8");
188
+ lines.push(content2);
189
+ } catch (error) {
190
+ lines.push("[Unable to read file]");
191
+ }
192
+ }
193
+ const content = lines.join("\n");
194
+ writeFileSync2(outputPath, content);
195
+ const totalLines = content.split("\n").length;
196
+ const totalSize = Buffer.byteLength(content, "utf-8");
197
+ return {
198
+ outputPath,
199
+ fileCount: files.length,
200
+ totalLines,
201
+ totalSize,
202
+ files: files.map((f) => relative(projectPath, f))
203
+ };
204
+ }
205
+
206
+ // src/core/engine.ts
207
+ import { readFileSync as readFileSync3 } from "fs";
208
+
209
+ // src/core/prompts.ts
210
+ var NUCLEUS_COMMANDS = `
211
+ COMMANDS (output ONE per turn):
212
+ (grep "pattern") - Find lines matching regex
213
+ (grep "pattern" "i") - Case-insensitive search
214
+ (count RESULTS) - Count matches
215
+ (take RESULTS n) - First n results
216
+ (filter RESULTS (lambda (x) (match x.line "pattern" 0))) - Filter results
217
+ (map RESULTS (lambda (x) x.line)) - Extract just the lines
218
+
219
+ VARIABLES: RESULTS = last result, _1 _2 _3 = results from turn 1,2,3
220
+
221
+ TO ANSWER: <<<FINAL>>>your answer<<<END>>>
222
+ `;
223
+ var CODEBASE_ANALYSIS_PROMPT = `You are analyzing a SOFTWARE CODEBASE snapshot to help a developer understand it.
224
+
225
+ The snapshot contains source files concatenated with "FILE: ./path/to/file" markers.
226
+
227
+ ${NUCLEUS_COMMANDS}
228
+
229
+ ## STRATEGY FOR CODEBASE SNAPSHOTS
230
+
231
+ **To find modules/directories:**
232
+ (grep "FILE:.*src/[^/]+/") - top-level source dirs
233
+ (grep "FILE:.*mod\\.rs") - Rust modules
234
+ (grep "FILE:.*index\\.(ts|js)") - JS/TS modules
235
+
236
+ **To find implementations:**
237
+ (grep "fn function_name") - Rust functions
238
+ (grep "function|const.*=>") - JS functions
239
+ (grep "class ClassName") - Classes
240
+ (grep "struct |type |interface") - Type definitions
241
+
242
+ **To understand structure:**
243
+ (grep "FILE:") - List all files
244
+ (grep "use |import |require") - Find dependencies
245
+ (grep "pub |export") - Public APIs
246
+
247
+ ## RULES
248
+ 1. Output ONLY a Nucleus command OR a final answer
249
+ 2. NO explanations, NO markdown formatting in commands
250
+ 3. MUST provide final answer by turn 8
251
+ 4. If turn 6+, start summarizing what you found
252
+
253
+ ## EXAMPLE SESSION
254
+ Turn 1: (grep "FILE:.*src/[^/]+/mod\\.rs")
255
+ Turn 2: (take RESULTS 15)
256
+ Turn 3: <<<FINAL>>>The codebase has these main modules:
257
+ - src/auth/ - Authentication handling
258
+ - src/api/ - API endpoints
259
+ - src/db/ - Database layer
260
+ ...<<<END>>>
261
+ `;
262
+ var ARCHITECTURE_PROMPT = `You are generating an ARCHITECTURE SUMMARY of a codebase.
263
+
264
+ ${NUCLEUS_COMMANDS}
265
+
266
+ ## YOUR TASK
267
+ Create a summary suitable for CLAUDE.md that helps Claude Code understand this project after context compaction.
268
+
269
+ ## SEARCH STRATEGY (do these in order)
270
+ 1. (grep "FILE:.*mod\\.rs|FILE:.*index\\.(ts|js)") - Find module entry points
271
+ 2. (take RESULTS 20) - Limit results
272
+ 3. Based on file paths, provide your summary
273
+
274
+ ## OUTPUT FORMAT
275
+ Your final answer should be structured like:
276
+
277
+ ## Modules
278
+ - **module_name/** - Brief description based on files found
279
+
280
+ ## Key Patterns
281
+ - Pattern observations from the code
282
+
283
+ ## Important Files
284
+ - List key files and their apparent purpose
285
+
286
+ PROVIDE FINAL ANSWER BY TURN 6.
287
+ `;
288
+ var IMPLEMENTATION_PROMPT = `You are finding HOW something works in a codebase.
289
+
290
+ ${NUCLEUS_COMMANDS}
291
+
292
+ ## STRATEGY
293
+ 1. (grep "FILE:.*keyword") - Find files related to the concept
294
+ 2. (grep "keyword") - Find all mentions
295
+ 3. (take RESULTS 30) - Limit if too many results
296
+ 4. Look for function definitions, structs, classes
297
+ 5. PROVIDE FINAL ANSWER based on file paths and code patterns found
298
+
299
+ ## IMPORTANT
300
+ - You have 12 turns maximum
301
+ - By turn 8, START WRITING YOUR FINAL ANSWER
302
+ - Use what you've found - don't keep searching indefinitely
303
+ - It's better to give a partial answer than no answer
304
+
305
+ ## OUTPUT FORMAT
306
+ Your final answer should explain:
307
+ - Which files contain the implementation
308
+ - Key functions/structs/classes involved
309
+ - Basic flow of how it works (based on what you found)
310
+ `;
311
+ var COUNT_PROMPT = `You are counting items in a codebase.
312
+
313
+ ${NUCLEUS_COMMANDS}
314
+
315
+ ## STRATEGY
316
+ 1. (grep "pattern")
317
+ 2. (count RESULTS)
318
+ 3. <<<FINAL>>>There are N items matching the pattern.<<<END>>>
319
+
320
+ THIS SHOULD TAKE 2-3 TURNS MAXIMUM.
321
+ `;
322
+ var SEARCH_PROMPT = `You are searching for specific code.
323
+
324
+ ${NUCLEUS_COMMANDS}
325
+
326
+ ## STRATEGY
327
+ 1. (grep "pattern")
328
+ 2. (take RESULTS 20) if too many
329
+ 3. Report what you found with file paths
330
+
331
+ PROVIDE FINAL ANSWER BY TURN 4.
332
+ `;
333
+ function selectPrompt(query) {
334
+ const q = query.toLowerCase();
335
+ if (/how many|count|number of|total|how much/.test(q)) {
336
+ return COUNT_PROMPT;
337
+ }
338
+ if (/^(find|search|show|list|where is|locate)\b/.test(q) && q.length < 50) {
339
+ return SEARCH_PROMPT;
340
+ }
341
+ if (/architect|structure|overview|module|organization|main.*component|summar|layout/.test(q)) {
342
+ return ARCHITECTURE_PROMPT;
343
+ }
344
+ if (/how does|how is|implement|work|handle|process|flow/.test(q)) {
345
+ return IMPLEMENTATION_PROMPT;
346
+ }
347
+ return CODEBASE_ANALYSIS_PROMPT;
348
+ }
349
+ function buildSystemPrompt(query) {
350
+ return selectPrompt(query);
351
+ }
352
+ function getTurnLimit(query) {
353
+ const q = query.toLowerCase();
354
+ if (/how many|count/.test(q)) return 5;
355
+ if (/^(find|search|show|list)\b/.test(q) && q.length < 50) return 6;
356
+ if (/architect|overview|structure|module/.test(q)) return 12;
357
+ if (/how does|how is|implement|work/.test(q)) return 12;
358
+ return 12;
359
+ }
360
+
361
+ // src/core/engine.ts
362
+ function executeNucleus(command, content, bindings) {
363
+ const parsed = parseSExpression(command);
364
+ if (!parsed) {
365
+ throw new Error(`Failed to parse command: ${command}`);
366
+ }
367
+ return evaluateExpr(parsed, content, bindings);
368
+ }
369
+ function parseSExpression(input) {
370
+ const tokens = tokenize(input.trim());
371
+ if (tokens.length === 0) return null;
372
+ let pos = 0;
373
+ function parse() {
374
+ const token = tokens[pos++];
375
+ if (token === "(") {
376
+ const list = [];
377
+ while (tokens[pos] !== ")" && pos < tokens.length) {
378
+ list.push(parse());
379
+ }
380
+ pos++;
381
+ return list;
382
+ } else if (token.startsWith('"')) {
383
+ return token.slice(1, -1).replace(/\\"/g, '"');
384
+ } else if (/^-?\d+(\.\d+)?$/.test(token)) {
385
+ return token;
386
+ } else {
387
+ return token;
388
+ }
389
+ }
390
+ return parse();
391
+ }
392
+ function tokenize(input) {
393
+ const tokens = [];
394
+ let i = 0;
395
+ while (i < input.length) {
396
+ const char = input[i];
397
+ if (/\s/.test(char)) {
398
+ i++;
399
+ continue;
400
+ }
401
+ if (char === "(" || char === ")") {
402
+ tokens.push(char);
403
+ i++;
404
+ continue;
405
+ }
406
+ if (char === '"') {
407
+ let str = '"';
408
+ i++;
409
+ while (i < input.length && input[i] !== '"') {
410
+ if (input[i] === "\\" && i + 1 < input.length) {
411
+ str += input[i] + input[i + 1];
412
+ i += 2;
413
+ } else {
414
+ str += input[i];
415
+ i++;
416
+ }
417
+ }
418
+ str += '"';
419
+ i++;
420
+ tokens.push(str);
421
+ continue;
422
+ }
423
+ let sym = "";
424
+ while (i < input.length && !/[\s()]/.test(input[i])) {
425
+ sym += input[i];
426
+ i++;
427
+ }
428
+ tokens.push(sym);
429
+ }
430
+ return tokens;
431
+ }
432
+ function evaluateExpr(expr, content, bindings) {
433
+ if (typeof expr === "string") {
434
+ if (bindings.has(expr)) {
435
+ return bindings.get(expr);
436
+ }
437
+ if (/^-?\d+(\.\d+)?$/.test(expr)) {
438
+ return parseFloat(expr);
439
+ }
440
+ return expr;
441
+ }
442
+ if (!Array.isArray(expr) || expr.length === 0) {
443
+ return expr;
444
+ }
445
+ const [op, ...args] = expr;
446
+ switch (op) {
447
+ case "grep": {
448
+ const pattern = evaluateExpr(args[0], content, bindings);
449
+ const flags = args[1] ? evaluateExpr(args[1], content, bindings) : "";
450
+ const regex = new RegExp(pattern, flags + "g");
451
+ const lines = content.split("\n");
452
+ const matches = [];
453
+ let charIndex = 0;
454
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
455
+ const line = lines[lineNum];
456
+ let match;
457
+ const lineRegex = new RegExp(pattern, flags + "g");
458
+ while ((match = lineRegex.exec(line)) !== null) {
459
+ matches.push({
460
+ match: match[0],
461
+ line,
462
+ lineNum: lineNum + 1,
463
+ index: charIndex + match.index,
464
+ groups: match.slice(1)
465
+ });
466
+ }
467
+ charIndex += line.length + 1;
468
+ }
469
+ return matches;
470
+ }
471
+ case "count": {
472
+ const arr = evaluateExpr(args[0], content, bindings);
473
+ if (Array.isArray(arr)) return arr.length;
474
+ return 0;
475
+ }
476
+ case "map": {
477
+ const arr = evaluateExpr(args[0], content, bindings);
478
+ const lambdaExpr = args[1];
479
+ if (!Array.isArray(lambdaExpr) || lambdaExpr[0] !== "lambda") {
480
+ throw new Error("map requires a lambda expression");
481
+ }
482
+ const params = lambdaExpr[1];
483
+ const body = lambdaExpr[2];
484
+ const paramName = Array.isArray(params) ? params[0] : params;
485
+ return arr.map((item) => {
486
+ const localBindings = new Map(bindings);
487
+ localBindings.set(paramName, item);
488
+ return evaluateExpr(body, content, localBindings);
489
+ });
490
+ }
491
+ case "filter": {
492
+ const arr = evaluateExpr(args[0], content, bindings);
493
+ const lambdaExpr = args[1];
494
+ if (!Array.isArray(lambdaExpr) || lambdaExpr[0] !== "lambda") {
495
+ throw new Error("filter requires a lambda expression");
496
+ }
497
+ const params = lambdaExpr[1];
498
+ const body = lambdaExpr[2];
499
+ const paramName = Array.isArray(params) ? params[0] : params;
500
+ return arr.filter((item) => {
501
+ const localBindings = new Map(bindings);
502
+ localBindings.set(paramName, item);
503
+ return evaluateExpr(body, content, localBindings);
504
+ });
505
+ }
506
+ case "first": {
507
+ const arr = evaluateExpr(args[0], content, bindings);
508
+ return arr[0];
509
+ }
510
+ case "last": {
511
+ const arr = evaluateExpr(args[0], content, bindings);
512
+ return arr[arr.length - 1];
513
+ }
514
+ case "take": {
515
+ const arr = evaluateExpr(args[0], content, bindings);
516
+ const n = evaluateExpr(args[1], content, bindings);
517
+ return arr.slice(0, n);
518
+ }
519
+ case "sort": {
520
+ const arr = evaluateExpr(args[0], content, bindings);
521
+ const key = evaluateExpr(args[1], content, bindings);
522
+ return [...arr].sort((a, b) => {
523
+ const aVal = a[key];
524
+ const bVal = b[key];
525
+ if (typeof aVal === "number" && typeof bVal === "number") {
526
+ return aVal - bVal;
527
+ }
528
+ return String(aVal).localeCompare(String(bVal));
529
+ });
530
+ }
531
+ case "match": {
532
+ const str = evaluateExpr(args[0], content, bindings);
533
+ const strValue = typeof str === "object" && str !== null && "line" in str ? str.line : String(str);
534
+ const pattern = evaluateExpr(args[1], content, bindings);
535
+ const group = args[2] ? evaluateExpr(args[2], content, bindings) : 0;
536
+ const regex = new RegExp(pattern);
537
+ const match = strValue.match(regex);
538
+ if (match) {
539
+ return match[group] || null;
540
+ }
541
+ return null;
542
+ }
543
+ default:
544
+ throw new Error(`Unknown command: ${op}`);
545
+ }
546
+ }
547
+ function extractCommand(response) {
548
+ const finalMatch = response.match(/<<<FINAL>>>([\s\S]*?)<<<END>>>/);
549
+ if (finalMatch) {
550
+ return { finalAnswer: finalMatch[1].trim() };
551
+ }
552
+ const sexpMatch = response.match(/\([^)]*(?:\([^)]*\)[^)]*)*\)/);
553
+ if (sexpMatch) {
554
+ return { command: sexpMatch[0] };
555
+ }
556
+ return {};
557
+ }
558
+ async function analyze(provider2, documentPath, query, options = {}) {
559
+ const {
560
+ maxTurns = 15,
561
+ verbose = false,
562
+ onProgress
563
+ } = options;
564
+ const dynamicLimit = Math.min(getTurnLimit(query), maxTurns);
565
+ const content = readFileSync3(documentPath, "utf-8");
566
+ const fileCount = (content.match(/^FILE:/gm) || []).length;
567
+ const lineCount = content.split("\n").length;
568
+ const bindings = /* @__PURE__ */ new Map();
569
+ const commands = [];
570
+ const messages = [
571
+ {
572
+ role: "system",
573
+ content: buildSystemPrompt(query)
574
+ },
575
+ {
576
+ role: "user",
577
+ content: `CODEBASE SNAPSHOT:
578
+ - Total size: ${content.length.toLocaleString()} characters
579
+ - Files: ${fileCount}
580
+ - Lines: ${lineCount.toLocaleString()}
581
+
582
+ Files are marked with "FILE: ./path/to/file" headers.
583
+
584
+ QUERY: ${query}
585
+
586
+ Begin analysis. You have ${dynamicLimit} turns maximum - provide final answer before then.`
587
+ }
588
+ ];
589
+ for (let turn = 1; turn <= dynamicLimit; turn++) {
590
+ const isLastTurn = turn === dynamicLimit;
591
+ const isNearEnd = turn >= dynamicLimit - 2;
592
+ if (verbose) {
593
+ console.log(`
594
+ [Turn ${turn}/${dynamicLimit}] Querying LLM...`);
595
+ }
596
+ const result = await provider2.complete(messages);
597
+ const response = result.content;
598
+ if (verbose) {
599
+ console.log(`[Turn ${turn}] Response: ${response.slice(0, 200)}...`);
600
+ }
601
+ const extracted = extractCommand(response);
602
+ if (extracted.finalAnswer) {
603
+ return {
604
+ answer: extracted.finalAnswer,
605
+ turns: turn,
606
+ commands,
607
+ success: true
608
+ };
609
+ }
610
+ if (!extracted.command) {
611
+ messages.push({ role: "assistant", content: response });
612
+ messages.push({ role: "user", content: "Please provide a Nucleus command or final answer." });
613
+ continue;
614
+ }
615
+ const command = extracted.command;
616
+ commands.push(command);
617
+ if (verbose) {
618
+ console.log(`[Turn ${turn}] Command: ${command}`);
619
+ }
620
+ try {
621
+ const cmdResult = executeNucleus(command, content, bindings);
622
+ bindings.set("RESULTS", cmdResult);
623
+ bindings.set(`_${turn}`, cmdResult);
624
+ const resultStr = JSON.stringify(cmdResult, null, 2);
625
+ const truncatedResult = resultStr.length > 2e3 ? resultStr.slice(0, 2e3) + "...[truncated]" : resultStr;
626
+ if (verbose) {
627
+ console.log(`[Turn ${turn}] Result: ${truncatedResult.slice(0, 500)}...`);
628
+ }
629
+ onProgress?.(turn, command, cmdResult);
630
+ messages.push({ role: "assistant", content: command });
631
+ let userMessage = `Result:
632
+ ${truncatedResult}`;
633
+ if (isNearEnd && !isLastTurn) {
634
+ userMessage += `
635
+
636
+ \u26A0\uFE0F ${dynamicLimit - turn} turns remaining. Start forming your final answer.`;
637
+ }
638
+ messages.push({ role: "user", content: userMessage });
639
+ if (isLastTurn) {
640
+ messages.push({
641
+ role: "user",
642
+ content: "STOP SEARCHING. Based on everything you found, provide your final answer NOW using <<<FINAL>>>your answer<<<END>>>"
643
+ });
644
+ const finalResult = await provider2.complete(messages);
645
+ const finalExtracted = extractCommand(finalResult.content);
646
+ if (finalExtracted.finalAnswer) {
647
+ return {
648
+ answer: finalExtracted.finalAnswer,
649
+ turns: turn,
650
+ commands,
651
+ success: true
652
+ };
653
+ }
654
+ return {
655
+ answer: finalResult.content,
656
+ turns: turn,
657
+ commands,
658
+ success: true
659
+ };
660
+ }
661
+ } catch (error) {
662
+ const errMsg = error instanceof Error ? error.message : String(error);
663
+ if (verbose) {
664
+ console.log(`[Turn ${turn}] Error: ${errMsg}`);
665
+ }
666
+ messages.push({ role: "assistant", content: command });
667
+ messages.push({ role: "user", content: `Error executing command: ${errMsg}` });
668
+ }
669
+ }
670
+ return {
671
+ answer: "Maximum turns reached without final answer",
672
+ turns: dynamicLimit,
673
+ commands,
674
+ success: false,
675
+ error: "Max turns reached"
676
+ };
677
+ }
678
+ function searchDocument(documentPath, pattern, options = {}) {
679
+ const content = readFileSync3(documentPath, "utf-8");
680
+ const flags = options.caseInsensitive ? "gi" : "g";
681
+ const regex = new RegExp(pattern, flags);
682
+ const lines = content.split("\n");
683
+ const matches = [];
684
+ let charIndex = 0;
685
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
686
+ const line = lines[lineNum];
687
+ let match;
688
+ const lineRegex = new RegExp(pattern, flags);
689
+ while ((match = lineRegex.exec(line)) !== null) {
690
+ matches.push({
691
+ match: match[0],
692
+ line,
693
+ lineNum: lineNum + 1,
694
+ index: charIndex + match.index,
695
+ groups: match.slice(1)
696
+ });
697
+ if (options.maxResults && matches.length >= options.maxResults) {
698
+ return matches;
699
+ }
700
+ }
701
+ charIndex += line.length + 1;
702
+ }
703
+ return matches;
704
+ }
705
+
706
+ // src/providers/openai-compatible.ts
707
+ var OpenAICompatibleProvider = class {
708
+ name;
709
+ config;
710
+ constructor(name, config2) {
711
+ this.name = name;
712
+ this.config = config2;
713
+ if (!config2.apiKey) {
714
+ throw new Error(`API key is required for ${name} provider`);
715
+ }
716
+ if (!config2.baseUrl) {
717
+ throw new Error(`Base URL is required for ${name} provider`);
718
+ }
719
+ }
720
+ async complete(messages, options) {
721
+ const endpoint = `${this.config.baseUrl}/chat/completions`;
722
+ const body = {
723
+ model: this.config.model,
724
+ messages: messages.map((m) => ({
725
+ role: m.role,
726
+ content: m.content
727
+ })),
728
+ temperature: options?.temperature ?? this.config.options?.temperature ?? 0.2,
729
+ max_tokens: options?.maxTokens ?? this.config.options?.max_tokens ?? 4096,
730
+ ...options?.stopSequences && { stop: options.stopSequences }
731
+ };
732
+ const response = await fetch(endpoint, {
733
+ method: "POST",
734
+ headers: {
735
+ "Content-Type": "application/json",
736
+ "Authorization": `Bearer ${this.config.apiKey}`
737
+ },
738
+ body: JSON.stringify(body)
739
+ });
740
+ if (!response.ok) {
741
+ const errorText = await response.text();
742
+ throw new Error(`${this.name} API error (${response.status}): ${errorText}`);
743
+ }
744
+ const data = await response.json();
745
+ const choice = data.choices[0];
746
+ return {
747
+ content: choice.message.content || "",
748
+ finishReason: choice.finish_reason === "stop" ? "stop" : choice.finish_reason === "length" ? "length" : "error",
749
+ usage: data.usage ? {
750
+ promptTokens: data.usage.prompt_tokens,
751
+ completionTokens: data.usage.completion_tokens,
752
+ totalTokens: data.usage.total_tokens
753
+ } : void 0
754
+ };
755
+ }
756
+ async healthCheck() {
757
+ try {
758
+ const result = await this.complete([
759
+ { role: "user", content: 'Say "ok"' }
760
+ ], { maxTokens: 10 });
761
+ return result.content.length > 0;
762
+ } catch {
763
+ return false;
764
+ }
765
+ }
766
+ };
767
+ function createZAIProvider(config2) {
768
+ return new OpenAICompatibleProvider("ZAI", {
769
+ ...config2,
770
+ baseUrl: config2.baseUrl || "https://api.z.ai/api/coding/paas/v4",
771
+ model: config2.model || "glm-4.7"
772
+ });
773
+ }
774
+ function createOpenAIProvider(config2) {
775
+ return new OpenAICompatibleProvider("OpenAI", {
776
+ ...config2,
777
+ baseUrl: config2.baseUrl || "https://api.openai.com/v1",
778
+ model: config2.model || "gpt-4o"
779
+ });
780
+ }
781
+ function createDeepSeekProvider(config2) {
782
+ return new OpenAICompatibleProvider("DeepSeek", {
783
+ ...config2,
784
+ baseUrl: config2.baseUrl || "https://api.deepseek.com",
785
+ model: config2.model || "deepseek-chat"
786
+ });
787
+ }
788
+
789
+ // src/providers/ollama.ts
790
+ var OllamaProvider = class {
791
+ name = "Ollama";
792
+ config;
793
+ constructor(config2) {
794
+ this.config = {
795
+ ...config2,
796
+ baseUrl: config2.baseUrl || "http://localhost:11434",
797
+ model: config2.model || "qwen2.5-coder:7b"
798
+ };
799
+ }
800
+ async complete(messages, options) {
801
+ const endpoint = `${this.config.baseUrl}/api/chat`;
802
+ const body = {
803
+ model: this.config.model,
804
+ messages: messages.map((m) => ({
805
+ role: m.role,
806
+ content: m.content
807
+ })),
808
+ stream: false,
809
+ options: {
810
+ temperature: options?.temperature ?? this.config.options?.temperature ?? 0.2,
811
+ num_ctx: this.config.options?.num_ctx ?? 8192
812
+ }
813
+ };
814
+ const response = await fetch(endpoint, {
815
+ method: "POST",
816
+ headers: {
817
+ "Content-Type": "application/json"
818
+ },
819
+ body: JSON.stringify(body)
820
+ });
821
+ if (!response.ok) {
822
+ const errorText = await response.text();
823
+ throw new Error(`Ollama API error (${response.status}): ${errorText}`);
824
+ }
825
+ const data = await response.json();
826
+ return {
827
+ content: data.message.content || "",
828
+ finishReason: data.done ? "stop" : "error",
829
+ usage: data.eval_count ? {
830
+ promptTokens: data.prompt_eval_count || 0,
831
+ completionTokens: data.eval_count,
832
+ totalTokens: (data.prompt_eval_count || 0) + data.eval_count
833
+ } : void 0
834
+ };
835
+ }
836
+ async healthCheck() {
837
+ try {
838
+ const response = await fetch(`${this.config.baseUrl}/api/tags`);
839
+ if (!response.ok) return false;
840
+ const data = await response.json();
841
+ const hasModel = data.models.some(
842
+ (m) => m.name === this.config.model || m.name.startsWith(this.config.model + ":")
843
+ );
844
+ return hasModel;
845
+ } catch {
846
+ return false;
847
+ }
848
+ }
849
+ /**
850
+ * List available models
851
+ */
852
+ async listModels() {
853
+ try {
854
+ const response = await fetch(`${this.config.baseUrl}/api/tags`);
855
+ if (!response.ok) return [];
856
+ const data = await response.json();
857
+ return data.models.map((m) => m.name);
858
+ } catch {
859
+ return [];
860
+ }
861
+ }
862
+ };
863
+ function createOllamaProvider(config2) {
864
+ return new OllamaProvider(config2);
865
+ }
866
+
867
+ // src/providers/anthropic.ts
868
+ var AnthropicProvider = class {
869
+ name = "Anthropic";
870
+ config;
871
+ constructor(config2) {
872
+ if (!config2.apiKey) {
873
+ throw new Error("API key is required for Anthropic provider");
874
+ }
875
+ this.config = {
876
+ ...config2,
877
+ baseUrl: config2.baseUrl || "https://api.anthropic.com",
878
+ model: config2.model || "claude-sonnet-4-20250514"
879
+ };
880
+ }
881
+ async complete(messages, options) {
882
+ const endpoint = `${this.config.baseUrl}/v1/messages`;
883
+ const systemMessage = messages.find((m) => m.role === "system");
884
+ const nonSystemMessages = messages.filter((m) => m.role !== "system");
885
+ const body = {
886
+ model: this.config.model,
887
+ max_tokens: options?.maxTokens ?? this.config.options?.max_tokens ?? 4096,
888
+ ...systemMessage && { system: systemMessage.content },
889
+ messages: nonSystemMessages.map((m) => ({
890
+ role: m.role,
891
+ content: m.content
892
+ })),
893
+ ...options?.temperature !== void 0 && { temperature: options.temperature },
894
+ ...options?.stopSequences && { stop_sequences: options.stopSequences }
895
+ };
896
+ const response = await fetch(endpoint, {
897
+ method: "POST",
898
+ headers: {
899
+ "Content-Type": "application/json",
900
+ "x-api-key": this.config.apiKey,
901
+ "anthropic-version": "2023-06-01"
902
+ },
903
+ body: JSON.stringify(body)
904
+ });
905
+ if (!response.ok) {
906
+ const errorText = await response.text();
907
+ throw new Error(`Anthropic API error (${response.status}): ${errorText}`);
908
+ }
909
+ const data = await response.json();
910
+ const textContent = data.content.filter((c) => c.type === "text").map((c) => c.text).join("");
911
+ return {
912
+ content: textContent,
913
+ finishReason: data.stop_reason === "end_turn" ? "stop" : data.stop_reason === "max_tokens" ? "length" : "error",
914
+ usage: {
915
+ promptTokens: data.usage.input_tokens,
916
+ completionTokens: data.usage.output_tokens,
917
+ totalTokens: data.usage.input_tokens + data.usage.output_tokens
918
+ }
919
+ };
920
+ }
921
+ async healthCheck() {
922
+ try {
923
+ const result = await this.complete([
924
+ { role: "user", content: 'Say "ok"' }
925
+ ], { maxTokens: 10 });
926
+ return result.content.length > 0;
927
+ } catch {
928
+ return false;
929
+ }
930
+ }
931
+ };
932
+ function createAnthropicProvider(config2) {
933
+ return new AnthropicProvider(config2);
934
+ }
935
+
936
+ // src/providers/index.ts
937
+ function createProvider(config2) {
938
+ const providerType = config2.provider;
939
+ const providerConfig = config2.providers[providerType];
940
+ if (!providerConfig) {
941
+ throw new Error(`No configuration found for provider: ${providerType}`);
942
+ }
943
+ return createProviderByType(providerType, providerConfig);
944
+ }
945
+ function createProviderByType(type, config2) {
946
+ switch (type) {
947
+ case "zai":
948
+ return createZAIProvider(config2);
949
+ case "openai":
950
+ return createOpenAIProvider(config2);
951
+ case "deepseek":
952
+ return createDeepSeekProvider(config2);
953
+ case "ollama":
954
+ return createOllamaProvider(config2);
955
+ case "anthropic":
956
+ return createAnthropicProvider(config2);
957
+ default:
958
+ throw new Error(`Unknown provider type: ${type}`);
959
+ }
960
+ }
961
+
962
+ // src/mcp.ts
963
+ import { existsSync as existsSync3, statSync as statSync2, mkdtempSync, unlinkSync, readFileSync as readFileSync4 } from "fs";
964
+ import { tmpdir } from "os";
965
+ import { join as join3, resolve } from "path";
966
+ var TOOLS = [
967
+ {
968
+ name: "find_importers",
969
+ description: `Find all files that import a given file or module. Zero AI cost.
970
+
971
+ Use when you need to know:
972
+ - What files depend on this module?
973
+ - Who uses this function/component?
974
+ - Impact analysis before refactoring
975
+
976
+ Requires an enhanced snapshot with metadata (created with --enhanced flag).`,
977
+ inputSchema: {
978
+ type: "object",
979
+ properties: {
980
+ path: {
981
+ type: "string",
982
+ description: "Path to the snapshot file (.argus/snapshot.txt)"
983
+ },
984
+ target: {
985
+ type: "string",
986
+ description: 'The file path to find importers of (e.g., "src/auth.ts")'
987
+ }
988
+ },
989
+ required: ["path", "target"]
990
+ }
991
+ },
992
+ {
993
+ name: "find_symbol",
994
+ description: `Find where a symbol (function, class, type, constant) is exported from. Zero AI cost.
995
+
996
+ Use when you need to know:
997
+ - Where is this function defined?
998
+ - Which file exports this component?
999
+ - Find the source of a type
1000
+
1001
+ Requires an enhanced snapshot with metadata (created with --enhanced flag).`,
1002
+ inputSchema: {
1003
+ type: "object",
1004
+ properties: {
1005
+ path: {
1006
+ type: "string",
1007
+ description: "Path to the snapshot file (.argus/snapshot.txt)"
1008
+ },
1009
+ symbol: {
1010
+ type: "string",
1011
+ description: 'The symbol name to find (e.g., "AuthProvider", "useAuth")'
1012
+ }
1013
+ },
1014
+ required: ["path", "symbol"]
1015
+ }
1016
+ },
1017
+ {
1018
+ name: "get_file_deps",
1019
+ description: `Get all dependencies (imports) of a specific file. Zero AI cost.
1020
+
1021
+ Use when you need to understand:
1022
+ - What does this file depend on?
1023
+ - What modules need to be loaded?
1024
+ - Trace the dependency chain
1025
+
1026
+ Requires an enhanced snapshot with metadata (created with --enhanced flag).`,
1027
+ inputSchema: {
1028
+ type: "object",
1029
+ properties: {
1030
+ path: {
1031
+ type: "string",
1032
+ description: "Path to the snapshot file (.argus/snapshot.txt)"
1033
+ },
1034
+ file: {
1035
+ type: "string",
1036
+ description: 'The file path to get dependencies for (e.g., "src/app.tsx")'
1037
+ }
1038
+ },
1039
+ required: ["path", "file"]
1040
+ }
1041
+ },
1042
+ {
1043
+ name: "analyze_codebase",
1044
+ description: `IMPORTANT: Use this tool INSTEAD of reading many files when you need to understand a codebase.
1045
+
1046
+ This tool analyzes codebases that are TOO LARGE to fit in context. It uses recursive AI reasoning
1047
+ to answer questions about code architecture, patterns, and relationships across hundreds of files.
1048
+
1049
+ AUTOMATICALLY USE THIS WHEN:
1050
+ - User asks about codebase architecture, structure, or "how does X work"
1051
+ - You would need to read more than 10-15 files to answer a question
1052
+ - Context was recently compacted and you lost codebase knowledge
1053
+ - User asks to find patterns, conventions, or implementations across the codebase
1054
+ - You need to understand module relationships or dependencies
1055
+
1056
+ This is MORE EFFICIENT than reading files individually - it uses ~500 tokens instead of 50,000+.
1057
+
1058
+ If a .argus/snapshot.txt exists, use that path. Otherwise, pass the project directory.`,
1059
+ inputSchema: {
1060
+ type: "object",
1061
+ properties: {
1062
+ path: {
1063
+ type: "string",
1064
+ description: "Path to .argus/snapshot.txt if it exists, otherwise the codebase directory"
1065
+ },
1066
+ query: {
1067
+ type: "string",
1068
+ description: "The question about the codebase (be specific for best results)"
1069
+ },
1070
+ maxTurns: {
1071
+ type: "number",
1072
+ description: "Maximum reasoning turns (default: 15, use 5 for simple counts)"
1073
+ }
1074
+ },
1075
+ required: ["path", "query"]
1076
+ }
1077
+ },
1078
+ {
1079
+ name: "search_codebase",
1080
+ description: `Fast regex search across a codebase - ZERO AI cost, instant results.
1081
+
1082
+ Use this BEFORE analyze_codebase when you need to:
1083
+ - Find where something is defined (function, class, variable)
1084
+ - Locate files containing a pattern
1085
+ - Count occurrences of something
1086
+ - Find all imports/exports of a module
1087
+
1088
+ Requires a snapshot file. If .argus/snapshot.txt exists, use that.
1089
+ Returns matching lines with line numbers - much faster than grep across many files.`,
1090
+ inputSchema: {
1091
+ type: "object",
1092
+ properties: {
1093
+ path: {
1094
+ type: "string",
1095
+ description: "Path to the snapshot file (.argus/snapshot.txt)"
1096
+ },
1097
+ pattern: {
1098
+ type: "string",
1099
+ description: "Regex pattern to search for"
1100
+ },
1101
+ caseInsensitive: {
1102
+ type: "boolean",
1103
+ description: "Whether to ignore case (default: false)"
1104
+ },
1105
+ maxResults: {
1106
+ type: "number",
1107
+ description: "Maximum results to return (default: 50)"
1108
+ }
1109
+ },
1110
+ required: ["path", "pattern"]
1111
+ }
1112
+ },
1113
+ {
1114
+ name: "create_snapshot",
1115
+ description: `Create a codebase snapshot for analysis. Run this ONCE per project, then use the snapshot for all queries.
1116
+
1117
+ The snapshot compiles all source files into a single optimized file that survives context compaction.
1118
+ Store at .argus/snapshot.txt so other tools can find it.
1119
+
1120
+ Run this when:
1121
+ - Starting work on a new project
1122
+ - .argus/snapshot.txt doesn't exist
1123
+ - Codebase has significantly changed since last snapshot`,
1124
+ inputSchema: {
1125
+ type: "object",
1126
+ properties: {
1127
+ path: {
1128
+ type: "string",
1129
+ description: "Path to the codebase directory"
1130
+ },
1131
+ outputPath: {
1132
+ type: "string",
1133
+ description: "Where to save (recommend: .argus/snapshot.txt)"
1134
+ },
1135
+ extensions: {
1136
+ type: "array",
1137
+ items: { type: "string" },
1138
+ description: "File extensions to include (default: common code extensions)"
1139
+ }
1140
+ },
1141
+ required: ["path"]
1142
+ }
1143
+ }
1144
+ ];
1145
+ var config;
1146
+ var provider = null;
1147
+ try {
1148
+ config = loadConfig();
1149
+ provider = validateConfig(config).length === 0 ? createProvider(config) : null;
1150
+ } catch {
1151
+ config = loadConfig();
1152
+ provider = null;
1153
+ }
1154
+ function parseSnapshotMetadata(content) {
1155
+ if (!content.includes("METADATA: IMPORT GRAPH")) {
1156
+ return null;
1157
+ }
1158
+ const importGraph = {};
1159
+ const exportGraph = {};
1160
+ const symbolIndex = {};
1161
+ const exports = [];
1162
+ const importSection = content.match(/METADATA: IMPORT GRAPH\n=+\n([\s\S]*?)\n\n=+\nMETADATA:/)?.[1] || "";
1163
+ for (const block of importSection.split("\n\n")) {
1164
+ const lines = block.trim().split("\n");
1165
+ if (lines.length > 0 && lines[0].endsWith(":")) {
1166
+ const file = lines[0].slice(0, -1);
1167
+ importGraph[file] = lines.slice(1).map((l) => l.replace(/^\s*→\s*/, "").trim()).filter(Boolean);
1168
+ }
1169
+ }
1170
+ const exportSection = content.match(/METADATA: EXPORT INDEX\n=+\n([\s\S]*?)\n\n=+\nMETADATA:/)?.[1] || "";
1171
+ for (const line of exportSection.split("\n")) {
1172
+ const match = line.match(/^([\w$]+):\s*(.+)$/);
1173
+ if (match) {
1174
+ symbolIndex[match[1]] = match[2].split(",").map((s) => s.trim());
1175
+ }
1176
+ }
1177
+ const whoImportsSection = content.match(/METADATA: WHO IMPORTS WHOM\n=+\n([\s\S]*)$/)?.[1] || "";
1178
+ for (const block of whoImportsSection.split("\n\n")) {
1179
+ const lines = block.trim().split("\n");
1180
+ if (lines.length > 0 && lines[0].includes(" is imported by:")) {
1181
+ const file = lines[0].replace(" is imported by:", "").trim();
1182
+ exportGraph[file] = lines.slice(1).map((l) => l.replace(/^\s*←\s*/, "").trim()).filter(Boolean);
1183
+ }
1184
+ }
1185
+ const fileExportsSection = content.match(/METADATA: FILE EXPORTS\n=+\n([\s\S]*?)\n\n=+\nMETADATA:/)?.[1] || "";
1186
+ for (const line of fileExportsSection.split("\n")) {
1187
+ const match = line.match(/^([^:]+):(\d+)\s*-\s*(\w+)\s+(.+)$/);
1188
+ if (match) {
1189
+ exports.push({
1190
+ file: match[1],
1191
+ line: parseInt(match[2]),
1192
+ type: match[3],
1193
+ symbol: match[4].split(" ")[0]
1194
+ // Take first word as symbol name
1195
+ });
1196
+ }
1197
+ }
1198
+ return { importGraph, exportGraph, symbolIndex, exports };
1199
+ }
1200
+ async function handleToolCall(name, args) {
1201
+ switch (name) {
1202
+ case "find_importers": {
1203
+ const path = resolve(args.path);
1204
+ const target = args.target;
1205
+ if (!existsSync3(path)) {
1206
+ throw new Error(`File not found: ${path}`);
1207
+ }
1208
+ const content = readFileSync4(path, "utf-8");
1209
+ const metadata = parseSnapshotMetadata(content);
1210
+ if (!metadata) {
1211
+ throw new Error("This snapshot does not have metadata. Create with: argus snapshot --enhanced");
1212
+ }
1213
+ const normalizedTarget = target.startsWith("./") ? target.slice(2) : target;
1214
+ const targetVariants = [normalizedTarget, "./" + normalizedTarget, normalizedTarget.replace(/\.(ts|tsx|js|jsx)$/, "")];
1215
+ const importers = [];
1216
+ for (const [file, imports] of Object.entries(metadata.importGraph)) {
1217
+ for (const imp of imports) {
1218
+ if (targetVariants.some((v) => imp === v || imp.endsWith("/" + v) || imp.includes(v))) {
1219
+ importers.push(file);
1220
+ break;
1221
+ }
1222
+ }
1223
+ }
1224
+ for (const variant of targetVariants) {
1225
+ if (metadata.exportGraph[variant]) {
1226
+ importers.push(...metadata.exportGraph[variant]);
1227
+ }
1228
+ }
1229
+ const unique = [...new Set(importers)];
1230
+ return {
1231
+ target,
1232
+ importedBy: unique,
1233
+ count: unique.length
1234
+ };
1235
+ }
1236
+ case "find_symbol": {
1237
+ const path = resolve(args.path);
1238
+ const symbol = args.symbol;
1239
+ if (!existsSync3(path)) {
1240
+ throw new Error(`File not found: ${path}`);
1241
+ }
1242
+ const content = readFileSync4(path, "utf-8");
1243
+ const metadata = parseSnapshotMetadata(content);
1244
+ if (!metadata) {
1245
+ throw new Error("This snapshot does not have metadata. Create with: argus snapshot --enhanced");
1246
+ }
1247
+ const files = metadata.symbolIndex[symbol] || [];
1248
+ const exportDetails = metadata.exports.filter((e) => e.symbol === symbol);
1249
+ return {
1250
+ symbol,
1251
+ exportedFrom: files,
1252
+ details: exportDetails,
1253
+ count: files.length
1254
+ };
1255
+ }
1256
+ case "get_file_deps": {
1257
+ const path = resolve(args.path);
1258
+ const file = args.file;
1259
+ if (!existsSync3(path)) {
1260
+ throw new Error(`File not found: ${path}`);
1261
+ }
1262
+ const content = readFileSync4(path, "utf-8");
1263
+ const metadata = parseSnapshotMetadata(content);
1264
+ if (!metadata) {
1265
+ throw new Error("This snapshot does not have metadata. Create with: argus snapshot --enhanced");
1266
+ }
1267
+ const normalizedFile = file.startsWith("./") ? file.slice(2) : file;
1268
+ const fileVariants = [normalizedFile, "./" + normalizedFile];
1269
+ let imports = [];
1270
+ for (const variant of fileVariants) {
1271
+ if (metadata.importGraph[variant]) {
1272
+ imports = metadata.importGraph[variant];
1273
+ break;
1274
+ }
1275
+ }
1276
+ return {
1277
+ file,
1278
+ imports,
1279
+ count: imports.length
1280
+ };
1281
+ }
1282
+ case "analyze_codebase": {
1283
+ if (!provider) {
1284
+ throw new Error("Argus not configured. Run `argus init` to set up.");
1285
+ }
1286
+ const path = resolve(args.path);
1287
+ const query = args.query;
1288
+ const maxTurns = args.maxTurns || 15;
1289
+ if (!existsSync3(path)) {
1290
+ throw new Error(`Path not found: ${path}`);
1291
+ }
1292
+ let snapshotPath = path;
1293
+ let tempSnapshot = false;
1294
+ const stats = statSync2(path);
1295
+ if (stats.isDirectory()) {
1296
+ const tempDir = mkdtempSync(join3(tmpdir(), "argus-"));
1297
+ snapshotPath = join3(tempDir, "snapshot.txt");
1298
+ const result = createSnapshot(path, snapshotPath, {
1299
+ extensions: config.defaults.snapshotExtensions,
1300
+ excludePatterns: config.defaults.excludePatterns
1301
+ });
1302
+ tempSnapshot = true;
1303
+ }
1304
+ try {
1305
+ const result = await analyze(provider, snapshotPath, query, { maxTurns });
1306
+ return {
1307
+ answer: result.answer,
1308
+ success: result.success,
1309
+ turns: result.turns,
1310
+ commands: result.commands
1311
+ };
1312
+ } finally {
1313
+ if (tempSnapshot && existsSync3(snapshotPath)) {
1314
+ unlinkSync(snapshotPath);
1315
+ }
1316
+ }
1317
+ }
1318
+ case "search_codebase": {
1319
+ const path = resolve(args.path);
1320
+ const pattern = args.pattern;
1321
+ const caseInsensitive = args.caseInsensitive || false;
1322
+ const maxResults = args.maxResults || 50;
1323
+ if (!existsSync3(path)) {
1324
+ throw new Error(`File not found: ${path}`);
1325
+ }
1326
+ const matches = searchDocument(path, pattern, { caseInsensitive, maxResults });
1327
+ return {
1328
+ count: matches.length,
1329
+ matches: matches.map((m) => ({
1330
+ lineNum: m.lineNum,
1331
+ line: m.line.trim(),
1332
+ match: m.match
1333
+ }))
1334
+ };
1335
+ }
1336
+ case "create_snapshot": {
1337
+ const path = resolve(args.path);
1338
+ const outputPath = args.outputPath ? resolve(args.outputPath) : join3(tmpdir(), `argus-snapshot-${Date.now()}.txt`);
1339
+ const extensions = args.extensions || config.defaults.snapshotExtensions;
1340
+ if (!existsSync3(path)) {
1341
+ throw new Error(`Path not found: ${path}`);
1342
+ }
1343
+ const result = createSnapshot(path, outputPath, {
1344
+ extensions,
1345
+ excludePatterns: config.defaults.excludePatterns
1346
+ });
1347
+ return {
1348
+ outputPath: result.outputPath,
1349
+ fileCount: result.fileCount,
1350
+ totalLines: result.totalLines,
1351
+ totalSize: result.totalSize
1352
+ };
1353
+ }
1354
+ default:
1355
+ throw new Error(`Unknown tool: ${name}`);
1356
+ }
1357
+ }
1358
+ function handleInitialize() {
1359
+ return {
1360
+ protocolVersion: "2024-11-05",
1361
+ capabilities: {
1362
+ tools: {}
1363
+ },
1364
+ serverInfo: {
1365
+ name: "argus",
1366
+ version: "1.0.0"
1367
+ }
1368
+ };
1369
+ }
1370
+ function handleToolsList() {
1371
+ return { tools: TOOLS };
1372
+ }
1373
+ async function handleToolsCall(params) {
1374
+ try {
1375
+ const result = await handleToolCall(params.name, params.arguments);
1376
+ return {
1377
+ content: [
1378
+ {
1379
+ type: "text",
1380
+ text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
1381
+ }
1382
+ ]
1383
+ };
1384
+ } catch (error) {
1385
+ return {
1386
+ content: [
1387
+ {
1388
+ type: "text",
1389
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
1390
+ }
1391
+ ],
1392
+ isError: true
1393
+ };
1394
+ }
1395
+ }
1396
+ async function handleMessage(request) {
1397
+ try {
1398
+ let result;
1399
+ switch (request.method) {
1400
+ case "initialize":
1401
+ result = handleInitialize();
1402
+ break;
1403
+ case "tools/list":
1404
+ result = handleToolsList();
1405
+ break;
1406
+ case "tools/call":
1407
+ result = await handleToolsCall(request.params);
1408
+ break;
1409
+ case "notifications/initialized":
1410
+ case "notifications/cancelled":
1411
+ return null;
1412
+ default:
1413
+ if (request.id === void 0 || request.id === null) {
1414
+ return null;
1415
+ }
1416
+ return {
1417
+ jsonrpc: "2.0",
1418
+ id: request.id,
1419
+ error: {
1420
+ code: -32601,
1421
+ message: `Method not found: ${request.method}`
1422
+ }
1423
+ };
1424
+ }
1425
+ return {
1426
+ jsonrpc: "2.0",
1427
+ id: request.id,
1428
+ result
1429
+ };
1430
+ } catch (error) {
1431
+ return {
1432
+ jsonrpc: "2.0",
1433
+ id: request.id,
1434
+ error: {
1435
+ code: -32603,
1436
+ message: error instanceof Error ? error.message : String(error)
1437
+ }
1438
+ };
1439
+ }
1440
+ }
1441
+ var rl = createInterface({
1442
+ input: process.stdin,
1443
+ output: process.stdout,
1444
+ terminal: false
1445
+ });
1446
+ rl.on("line", async (line) => {
1447
+ if (!line.trim()) return;
1448
+ try {
1449
+ const request = JSON.parse(line);
1450
+ const response = await handleMessage(request);
1451
+ if (response !== null && response.id !== void 0 && response.id !== null) {
1452
+ console.log(JSON.stringify(response));
1453
+ }
1454
+ } catch (error) {
1455
+ const errorResponse = {
1456
+ jsonrpc: "2.0",
1457
+ id: 0,
1458
+ // Use 0 as fallback id for parse errors
1459
+ error: {
1460
+ code: -32700,
1461
+ message: "Parse error"
1462
+ }
1463
+ };
1464
+ console.log(JSON.stringify(errorResponse));
1465
+ }
1466
+ });
1467
+ //# sourceMappingURL=mcp.mjs.map