@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/LICENSE +21 -0
- package/README.md +323 -0
- package/dist/cli.mjs +2796 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +241 -0
- package/dist/index.mjs +1048 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mcp.mjs +1467 -0
- package/dist/mcp.mjs.map +1 -0
- package/package.json +74 -0
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
|