@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/index.mjs ADDED
@@ -0,0 +1,1048 @@
1
+ // src/core/config.ts
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+ var DEFAULT_CONFIG = {
6
+ provider: "ollama",
7
+ providers: {
8
+ ollama: {
9
+ baseUrl: "http://localhost:11434",
10
+ model: "qwen2.5-coder:7b"
11
+ }
12
+ },
13
+ defaults: {
14
+ maxTurns: 15,
15
+ turnTimeoutMs: 6e4,
16
+ snapshotExtensions: ["ts", "tsx", "js", "jsx", "rs", "py", "go", "java", "rb", "php", "swift", "kt", "scala", "c", "cpp", "h", "hpp", "cs", "md"],
17
+ excludePatterns: [
18
+ "node_modules",
19
+ ".git",
20
+ "target",
21
+ "dist",
22
+ "build",
23
+ ".next",
24
+ "coverage",
25
+ "__pycache__",
26
+ ".venv",
27
+ "vendor"
28
+ ]
29
+ }
30
+ };
31
+ function getConfigDir() {
32
+ return join(homedir(), ".argus");
33
+ }
34
+ function getConfigPath() {
35
+ return join(getConfigDir(), "config.json");
36
+ }
37
+ function ensureConfigDir() {
38
+ const dir = getConfigDir();
39
+ if (!existsSync(dir)) {
40
+ mkdirSync(dir, { recursive: true });
41
+ }
42
+ }
43
+ function loadConfig() {
44
+ const configPath = getConfigPath();
45
+ if (!existsSync(configPath)) {
46
+ return DEFAULT_CONFIG;
47
+ }
48
+ try {
49
+ const content = readFileSync(configPath, "utf-8");
50
+ const loaded = JSON.parse(content);
51
+ return {
52
+ ...DEFAULT_CONFIG,
53
+ ...loaded,
54
+ providers: {
55
+ ...DEFAULT_CONFIG.providers,
56
+ ...loaded.providers
57
+ },
58
+ defaults: {
59
+ ...DEFAULT_CONFIG.defaults,
60
+ ...loaded.defaults
61
+ }
62
+ };
63
+ } catch {
64
+ return DEFAULT_CONFIG;
65
+ }
66
+ }
67
+ function saveConfig(config) {
68
+ ensureConfigDir();
69
+ const configPath = getConfigPath();
70
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
71
+ }
72
+ function getProviderConfig(config) {
73
+ const providerConfig = config.providers[config.provider];
74
+ if (!providerConfig) {
75
+ throw new Error(`No configuration found for provider: ${config.provider}`);
76
+ }
77
+ return providerConfig;
78
+ }
79
+ function validateConfig(config) {
80
+ const errors = [];
81
+ const providerConfig = config.providers[config.provider];
82
+ if (!providerConfig) {
83
+ errors.push(`Provider "${config.provider}" is not configured`);
84
+ return errors;
85
+ }
86
+ if (config.provider !== "ollama" && !providerConfig.apiKey) {
87
+ errors.push(`API key is required for provider "${config.provider}"`);
88
+ }
89
+ if (!providerConfig.model) {
90
+ errors.push(`Model is required for provider "${config.provider}"`);
91
+ }
92
+ return errors;
93
+ }
94
+ var PROVIDER_DEFAULTS = {
95
+ zai: {
96
+ baseUrl: "https://api.z.ai/api/coding/paas/v4",
97
+ model: "glm-4.7"
98
+ },
99
+ anthropic: {
100
+ baseUrl: "https://api.anthropic.com",
101
+ model: "claude-sonnet-4-20250514"
102
+ },
103
+ openai: {
104
+ baseUrl: "https://api.openai.com/v1",
105
+ model: "gpt-4o"
106
+ },
107
+ deepseek: {
108
+ baseUrl: "https://api.deepseek.com",
109
+ model: "deepseek-chat"
110
+ },
111
+ ollama: {
112
+ baseUrl: "http://localhost:11434",
113
+ model: "qwen2.5-coder:7b"
114
+ }
115
+ };
116
+
117
+ // src/core/snapshot.ts
118
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
119
+ import { join as join2, relative, extname } from "path";
120
+ var DEFAULT_OPTIONS = {
121
+ extensions: ["ts", "tsx", "js", "jsx", "rs", "py", "go", "java", "rb", "php", "swift", "kt", "scala", "c", "cpp", "h", "hpp", "cs", "md", "json"],
122
+ excludePatterns: [
123
+ "node_modules",
124
+ ".git",
125
+ "target",
126
+ "dist",
127
+ "build",
128
+ ".next",
129
+ "coverage",
130
+ "__pycache__",
131
+ ".venv",
132
+ "vendor",
133
+ ".DS_Store",
134
+ "*.lock",
135
+ "package-lock.json",
136
+ "*.min.js",
137
+ "*.min.css"
138
+ ],
139
+ maxFileSize: 1024 * 1024,
140
+ // 1MB
141
+ includeHidden: false
142
+ };
143
+ function shouldExclude(filePath, patterns) {
144
+ const normalizedPath = filePath.replace(/\\/g, "/");
145
+ for (const pattern of patterns) {
146
+ if (pattern.startsWith("*")) {
147
+ const suffix = pattern.slice(1);
148
+ if (normalizedPath.endsWith(suffix)) return true;
149
+ } else if (normalizedPath.includes(`/${pattern}/`) || normalizedPath.endsWith(`/${pattern}`) || normalizedPath === pattern) {
150
+ return true;
151
+ }
152
+ }
153
+ return false;
154
+ }
155
+ function hasValidExtension(filePath, extensions) {
156
+ const ext = extname(filePath).slice(1).toLowerCase();
157
+ return extensions.includes(ext);
158
+ }
159
+ function collectFiles(dir, options, baseDir = dir) {
160
+ const files = [];
161
+ try {
162
+ const entries = readdirSync(dir, { withFileTypes: true });
163
+ for (const entry of entries) {
164
+ const fullPath = join2(dir, entry.name);
165
+ const relativePath = relative(baseDir, fullPath);
166
+ if (!options.includeHidden && entry.name.startsWith(".")) {
167
+ continue;
168
+ }
169
+ if (shouldExclude(relativePath, options.excludePatterns)) {
170
+ continue;
171
+ }
172
+ if (entry.isDirectory()) {
173
+ files.push(...collectFiles(fullPath, options, baseDir));
174
+ } else if (entry.isFile()) {
175
+ if (!hasValidExtension(entry.name, options.extensions)) {
176
+ continue;
177
+ }
178
+ try {
179
+ const stats = statSync(fullPath);
180
+ if (stats.size > options.maxFileSize) {
181
+ continue;
182
+ }
183
+ } catch {
184
+ continue;
185
+ }
186
+ files.push(fullPath);
187
+ }
188
+ }
189
+ } catch (error) {
190
+ }
191
+ return files.sort();
192
+ }
193
+ function createSnapshot(projectPath, outputPath, options = {}) {
194
+ const mergedOptions = {
195
+ ...DEFAULT_OPTIONS,
196
+ ...options
197
+ };
198
+ if (!existsSync2(projectPath)) {
199
+ throw new Error(`Project path does not exist: ${projectPath}`);
200
+ }
201
+ const stats = statSync(projectPath);
202
+ if (!stats.isDirectory()) {
203
+ throw new Error(`Project path is not a directory: ${projectPath}`);
204
+ }
205
+ const files = collectFiles(projectPath, mergedOptions);
206
+ const lines = [];
207
+ lines.push("================================================================================");
208
+ lines.push("CODEBASE SNAPSHOT");
209
+ lines.push(`Project: ${projectPath}`);
210
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
211
+ lines.push(`Extensions: ${mergedOptions.extensions.join(", ")}`);
212
+ lines.push(`Files: ${files.length}`);
213
+ lines.push("================================================================================");
214
+ lines.push("");
215
+ for (const filePath of files) {
216
+ const relativePath = relative(projectPath, filePath);
217
+ lines.push("");
218
+ lines.push("================================================================================");
219
+ lines.push(`FILE: ./${relativePath}`);
220
+ lines.push("================================================================================");
221
+ try {
222
+ const content2 = readFileSync2(filePath, "utf-8");
223
+ lines.push(content2);
224
+ } catch (error) {
225
+ lines.push("[Unable to read file]");
226
+ }
227
+ }
228
+ const content = lines.join("\n");
229
+ writeFileSync2(outputPath, content);
230
+ const totalLines = content.split("\n").length;
231
+ const totalSize = Buffer.byteLength(content, "utf-8");
232
+ return {
233
+ outputPath,
234
+ fileCount: files.length,
235
+ totalLines,
236
+ totalSize,
237
+ files: files.map((f) => relative(projectPath, f))
238
+ };
239
+ }
240
+ function getSnapshotStats(snapshotPath) {
241
+ if (!existsSync2(snapshotPath)) {
242
+ throw new Error(`Snapshot file does not exist: ${snapshotPath}`);
243
+ }
244
+ const content = readFileSync2(snapshotPath, "utf-8");
245
+ const totalLines = content.split("\n").length;
246
+ const totalSize = Buffer.byteLength(content, "utf-8");
247
+ const fileMatches = content.match(/^FILE: /gm);
248
+ const fileCount = fileMatches ? fileMatches.length : 0;
249
+ return { fileCount, totalLines, totalSize };
250
+ }
251
+
252
+ // src/core/engine.ts
253
+ import { readFileSync as readFileSync3 } from "fs";
254
+
255
+ // src/core/prompts.ts
256
+ var NUCLEUS_COMMANDS = `
257
+ COMMANDS (output ONE per turn):
258
+ (grep "pattern") - Find lines matching regex
259
+ (grep "pattern" "i") - Case-insensitive search
260
+ (count RESULTS) - Count matches
261
+ (take RESULTS n) - First n results
262
+ (filter RESULTS (lambda (x) (match x.line "pattern" 0))) - Filter results
263
+ (map RESULTS (lambda (x) x.line)) - Extract just the lines
264
+
265
+ VARIABLES: RESULTS = last result, _1 _2 _3 = results from turn 1,2,3
266
+
267
+ TO ANSWER: <<<FINAL>>>your answer<<<END>>>
268
+ `;
269
+ var CODEBASE_ANALYSIS_PROMPT = `You are analyzing a SOFTWARE CODEBASE snapshot to help a developer understand it.
270
+
271
+ The snapshot contains source files concatenated with "FILE: ./path/to/file" markers.
272
+
273
+ ${NUCLEUS_COMMANDS}
274
+
275
+ ## STRATEGY FOR CODEBASE SNAPSHOTS
276
+
277
+ **To find modules/directories:**
278
+ (grep "FILE:.*src/[^/]+/") - top-level source dirs
279
+ (grep "FILE:.*mod\\.rs") - Rust modules
280
+ (grep "FILE:.*index\\.(ts|js)") - JS/TS modules
281
+
282
+ **To find implementations:**
283
+ (grep "fn function_name") - Rust functions
284
+ (grep "function|const.*=>") - JS functions
285
+ (grep "class ClassName") - Classes
286
+ (grep "struct |type |interface") - Type definitions
287
+
288
+ **To understand structure:**
289
+ (grep "FILE:") - List all files
290
+ (grep "use |import |require") - Find dependencies
291
+ (grep "pub |export") - Public APIs
292
+
293
+ ## RULES
294
+ 1. Output ONLY a Nucleus command OR a final answer
295
+ 2. NO explanations, NO markdown formatting in commands
296
+ 3. MUST provide final answer by turn 8
297
+ 4. If turn 6+, start summarizing what you found
298
+
299
+ ## EXAMPLE SESSION
300
+ Turn 1: (grep "FILE:.*src/[^/]+/mod\\.rs")
301
+ Turn 2: (take RESULTS 15)
302
+ Turn 3: <<<FINAL>>>The codebase has these main modules:
303
+ - src/auth/ - Authentication handling
304
+ - src/api/ - API endpoints
305
+ - src/db/ - Database layer
306
+ ...<<<END>>>
307
+ `;
308
+ var ARCHITECTURE_PROMPT = `You are generating an ARCHITECTURE SUMMARY of a codebase.
309
+
310
+ ${NUCLEUS_COMMANDS}
311
+
312
+ ## YOUR TASK
313
+ Create a summary suitable for CLAUDE.md that helps Claude Code understand this project after context compaction.
314
+
315
+ ## SEARCH STRATEGY (do these in order)
316
+ 1. (grep "FILE:.*mod\\.rs|FILE:.*index\\.(ts|js)") - Find module entry points
317
+ 2. (take RESULTS 20) - Limit results
318
+ 3. Based on file paths, provide your summary
319
+
320
+ ## OUTPUT FORMAT
321
+ Your final answer should be structured like:
322
+
323
+ ## Modules
324
+ - **module_name/** - Brief description based on files found
325
+
326
+ ## Key Patterns
327
+ - Pattern observations from the code
328
+
329
+ ## Important Files
330
+ - List key files and their apparent purpose
331
+
332
+ PROVIDE FINAL ANSWER BY TURN 6.
333
+ `;
334
+ var IMPLEMENTATION_PROMPT = `You are finding HOW something works in a codebase.
335
+
336
+ ${NUCLEUS_COMMANDS}
337
+
338
+ ## STRATEGY
339
+ 1. (grep "FILE:.*keyword") - Find files related to the concept
340
+ 2. (grep "keyword") - Find all mentions
341
+ 3. (take RESULTS 30) - Limit if too many results
342
+ 4. Look for function definitions, structs, classes
343
+ 5. PROVIDE FINAL ANSWER based on file paths and code patterns found
344
+
345
+ ## IMPORTANT
346
+ - You have 12 turns maximum
347
+ - By turn 8, START WRITING YOUR FINAL ANSWER
348
+ - Use what you've found - don't keep searching indefinitely
349
+ - It's better to give a partial answer than no answer
350
+
351
+ ## OUTPUT FORMAT
352
+ Your final answer should explain:
353
+ - Which files contain the implementation
354
+ - Key functions/structs/classes involved
355
+ - Basic flow of how it works (based on what you found)
356
+ `;
357
+ var COUNT_PROMPT = `You are counting items in a codebase.
358
+
359
+ ${NUCLEUS_COMMANDS}
360
+
361
+ ## STRATEGY
362
+ 1. (grep "pattern")
363
+ 2. (count RESULTS)
364
+ 3. <<<FINAL>>>There are N items matching the pattern.<<<END>>>
365
+
366
+ THIS SHOULD TAKE 2-3 TURNS MAXIMUM.
367
+ `;
368
+ var SEARCH_PROMPT = `You are searching for specific code.
369
+
370
+ ${NUCLEUS_COMMANDS}
371
+
372
+ ## STRATEGY
373
+ 1. (grep "pattern")
374
+ 2. (take RESULTS 20) if too many
375
+ 3. Report what you found with file paths
376
+
377
+ PROVIDE FINAL ANSWER BY TURN 4.
378
+ `;
379
+ function selectPrompt(query) {
380
+ const q = query.toLowerCase();
381
+ if (/how many|count|number of|total|how much/.test(q)) {
382
+ return COUNT_PROMPT;
383
+ }
384
+ if (/^(find|search|show|list|where is|locate)\b/.test(q) && q.length < 50) {
385
+ return SEARCH_PROMPT;
386
+ }
387
+ if (/architect|structure|overview|module|organization|main.*component|summar|layout/.test(q)) {
388
+ return ARCHITECTURE_PROMPT;
389
+ }
390
+ if (/how does|how is|implement|work|handle|process|flow/.test(q)) {
391
+ return IMPLEMENTATION_PROMPT;
392
+ }
393
+ return CODEBASE_ANALYSIS_PROMPT;
394
+ }
395
+ function buildSystemPrompt(query) {
396
+ return selectPrompt(query);
397
+ }
398
+ function getTurnLimit(query) {
399
+ const q = query.toLowerCase();
400
+ if (/how many|count/.test(q)) return 5;
401
+ if (/^(find|search|show|list)\b/.test(q) && q.length < 50) return 6;
402
+ if (/architect|overview|structure|module/.test(q)) return 12;
403
+ if (/how does|how is|implement|work/.test(q)) return 12;
404
+ return 12;
405
+ }
406
+
407
+ // src/core/engine.ts
408
+ function executeNucleus(command, content, bindings) {
409
+ const parsed = parseSExpression(command);
410
+ if (!parsed) {
411
+ throw new Error(`Failed to parse command: ${command}`);
412
+ }
413
+ return evaluateExpr(parsed, content, bindings);
414
+ }
415
+ function parseSExpression(input) {
416
+ const tokens = tokenize(input.trim());
417
+ if (tokens.length === 0) return null;
418
+ let pos = 0;
419
+ function parse() {
420
+ const token = tokens[pos++];
421
+ if (token === "(") {
422
+ const list = [];
423
+ while (tokens[pos] !== ")" && pos < tokens.length) {
424
+ list.push(parse());
425
+ }
426
+ pos++;
427
+ return list;
428
+ } else if (token.startsWith('"')) {
429
+ return token.slice(1, -1).replace(/\\"/g, '"');
430
+ } else if (/^-?\d+(\.\d+)?$/.test(token)) {
431
+ return token;
432
+ } else {
433
+ return token;
434
+ }
435
+ }
436
+ return parse();
437
+ }
438
+ function tokenize(input) {
439
+ const tokens = [];
440
+ let i = 0;
441
+ while (i < input.length) {
442
+ const char = input[i];
443
+ if (/\s/.test(char)) {
444
+ i++;
445
+ continue;
446
+ }
447
+ if (char === "(" || char === ")") {
448
+ tokens.push(char);
449
+ i++;
450
+ continue;
451
+ }
452
+ if (char === '"') {
453
+ let str = '"';
454
+ i++;
455
+ while (i < input.length && input[i] !== '"') {
456
+ if (input[i] === "\\" && i + 1 < input.length) {
457
+ str += input[i] + input[i + 1];
458
+ i += 2;
459
+ } else {
460
+ str += input[i];
461
+ i++;
462
+ }
463
+ }
464
+ str += '"';
465
+ i++;
466
+ tokens.push(str);
467
+ continue;
468
+ }
469
+ let sym = "";
470
+ while (i < input.length && !/[\s()]/.test(input[i])) {
471
+ sym += input[i];
472
+ i++;
473
+ }
474
+ tokens.push(sym);
475
+ }
476
+ return tokens;
477
+ }
478
+ function evaluateExpr(expr, content, bindings) {
479
+ if (typeof expr === "string") {
480
+ if (bindings.has(expr)) {
481
+ return bindings.get(expr);
482
+ }
483
+ if (/^-?\d+(\.\d+)?$/.test(expr)) {
484
+ return parseFloat(expr);
485
+ }
486
+ return expr;
487
+ }
488
+ if (!Array.isArray(expr) || expr.length === 0) {
489
+ return expr;
490
+ }
491
+ const [op, ...args] = expr;
492
+ switch (op) {
493
+ case "grep": {
494
+ const pattern = evaluateExpr(args[0], content, bindings);
495
+ const flags = args[1] ? evaluateExpr(args[1], content, bindings) : "";
496
+ const regex = new RegExp(pattern, flags + "g");
497
+ const lines = content.split("\n");
498
+ const matches = [];
499
+ let charIndex = 0;
500
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
501
+ const line = lines[lineNum];
502
+ let match;
503
+ const lineRegex = new RegExp(pattern, flags + "g");
504
+ while ((match = lineRegex.exec(line)) !== null) {
505
+ matches.push({
506
+ match: match[0],
507
+ line,
508
+ lineNum: lineNum + 1,
509
+ index: charIndex + match.index,
510
+ groups: match.slice(1)
511
+ });
512
+ }
513
+ charIndex += line.length + 1;
514
+ }
515
+ return matches;
516
+ }
517
+ case "count": {
518
+ const arr = evaluateExpr(args[0], content, bindings);
519
+ if (Array.isArray(arr)) return arr.length;
520
+ return 0;
521
+ }
522
+ case "map": {
523
+ const arr = evaluateExpr(args[0], content, bindings);
524
+ const lambdaExpr = args[1];
525
+ if (!Array.isArray(lambdaExpr) || lambdaExpr[0] !== "lambda") {
526
+ throw new Error("map requires a lambda expression");
527
+ }
528
+ const params = lambdaExpr[1];
529
+ const body = lambdaExpr[2];
530
+ const paramName = Array.isArray(params) ? params[0] : params;
531
+ return arr.map((item) => {
532
+ const localBindings = new Map(bindings);
533
+ localBindings.set(paramName, item);
534
+ return evaluateExpr(body, content, localBindings);
535
+ });
536
+ }
537
+ case "filter": {
538
+ const arr = evaluateExpr(args[0], content, bindings);
539
+ const lambdaExpr = args[1];
540
+ if (!Array.isArray(lambdaExpr) || lambdaExpr[0] !== "lambda") {
541
+ throw new Error("filter requires a lambda expression");
542
+ }
543
+ const params = lambdaExpr[1];
544
+ const body = lambdaExpr[2];
545
+ const paramName = Array.isArray(params) ? params[0] : params;
546
+ return arr.filter((item) => {
547
+ const localBindings = new Map(bindings);
548
+ localBindings.set(paramName, item);
549
+ return evaluateExpr(body, content, localBindings);
550
+ });
551
+ }
552
+ case "first": {
553
+ const arr = evaluateExpr(args[0], content, bindings);
554
+ return arr[0];
555
+ }
556
+ case "last": {
557
+ const arr = evaluateExpr(args[0], content, bindings);
558
+ return arr[arr.length - 1];
559
+ }
560
+ case "take": {
561
+ const arr = evaluateExpr(args[0], content, bindings);
562
+ const n = evaluateExpr(args[1], content, bindings);
563
+ return arr.slice(0, n);
564
+ }
565
+ case "sort": {
566
+ const arr = evaluateExpr(args[0], content, bindings);
567
+ const key = evaluateExpr(args[1], content, bindings);
568
+ return [...arr].sort((a, b) => {
569
+ const aVal = a[key];
570
+ const bVal = b[key];
571
+ if (typeof aVal === "number" && typeof bVal === "number") {
572
+ return aVal - bVal;
573
+ }
574
+ return String(aVal).localeCompare(String(bVal));
575
+ });
576
+ }
577
+ case "match": {
578
+ const str = evaluateExpr(args[0], content, bindings);
579
+ const strValue = typeof str === "object" && str !== null && "line" in str ? str.line : String(str);
580
+ const pattern = evaluateExpr(args[1], content, bindings);
581
+ const group = args[2] ? evaluateExpr(args[2], content, bindings) : 0;
582
+ const regex = new RegExp(pattern);
583
+ const match = strValue.match(regex);
584
+ if (match) {
585
+ return match[group] || null;
586
+ }
587
+ return null;
588
+ }
589
+ default:
590
+ throw new Error(`Unknown command: ${op}`);
591
+ }
592
+ }
593
+ function extractCommand(response) {
594
+ const finalMatch = response.match(/<<<FINAL>>>([\s\S]*?)<<<END>>>/);
595
+ if (finalMatch) {
596
+ return { finalAnswer: finalMatch[1].trim() };
597
+ }
598
+ const sexpMatch = response.match(/\([^)]*(?:\([^)]*\)[^)]*)*\)/);
599
+ if (sexpMatch) {
600
+ return { command: sexpMatch[0] };
601
+ }
602
+ return {};
603
+ }
604
+ async function analyze(provider, documentPath, query, options = {}) {
605
+ const {
606
+ maxTurns = 15,
607
+ verbose = false,
608
+ onProgress
609
+ } = options;
610
+ const dynamicLimit = Math.min(getTurnLimit(query), maxTurns);
611
+ const content = readFileSync3(documentPath, "utf-8");
612
+ const fileCount = (content.match(/^FILE:/gm) || []).length;
613
+ const lineCount = content.split("\n").length;
614
+ const bindings = /* @__PURE__ */ new Map();
615
+ const commands = [];
616
+ const messages = [
617
+ {
618
+ role: "system",
619
+ content: buildSystemPrompt(query)
620
+ },
621
+ {
622
+ role: "user",
623
+ content: `CODEBASE SNAPSHOT:
624
+ - Total size: ${content.length.toLocaleString()} characters
625
+ - Files: ${fileCount}
626
+ - Lines: ${lineCount.toLocaleString()}
627
+
628
+ Files are marked with "FILE: ./path/to/file" headers.
629
+
630
+ QUERY: ${query}
631
+
632
+ Begin analysis. You have ${dynamicLimit} turns maximum - provide final answer before then.`
633
+ }
634
+ ];
635
+ for (let turn = 1; turn <= dynamicLimit; turn++) {
636
+ const isLastTurn = turn === dynamicLimit;
637
+ const isNearEnd = turn >= dynamicLimit - 2;
638
+ if (verbose) {
639
+ console.log(`
640
+ [Turn ${turn}/${dynamicLimit}] Querying LLM...`);
641
+ }
642
+ const result = await provider.complete(messages);
643
+ const response = result.content;
644
+ if (verbose) {
645
+ console.log(`[Turn ${turn}] Response: ${response.slice(0, 200)}...`);
646
+ }
647
+ const extracted = extractCommand(response);
648
+ if (extracted.finalAnswer) {
649
+ return {
650
+ answer: extracted.finalAnswer,
651
+ turns: turn,
652
+ commands,
653
+ success: true
654
+ };
655
+ }
656
+ if (!extracted.command) {
657
+ messages.push({ role: "assistant", content: response });
658
+ messages.push({ role: "user", content: "Please provide a Nucleus command or final answer." });
659
+ continue;
660
+ }
661
+ const command = extracted.command;
662
+ commands.push(command);
663
+ if (verbose) {
664
+ console.log(`[Turn ${turn}] Command: ${command}`);
665
+ }
666
+ try {
667
+ const cmdResult = executeNucleus(command, content, bindings);
668
+ bindings.set("RESULTS", cmdResult);
669
+ bindings.set(`_${turn}`, cmdResult);
670
+ const resultStr = JSON.stringify(cmdResult, null, 2);
671
+ const truncatedResult = resultStr.length > 2e3 ? resultStr.slice(0, 2e3) + "...[truncated]" : resultStr;
672
+ if (verbose) {
673
+ console.log(`[Turn ${turn}] Result: ${truncatedResult.slice(0, 500)}...`);
674
+ }
675
+ onProgress?.(turn, command, cmdResult);
676
+ messages.push({ role: "assistant", content: command });
677
+ let userMessage = `Result:
678
+ ${truncatedResult}`;
679
+ if (isNearEnd && !isLastTurn) {
680
+ userMessage += `
681
+
682
+ \u26A0\uFE0F ${dynamicLimit - turn} turns remaining. Start forming your final answer.`;
683
+ }
684
+ messages.push({ role: "user", content: userMessage });
685
+ if (isLastTurn) {
686
+ messages.push({
687
+ role: "user",
688
+ content: "STOP SEARCHING. Based on everything you found, provide your final answer NOW using <<<FINAL>>>your answer<<<END>>>"
689
+ });
690
+ const finalResult = await provider.complete(messages);
691
+ const finalExtracted = extractCommand(finalResult.content);
692
+ if (finalExtracted.finalAnswer) {
693
+ return {
694
+ answer: finalExtracted.finalAnswer,
695
+ turns: turn,
696
+ commands,
697
+ success: true
698
+ };
699
+ }
700
+ return {
701
+ answer: finalResult.content,
702
+ turns: turn,
703
+ commands,
704
+ success: true
705
+ };
706
+ }
707
+ } catch (error) {
708
+ const errMsg = error instanceof Error ? error.message : String(error);
709
+ if (verbose) {
710
+ console.log(`[Turn ${turn}] Error: ${errMsg}`);
711
+ }
712
+ messages.push({ role: "assistant", content: command });
713
+ messages.push({ role: "user", content: `Error executing command: ${errMsg}` });
714
+ }
715
+ }
716
+ return {
717
+ answer: "Maximum turns reached without final answer",
718
+ turns: dynamicLimit,
719
+ commands,
720
+ success: false,
721
+ error: "Max turns reached"
722
+ };
723
+ }
724
+ function searchDocument(documentPath, pattern, options = {}) {
725
+ const content = readFileSync3(documentPath, "utf-8");
726
+ const flags = options.caseInsensitive ? "gi" : "g";
727
+ const regex = new RegExp(pattern, flags);
728
+ const lines = content.split("\n");
729
+ const matches = [];
730
+ let charIndex = 0;
731
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
732
+ const line = lines[lineNum];
733
+ let match;
734
+ const lineRegex = new RegExp(pattern, flags);
735
+ while ((match = lineRegex.exec(line)) !== null) {
736
+ matches.push({
737
+ match: match[0],
738
+ line,
739
+ lineNum: lineNum + 1,
740
+ index: charIndex + match.index,
741
+ groups: match.slice(1)
742
+ });
743
+ if (options.maxResults && matches.length >= options.maxResults) {
744
+ return matches;
745
+ }
746
+ }
747
+ charIndex += line.length + 1;
748
+ }
749
+ return matches;
750
+ }
751
+
752
+ // src/providers/openai-compatible.ts
753
+ var OpenAICompatibleProvider = class {
754
+ name;
755
+ config;
756
+ constructor(name, config) {
757
+ this.name = name;
758
+ this.config = config;
759
+ if (!config.apiKey) {
760
+ throw new Error(`API key is required for ${name} provider`);
761
+ }
762
+ if (!config.baseUrl) {
763
+ throw new Error(`Base URL is required for ${name} provider`);
764
+ }
765
+ }
766
+ async complete(messages, options) {
767
+ const endpoint = `${this.config.baseUrl}/chat/completions`;
768
+ const body = {
769
+ model: this.config.model,
770
+ messages: messages.map((m) => ({
771
+ role: m.role,
772
+ content: m.content
773
+ })),
774
+ temperature: options?.temperature ?? this.config.options?.temperature ?? 0.2,
775
+ max_tokens: options?.maxTokens ?? this.config.options?.max_tokens ?? 4096,
776
+ ...options?.stopSequences && { stop: options.stopSequences }
777
+ };
778
+ const response = await fetch(endpoint, {
779
+ method: "POST",
780
+ headers: {
781
+ "Content-Type": "application/json",
782
+ "Authorization": `Bearer ${this.config.apiKey}`
783
+ },
784
+ body: JSON.stringify(body)
785
+ });
786
+ if (!response.ok) {
787
+ const errorText = await response.text();
788
+ throw new Error(`${this.name} API error (${response.status}): ${errorText}`);
789
+ }
790
+ const data = await response.json();
791
+ const choice = data.choices[0];
792
+ return {
793
+ content: choice.message.content || "",
794
+ finishReason: choice.finish_reason === "stop" ? "stop" : choice.finish_reason === "length" ? "length" : "error",
795
+ usage: data.usage ? {
796
+ promptTokens: data.usage.prompt_tokens,
797
+ completionTokens: data.usage.completion_tokens,
798
+ totalTokens: data.usage.total_tokens
799
+ } : void 0
800
+ };
801
+ }
802
+ async healthCheck() {
803
+ try {
804
+ const result = await this.complete([
805
+ { role: "user", content: 'Say "ok"' }
806
+ ], { maxTokens: 10 });
807
+ return result.content.length > 0;
808
+ } catch {
809
+ return false;
810
+ }
811
+ }
812
+ };
813
+ function createZAIProvider(config) {
814
+ return new OpenAICompatibleProvider("ZAI", {
815
+ ...config,
816
+ baseUrl: config.baseUrl || "https://api.z.ai/api/coding/paas/v4",
817
+ model: config.model || "glm-4.7"
818
+ });
819
+ }
820
+ function createOpenAIProvider(config) {
821
+ return new OpenAICompatibleProvider("OpenAI", {
822
+ ...config,
823
+ baseUrl: config.baseUrl || "https://api.openai.com/v1",
824
+ model: config.model || "gpt-4o"
825
+ });
826
+ }
827
+ function createDeepSeekProvider(config) {
828
+ return new OpenAICompatibleProvider("DeepSeek", {
829
+ ...config,
830
+ baseUrl: config.baseUrl || "https://api.deepseek.com",
831
+ model: config.model || "deepseek-chat"
832
+ });
833
+ }
834
+
835
+ // src/providers/ollama.ts
836
+ var OllamaProvider = class {
837
+ name = "Ollama";
838
+ config;
839
+ constructor(config) {
840
+ this.config = {
841
+ ...config,
842
+ baseUrl: config.baseUrl || "http://localhost:11434",
843
+ model: config.model || "qwen2.5-coder:7b"
844
+ };
845
+ }
846
+ async complete(messages, options) {
847
+ const endpoint = `${this.config.baseUrl}/api/chat`;
848
+ const body = {
849
+ model: this.config.model,
850
+ messages: messages.map((m) => ({
851
+ role: m.role,
852
+ content: m.content
853
+ })),
854
+ stream: false,
855
+ options: {
856
+ temperature: options?.temperature ?? this.config.options?.temperature ?? 0.2,
857
+ num_ctx: this.config.options?.num_ctx ?? 8192
858
+ }
859
+ };
860
+ const response = await fetch(endpoint, {
861
+ method: "POST",
862
+ headers: {
863
+ "Content-Type": "application/json"
864
+ },
865
+ body: JSON.stringify(body)
866
+ });
867
+ if (!response.ok) {
868
+ const errorText = await response.text();
869
+ throw new Error(`Ollama API error (${response.status}): ${errorText}`);
870
+ }
871
+ const data = await response.json();
872
+ return {
873
+ content: data.message.content || "",
874
+ finishReason: data.done ? "stop" : "error",
875
+ usage: data.eval_count ? {
876
+ promptTokens: data.prompt_eval_count || 0,
877
+ completionTokens: data.eval_count,
878
+ totalTokens: (data.prompt_eval_count || 0) + data.eval_count
879
+ } : void 0
880
+ };
881
+ }
882
+ async healthCheck() {
883
+ try {
884
+ const response = await fetch(`${this.config.baseUrl}/api/tags`);
885
+ if (!response.ok) return false;
886
+ const data = await response.json();
887
+ const hasModel = data.models.some(
888
+ (m) => m.name === this.config.model || m.name.startsWith(this.config.model + ":")
889
+ );
890
+ return hasModel;
891
+ } catch {
892
+ return false;
893
+ }
894
+ }
895
+ /**
896
+ * List available models
897
+ */
898
+ async listModels() {
899
+ try {
900
+ const response = await fetch(`${this.config.baseUrl}/api/tags`);
901
+ if (!response.ok) return [];
902
+ const data = await response.json();
903
+ return data.models.map((m) => m.name);
904
+ } catch {
905
+ return [];
906
+ }
907
+ }
908
+ };
909
+ function createOllamaProvider(config) {
910
+ return new OllamaProvider(config);
911
+ }
912
+
913
+ // src/providers/anthropic.ts
914
+ var AnthropicProvider = class {
915
+ name = "Anthropic";
916
+ config;
917
+ constructor(config) {
918
+ if (!config.apiKey) {
919
+ throw new Error("API key is required for Anthropic provider");
920
+ }
921
+ this.config = {
922
+ ...config,
923
+ baseUrl: config.baseUrl || "https://api.anthropic.com",
924
+ model: config.model || "claude-sonnet-4-20250514"
925
+ };
926
+ }
927
+ async complete(messages, options) {
928
+ const endpoint = `${this.config.baseUrl}/v1/messages`;
929
+ const systemMessage = messages.find((m) => m.role === "system");
930
+ const nonSystemMessages = messages.filter((m) => m.role !== "system");
931
+ const body = {
932
+ model: this.config.model,
933
+ max_tokens: options?.maxTokens ?? this.config.options?.max_tokens ?? 4096,
934
+ ...systemMessage && { system: systemMessage.content },
935
+ messages: nonSystemMessages.map((m) => ({
936
+ role: m.role,
937
+ content: m.content
938
+ })),
939
+ ...options?.temperature !== void 0 && { temperature: options.temperature },
940
+ ...options?.stopSequences && { stop_sequences: options.stopSequences }
941
+ };
942
+ const response = await fetch(endpoint, {
943
+ method: "POST",
944
+ headers: {
945
+ "Content-Type": "application/json",
946
+ "x-api-key": this.config.apiKey,
947
+ "anthropic-version": "2023-06-01"
948
+ },
949
+ body: JSON.stringify(body)
950
+ });
951
+ if (!response.ok) {
952
+ const errorText = await response.text();
953
+ throw new Error(`Anthropic API error (${response.status}): ${errorText}`);
954
+ }
955
+ const data = await response.json();
956
+ const textContent = data.content.filter((c) => c.type === "text").map((c) => c.text).join("");
957
+ return {
958
+ content: textContent,
959
+ finishReason: data.stop_reason === "end_turn" ? "stop" : data.stop_reason === "max_tokens" ? "length" : "error",
960
+ usage: {
961
+ promptTokens: data.usage.input_tokens,
962
+ completionTokens: data.usage.output_tokens,
963
+ totalTokens: data.usage.input_tokens + data.usage.output_tokens
964
+ }
965
+ };
966
+ }
967
+ async healthCheck() {
968
+ try {
969
+ const result = await this.complete([
970
+ { role: "user", content: 'Say "ok"' }
971
+ ], { maxTokens: 10 });
972
+ return result.content.length > 0;
973
+ } catch {
974
+ return false;
975
+ }
976
+ }
977
+ };
978
+ function createAnthropicProvider(config) {
979
+ return new AnthropicProvider(config);
980
+ }
981
+
982
+ // src/providers/index.ts
983
+ function createProvider(config) {
984
+ const providerType = config.provider;
985
+ const providerConfig = config.providers[providerType];
986
+ if (!providerConfig) {
987
+ throw new Error(`No configuration found for provider: ${providerType}`);
988
+ }
989
+ return createProviderByType(providerType, providerConfig);
990
+ }
991
+ function createProviderByType(type, config) {
992
+ switch (type) {
993
+ case "zai":
994
+ return createZAIProvider(config);
995
+ case "openai":
996
+ return createOpenAIProvider(config);
997
+ case "deepseek":
998
+ return createDeepSeekProvider(config);
999
+ case "ollama":
1000
+ return createOllamaProvider(config);
1001
+ case "anthropic":
1002
+ return createAnthropicProvider(config);
1003
+ default:
1004
+ throw new Error(`Unknown provider type: ${type}`);
1005
+ }
1006
+ }
1007
+ function getProviderDisplayName(type) {
1008
+ switch (type) {
1009
+ case "zai":
1010
+ return "ZAI (GLM)";
1011
+ case "openai":
1012
+ return "OpenAI";
1013
+ case "deepseek":
1014
+ return "DeepSeek";
1015
+ case "ollama":
1016
+ return "Ollama (Local)";
1017
+ case "anthropic":
1018
+ return "Anthropic (Claude)";
1019
+ default:
1020
+ return type;
1021
+ }
1022
+ }
1023
+ function listProviderTypes() {
1024
+ return ["zai", "anthropic", "openai", "deepseek", "ollama"];
1025
+ }
1026
+ export {
1027
+ PROVIDER_DEFAULTS,
1028
+ analyze,
1029
+ createAnthropicProvider,
1030
+ createDeepSeekProvider,
1031
+ createOllamaProvider,
1032
+ createOpenAIProvider,
1033
+ createProvider,
1034
+ createProviderByType,
1035
+ createSnapshot,
1036
+ createZAIProvider,
1037
+ ensureConfigDir,
1038
+ getConfigPath,
1039
+ getProviderConfig,
1040
+ getProviderDisplayName,
1041
+ getSnapshotStats,
1042
+ listProviderTypes,
1043
+ loadConfig,
1044
+ saveConfig,
1045
+ searchDocument,
1046
+ validateConfig
1047
+ };
1048
+ //# sourceMappingURL=index.mjs.map