@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/cli.mjs ADDED
@@ -0,0 +1,2796 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // node_modules/tsup/assets/esm_shims.js
13
+ var init_esm_shims = __esm({
14
+ "node_modules/tsup/assets/esm_shims.js"() {
15
+ "use strict";
16
+ }
17
+ });
18
+
19
+ // src/core/onboarding-ui.tsx
20
+ var onboarding_ui_exports = {};
21
+ __export(onboarding_ui_exports, {
22
+ renderGlobalOnboarding: () => renderGlobalOnboarding,
23
+ renderProjectOnboarding: () => renderProjectOnboarding
24
+ });
25
+ import React, { useState, useCallback } from "react";
26
+ import { render, Box, Text, useInput, useApp } from "ink";
27
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
28
+ function TabBar({ tabs, activeIndex }) {
29
+ return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
30
+ tabs.map((tab, i) => /* @__PURE__ */ jsx(React.Fragment, { children: i === activeIndex ? /* @__PURE__ */ jsxs(Text, { bold: true, inverse: true, children: [
31
+ " ",
32
+ tab,
33
+ " "
34
+ ] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
35
+ " ",
36
+ tab,
37
+ " "
38
+ ] }) }, tab)),
39
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (tab to cycle)" })
40
+ ] });
41
+ }
42
+ function SelectItem({ label, value, description, isSelected, isCurrent, isMulti = false }) {
43
+ const indicator = isMulti ? isSelected ? "\u25CF" : "\u25CB" : isSelected ? "\u25CF" : "\u25CB";
44
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
45
+ /* @__PURE__ */ jsxs(Box, { children: [
46
+ /* @__PURE__ */ jsx(Text, { color: isCurrent ? "cyan" : void 0, children: isCurrent ? "\u203A " : " " }),
47
+ /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
48
+ indicator,
49
+ " "
50
+ ] }),
51
+ /* @__PURE__ */ jsx(Text, { bold: isCurrent, children: label }),
52
+ value && /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
53
+ " ",
54
+ value
55
+ ] })
56
+ ] }),
57
+ description && /* @__PURE__ */ jsx(Box, { marginLeft: 4, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: description }) })
58
+ ] });
59
+ }
60
+ function Footer({ hints }) {
61
+ return /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingTop: 0, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hints.join(" \xB7 ") }) });
62
+ }
63
+ function GlobalWizard({ onComplete }) {
64
+ const { exit } = useApp();
65
+ const [activeTab, setActiveTab] = useState("experience");
66
+ const [experienceLevel, setExperienceLevel] = useState("intermediate");
67
+ const [selectedPatterns, setSelectedPatterns] = useState(
68
+ new Set(COMMON_KEY_FILE_PATTERNS.filter((p) => p.default).map((p) => p.pattern))
69
+ );
70
+ const [customPatterns, setCustomPatterns] = useState("");
71
+ const [refreshStale, setRefreshStale] = useState(true);
72
+ const [contextRestore, setContextRestore] = useState(true);
73
+ const [trackNew, setTrackNew] = useState("auto");
74
+ const [cursorIndex, setCursorIndex] = useState(0);
75
+ const [isEditingCustom, setIsEditingCustom] = useState(false);
76
+ const getTabs = useCallback(() => {
77
+ if (experienceLevel === "beginner") return ["experience", "confirm"];
78
+ if (experienceLevel === "intermediate") return ["experience", "patterns", "confirm"];
79
+ return ["experience", "patterns", "behaviors", "confirm"];
80
+ }, [experienceLevel]);
81
+ const tabs = getTabs();
82
+ const tabIndex = tabs.indexOf(activeTab);
83
+ const handleComplete = useCallback(() => {
84
+ const allPatterns = [
85
+ ...Array.from(selectedPatterns),
86
+ ...customPatterns ? customPatterns.split(",").map((p) => p.trim()).filter(Boolean) : []
87
+ ];
88
+ const config = {
89
+ experienceLevel,
90
+ globalKeyPatterns: experienceLevel === "beginner" ? DEFAULT_ONBOARDING_CONFIG.globalKeyPatterns : allPatterns,
91
+ autoBehaviors: experienceLevel === "expert" ? { refreshStaleSnapshots: refreshStale, contextRestoreOnCompact: contextRestore, trackNewKeyFiles: trackNew } : DEFAULT_ONBOARDING_CONFIG.autoBehaviors,
92
+ projects: {}
93
+ };
94
+ onComplete(config);
95
+ exit();
96
+ }, [experienceLevel, selectedPatterns, customPatterns, refreshStale, contextRestore, trackNew, onComplete, exit]);
97
+ useInput((input, key) => {
98
+ if (key.escape) {
99
+ if (isEditingCustom) {
100
+ setIsEditingCustom(false);
101
+ } else {
102
+ exit();
103
+ }
104
+ return;
105
+ }
106
+ if (key.tab && !isEditingCustom) {
107
+ const newIndex = key.shift ? (tabIndex - 1 + tabs.length) % tabs.length : (tabIndex + 1) % tabs.length;
108
+ setActiveTab(tabs[newIndex]);
109
+ setCursorIndex(0);
110
+ return;
111
+ }
112
+ if (isEditingCustom) {
113
+ if (key.return) {
114
+ setIsEditingCustom(false);
115
+ } else if (key.backspace || key.delete) {
116
+ setCustomPatterns((prev) => prev.slice(0, -1));
117
+ } else if (input && !key.ctrl && !key.meta) {
118
+ setCustomPatterns((prev) => prev + input);
119
+ }
120
+ return;
121
+ }
122
+ const getItemCount = () => {
123
+ switch (activeTab) {
124
+ case "experience":
125
+ return 3;
126
+ case "patterns":
127
+ return COMMON_KEY_FILE_PATTERNS.length + 1;
128
+ case "behaviors":
129
+ return 3;
130
+ case "confirm":
131
+ return 2;
132
+ default:
133
+ return 0;
134
+ }
135
+ };
136
+ if (key.upArrow) {
137
+ setCursorIndex((prev) => Math.max(0, prev - 1));
138
+ return;
139
+ }
140
+ if (key.downArrow) {
141
+ setCursorIndex((prev) => Math.min(getItemCount() - 1, prev + 1));
142
+ return;
143
+ }
144
+ if (key.return || input === " ") {
145
+ switch (activeTab) {
146
+ case "experience": {
147
+ const levels = ["beginner", "intermediate", "expert"];
148
+ const selected = levels[cursorIndex];
149
+ if (selected) {
150
+ setExperienceLevel(selected);
151
+ const newTabs = selected === "beginner" ? ["experience", "confirm"] : selected === "intermediate" ? ["experience", "patterns", "confirm"] : ["experience", "patterns", "behaviors", "confirm"];
152
+ setActiveTab(newTabs[1]);
153
+ setCursorIndex(0);
154
+ }
155
+ break;
156
+ }
157
+ case "patterns": {
158
+ const isCustom = cursorIndex === COMMON_KEY_FILE_PATTERNS.length;
159
+ if (isCustom) {
160
+ setIsEditingCustom(true);
161
+ } else {
162
+ const pattern = COMMON_KEY_FILE_PATTERNS[cursorIndex];
163
+ if (pattern) {
164
+ setSelectedPatterns((prev) => {
165
+ const next = new Set(prev);
166
+ if (next.has(pattern.pattern)) {
167
+ next.delete(pattern.pattern);
168
+ } else {
169
+ next.add(pattern.pattern);
170
+ }
171
+ return next;
172
+ });
173
+ }
174
+ }
175
+ break;
176
+ }
177
+ case "behaviors": {
178
+ if (cursorIndex === 0) {
179
+ setRefreshStale(!refreshStale);
180
+ } else if (cursorIndex === 1) {
181
+ setContextRestore(!contextRestore);
182
+ } else if (cursorIndex === 2) {
183
+ setTrackNew(trackNew === "auto" ? "ask" : trackNew === "ask" ? "manual" : "auto");
184
+ }
185
+ break;
186
+ }
187
+ case "confirm": {
188
+ if (cursorIndex === 0) {
189
+ handleComplete();
190
+ } else {
191
+ setActiveTab(tabs[tabIndex - 1] || "experience");
192
+ setCursorIndex(0);
193
+ }
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ });
199
+ const tabNames = tabs.map((t) => {
200
+ switch (t) {
201
+ case "experience":
202
+ return "Experience";
203
+ case "patterns":
204
+ return "Patterns";
205
+ case "behaviors":
206
+ return "Behaviors";
207
+ case "confirm":
208
+ return "Confirm";
209
+ default:
210
+ return t;
211
+ }
212
+ });
213
+ const getTitle = () => {
214
+ switch (activeTab) {
215
+ case "experience":
216
+ return "Select your experience level";
217
+ case "patterns":
218
+ return `Key file patterns (${selectedPatterns.size} selected)`;
219
+ case "behaviors":
220
+ return "Configure auto behaviors";
221
+ case "confirm":
222
+ return "Ready to complete setup";
223
+ default:
224
+ return "";
225
+ }
226
+ };
227
+ const renderExperience = () => {
228
+ const items = [
229
+ { id: "beginner", label: "Beginner", desc: "Auto-setup, minimal questions" },
230
+ { id: "intermediate", label: "Intermediate", desc: "Smart defaults with confirmation" },
231
+ { id: "expert", label: "Expert", desc: "Full control over all settings" }
232
+ ];
233
+ return items.map((item, i) => /* @__PURE__ */ jsx(
234
+ SelectItem,
235
+ {
236
+ label: item.label,
237
+ description: item.desc,
238
+ isCurrent: i === cursorIndex,
239
+ isSelected: item.id === experienceLevel
240
+ },
241
+ item.id
242
+ ));
243
+ };
244
+ const renderPatterns = () => {
245
+ const patternItems = COMMON_KEY_FILE_PATTERNS.map((p, i) => /* @__PURE__ */ jsx(
246
+ SelectItem,
247
+ {
248
+ label: p.pattern,
249
+ description: p.description,
250
+ isCurrent: i === cursorIndex,
251
+ isSelected: selectedPatterns.has(p.pattern),
252
+ isMulti: true
253
+ },
254
+ p.pattern
255
+ ));
256
+ const customIndex = COMMON_KEY_FILE_PATTERNS.length;
257
+ const customItem = isEditingCustom ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
258
+ /* @__PURE__ */ jsxs(Box, { children: [
259
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u203A " }),
260
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u25CB " }),
261
+ /* @__PURE__ */ jsx(Text, { children: "Custom: " }),
262
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: customPatterns }),
263
+ /* @__PURE__ */ jsx(Text, { color: "cyan", inverse: true, children: " " })
264
+ ] }),
265
+ /* @__PURE__ */ jsx(Box, { marginLeft: 4, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type comma-separated patterns, Enter when done" }) })
266
+ ] }, "_custom") : /* @__PURE__ */ jsx(
267
+ SelectItem,
268
+ {
269
+ label: "Custom patterns...",
270
+ description: customPatterns || "Add your own patterns",
271
+ isCurrent: cursorIndex === customIndex,
272
+ isSelected: false
273
+ },
274
+ "_custom"
275
+ );
276
+ return [...patternItems, customItem];
277
+ };
278
+ const renderBehaviors = () => {
279
+ const items = [
280
+ {
281
+ id: "refresh",
282
+ label: "Auto-refresh snapshots:",
283
+ value: refreshStale ? "Yes" : "No",
284
+ desc: "Refresh when snapshots become stale"
285
+ },
286
+ {
287
+ id: "context",
288
+ label: "Context restore:",
289
+ value: contextRestore ? "Yes" : "No",
290
+ desc: "Auto-restore after compaction"
291
+ },
292
+ {
293
+ id: "track",
294
+ label: "New key files:",
295
+ value: trackNew,
296
+ desc: "When new potential key files detected (auto/ask/manual)"
297
+ }
298
+ ];
299
+ return items.map((item, i) => /* @__PURE__ */ jsx(
300
+ SelectItem,
301
+ {
302
+ label: item.label,
303
+ value: item.value,
304
+ description: item.desc,
305
+ isCurrent: i === cursorIndex,
306
+ isSelected: i === cursorIndex,
307
+ isMulti: true
308
+ },
309
+ item.id
310
+ ));
311
+ };
312
+ const renderConfirm = () => {
313
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
314
+ /* @__PURE__ */ jsx(
315
+ SelectItem,
316
+ {
317
+ label: "Confirm and continue",
318
+ description: "Save settings and install MCP server",
319
+ isCurrent: cursorIndex === 0,
320
+ isSelected: cursorIndex === 0
321
+ }
322
+ ),
323
+ /* @__PURE__ */ jsx(
324
+ SelectItem,
325
+ {
326
+ label: "Go back",
327
+ description: "Review settings",
328
+ isCurrent: cursorIndex === 1,
329
+ isSelected: false
330
+ }
331
+ )
332
+ ] });
333
+ };
334
+ const renderContent = () => {
335
+ switch (activeTab) {
336
+ case "experience":
337
+ return renderExperience();
338
+ case "patterns":
339
+ return renderPatterns();
340
+ case "behaviors":
341
+ return renderBehaviors();
342
+ case "confirm":
343
+ return renderConfirm();
344
+ default:
345
+ return null;
346
+ }
347
+ };
348
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
349
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u{1F52E} Argus Setup" }) }),
350
+ /* @__PURE__ */ jsx(TabBar, { tabs: tabNames, activeIndex: tabIndex }),
351
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, children: getTitle() }) }),
352
+ renderContent(),
353
+ activeTab === "patterns" && cursorIndex < COMMON_KEY_FILE_PATTERNS.length && /* @__PURE__ */ jsx(Box, { marginLeft: 2, marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2193 more below" }) }),
354
+ /* @__PURE__ */ jsx(Footer, { hints: isEditingCustom ? ["Type patterns", "Enter: done", "Esc: cancel"] : ["\u2191\u2193: navigate", "Space/Enter: select", "Tab: next section", "Esc: cancel"] })
355
+ ] });
356
+ }
357
+ function ProjectWizard({
358
+ projectName,
359
+ detectedFiles,
360
+ globalPatterns,
361
+ experienceLevel,
362
+ onComplete
363
+ }) {
364
+ const { exit } = useApp();
365
+ const patternMatches = detectedFiles.filter((f) => f.matchedPattern);
366
+ const otherFiles = detectedFiles.filter((f) => !f.matchedPattern).slice(0, 10);
367
+ const allFiles = [...patternMatches, ...otherFiles];
368
+ const [selectedFiles, setSelectedFiles] = useState(
369
+ new Set(patternMatches.map((f) => f.path))
370
+ );
371
+ const [cursorIndex, setCursorIndex] = useState(0);
372
+ const [tab, setTab] = useState(allFiles.length > 0 ? "files" : "confirm");
373
+ const tabs = allFiles.length > 0 ? ["files", "confirm"] : ["confirm"];
374
+ const tabIndex = tabs.indexOf(tab);
375
+ const handleComplete = useCallback(() => {
376
+ const config = {
377
+ keyFiles: Array.from(selectedFiles),
378
+ customPatterns: [],
379
+ lastScanDate: (/* @__PURE__ */ new Date()).toISOString()
380
+ };
381
+ onComplete(config);
382
+ exit();
383
+ }, [selectedFiles, onComplete, exit]);
384
+ useInput((input, key) => {
385
+ if (key.escape) {
386
+ exit();
387
+ return;
388
+ }
389
+ if (key.tab) {
390
+ const newIndex = key.shift ? (tabIndex - 1 + tabs.length) % tabs.length : (tabIndex + 1) % tabs.length;
391
+ setTab(tabs[newIndex]);
392
+ setCursorIndex(0);
393
+ return;
394
+ }
395
+ if (key.upArrow) {
396
+ setCursorIndex((prev) => Math.max(0, prev - 1));
397
+ return;
398
+ }
399
+ if (key.downArrow) {
400
+ const maxIndex = tab === "files" ? allFiles.length - 1 : 1;
401
+ setCursorIndex((prev) => Math.min(maxIndex, prev + 1));
402
+ return;
403
+ }
404
+ if (key.return || input === " ") {
405
+ if (tab === "files") {
406
+ const file = allFiles[cursorIndex];
407
+ if (file) {
408
+ setSelectedFiles((prev) => {
409
+ const next = new Set(prev);
410
+ if (next.has(file.path)) {
411
+ next.delete(file.path);
412
+ } else {
413
+ next.add(file.path);
414
+ }
415
+ return next;
416
+ });
417
+ }
418
+ } else {
419
+ if (cursorIndex === 0) {
420
+ handleComplete();
421
+ } else {
422
+ setTab("files");
423
+ setCursorIndex(0);
424
+ }
425
+ }
426
+ }
427
+ });
428
+ const tabNames = tabs.map((t) => t === "files" ? "Files" : "Confirm");
429
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
430
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
431
+ "\u{1F4C2} Project Setup: ",
432
+ projectName
433
+ ] }) }),
434
+ /* @__PURE__ */ jsx(TabBar, { tabs: tabNames, activeIndex: tabIndex }),
435
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, children: tab === "files" ? `Select key files (${selectedFiles.size}/${allFiles.length} selected)` : "Ready to continue" }) }),
436
+ tab === "files" && /* @__PURE__ */ jsxs(Fragment, { children: [
437
+ patternMatches.length > 0 && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500 Matches your patterns \u2500\u2500" }) }),
438
+ allFiles.map((file, i) => {
439
+ const isPatternMatch = patternMatches.includes(file);
440
+ const showSeparator = i === patternMatches.length && otherFiles.length > 0;
441
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
442
+ showSeparator && /* @__PURE__ */ jsx(Box, { marginY: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500 Other detected files \u2500\u2500" }) }),
443
+ /* @__PURE__ */ jsx(
444
+ SelectItem,
445
+ {
446
+ label: file.path,
447
+ description: `${file.lines} lines \xB7 ${file.reason}`,
448
+ isCurrent: i === cursorIndex,
449
+ isSelected: selectedFiles.has(file.path),
450
+ isMulti: true
451
+ }
452
+ )
453
+ ] }, file.path);
454
+ }),
455
+ allFiles.length > 8 && cursorIndex < allFiles.length - 1 && /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2193 more below" }) })
456
+ ] }),
457
+ tab === "confirm" && /* @__PURE__ */ jsxs(Fragment, { children: [
458
+ /* @__PURE__ */ jsx(
459
+ SelectItem,
460
+ {
461
+ label: "Confirm and continue",
462
+ description: `Track ${selectedFiles.size} key file(s)`,
463
+ isCurrent: cursorIndex === 0,
464
+ isSelected: cursorIndex === 0
465
+ }
466
+ ),
467
+ /* @__PURE__ */ jsx(
468
+ SelectItem,
469
+ {
470
+ label: "Go back",
471
+ description: "Review file selection",
472
+ isCurrent: cursorIndex === 1,
473
+ isSelected: false
474
+ }
475
+ )
476
+ ] }),
477
+ /* @__PURE__ */ jsx(Footer, { hints: ["\u2191\u2193: navigate", "Space/Enter: select", "Tab: next section", "Esc: cancel"] })
478
+ ] });
479
+ }
480
+ async function renderGlobalOnboarding() {
481
+ return new Promise((resolve2) => {
482
+ const { waitUntilExit } = render(
483
+ /* @__PURE__ */ jsx(GlobalWizard, { onComplete: resolve2 })
484
+ );
485
+ waitUntilExit();
486
+ });
487
+ }
488
+ async function renderProjectOnboarding(projectName, detectedFiles, globalPatterns, experienceLevel) {
489
+ return new Promise((resolve2) => {
490
+ const { waitUntilExit } = render(
491
+ /* @__PURE__ */ jsx(
492
+ ProjectWizard,
493
+ {
494
+ projectName,
495
+ detectedFiles,
496
+ globalPatterns,
497
+ experienceLevel,
498
+ onComplete: resolve2
499
+ }
500
+ )
501
+ );
502
+ waitUntilExit();
503
+ });
504
+ }
505
+ var init_onboarding_ui = __esm({
506
+ "src/core/onboarding-ui.tsx"() {
507
+ "use strict";
508
+ init_esm_shims();
509
+ init_onboarding();
510
+ }
511
+ });
512
+
513
+ // src/core/onboarding.ts
514
+ function detectPotentialKeyFiles(projectPath, userPatterns, fs2, path2) {
515
+ const detected = [];
516
+ function checkFile(filePath, relativePath) {
517
+ try {
518
+ const stats = fs2.statSync(filePath);
519
+ if (!stats.isFile()) return;
520
+ const fileName = path2.basename(filePath).toLowerCase();
521
+ const ext = path2.extname(filePath).toLowerCase();
522
+ if (![".md", ".txt", ".org", ""].includes(ext)) return;
523
+ let reason = "";
524
+ let matchedPattern;
525
+ let matchedSignal;
526
+ for (const pattern of userPatterns) {
527
+ const regex = new RegExp(
528
+ "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
529
+ "i"
530
+ );
531
+ if (regex.test(fileName) || regex.test(relativePath)) {
532
+ reason = `Matches pattern: ${pattern}`;
533
+ matchedPattern = pattern;
534
+ break;
535
+ }
536
+ }
537
+ if (!reason && ext === ".md") {
538
+ try {
539
+ const content = fs2.readFileSync(filePath, "utf-8").toLowerCase();
540
+ for (const signal of CONTENT_SIGNALS) {
541
+ if (content.includes(signal)) {
542
+ reason = `Contains "${signal}" keyword`;
543
+ matchedSignal = signal;
544
+ break;
545
+ }
546
+ }
547
+ } catch {
548
+ }
549
+ }
550
+ if (!reason && ext === ".md" && !relativePath.includes("/")) {
551
+ const lineCount = countLines(filePath, fs2);
552
+ if (lineCount > 50) {
553
+ reason = "Large markdown file in project root";
554
+ }
555
+ }
556
+ if (reason) {
557
+ detected.push({
558
+ path: relativePath,
559
+ reason,
560
+ lines: countLines(filePath, fs2),
561
+ lastModified: stats.mtime,
562
+ matchedPattern,
563
+ matchedSignal
564
+ });
565
+ }
566
+ } catch {
567
+ }
568
+ }
569
+ function scanDir(dirPath, relativePath = "") {
570
+ try {
571
+ const entries = fs2.readdirSync(dirPath, { withFileTypes: true });
572
+ for (const entry of entries) {
573
+ if (entry.isDirectory()) {
574
+ if ([
575
+ "node_modules",
576
+ ".git",
577
+ "target",
578
+ "dist",
579
+ "build",
580
+ ".next",
581
+ "coverage",
582
+ "__pycache__",
583
+ ".venv",
584
+ "vendor"
585
+ ].includes(entry.name)) {
586
+ continue;
587
+ }
588
+ if (relativePath.split("/").filter(Boolean).length < 2) {
589
+ scanDir(
590
+ path2.join(dirPath, entry.name),
591
+ relativePath ? `${relativePath}/${entry.name}` : entry.name
592
+ );
593
+ }
594
+ } else {
595
+ checkFile(
596
+ path2.join(dirPath, entry.name),
597
+ relativePath ? `${relativePath}/${entry.name}` : entry.name
598
+ );
599
+ }
600
+ }
601
+ } catch {
602
+ }
603
+ }
604
+ scanDir(projectPath);
605
+ return detected.sort((a, b) => {
606
+ if (a.matchedPattern && !b.matchedPattern) return -1;
607
+ if (!a.matchedPattern && b.matchedPattern) return 1;
608
+ return b.lines - a.lines;
609
+ });
610
+ }
611
+ function countLines(filePath, fs2) {
612
+ try {
613
+ const content = fs2.readFileSync(filePath, "utf-8");
614
+ return content.split("\n").length;
615
+ } catch {
616
+ return 0;
617
+ }
618
+ }
619
+ async function runGlobalOnboarding() {
620
+ const { renderGlobalOnboarding: renderGlobalOnboarding2 } = await Promise.resolve().then(() => (init_onboarding_ui(), onboarding_ui_exports));
621
+ const config = await renderGlobalOnboarding2();
622
+ console.log("\n\u2705 Onboarding complete!");
623
+ console.log(` Experience level: ${config.experienceLevel}`);
624
+ console.log(` Key patterns: ${config.globalKeyPatterns.join(", ")}`);
625
+ return config;
626
+ }
627
+ async function runProjectOnboarding(projectPath, globalConfig, fs2, path2) {
628
+ const projectName = path2.basename(projectPath);
629
+ console.log(`
630
+ \u{1F4C2} Scanning project: ${projectName}
631
+ `);
632
+ const detected = detectPotentialKeyFiles(projectPath, globalConfig.globalKeyPatterns, fs2, path2);
633
+ if (globalConfig.experienceLevel === "beginner") {
634
+ const autoSelected = detected.filter((d) => d.matchedPattern).map((d) => d.path);
635
+ if (autoSelected.length > 0) {
636
+ console.log("\u2705 Auto-detected key files:");
637
+ autoSelected.forEach((f) => console.log(` \u2022 ${f}`));
638
+ } else {
639
+ console.log("\u2139\uFE0F No key files matching your patterns found");
640
+ }
641
+ return {
642
+ keyFiles: autoSelected,
643
+ customPatterns: [],
644
+ lastScanDate: (/* @__PURE__ */ new Date()).toISOString()
645
+ };
646
+ }
647
+ const { renderProjectOnboarding: renderProjectOnboarding2 } = await Promise.resolve().then(() => (init_onboarding_ui(), onboarding_ui_exports));
648
+ const config = await renderProjectOnboarding2(
649
+ projectName,
650
+ detected,
651
+ globalConfig.globalKeyPatterns,
652
+ globalConfig.experienceLevel
653
+ );
654
+ if (config.keyFiles.length > 0) {
655
+ console.log(`
656
+ \u2705 Tracking ${config.keyFiles.length} key file(s)`);
657
+ }
658
+ return config;
659
+ }
660
+ var COMMON_KEY_FILE_PATTERNS, CONTENT_SIGNALS, DEFAULT_ONBOARDING_CONFIG;
661
+ var init_onboarding = __esm({
662
+ "src/core/onboarding.ts"() {
663
+ "use strict";
664
+ init_esm_shims();
665
+ COMMON_KEY_FILE_PATTERNS = [
666
+ { pattern: "STATUS*", description: "Project status tracking", default: true },
667
+ { pattern: "README*", description: "Project documentation", default: true },
668
+ { pattern: "TODO*", description: "Task lists", default: true },
669
+ { pattern: "ROADMAP*", description: "Project roadmap", default: false },
670
+ { pattern: "PROGRESS*", description: "Progress tracking", default: false },
671
+ { pattern: "CHANGELOG*", description: "Version history", default: false },
672
+ { pattern: "ARCHITECTURE*", description: "Architecture docs", default: false },
673
+ { pattern: "DEVELOPMENT*", description: "Development notes", default: false },
674
+ { pattern: ".plan", description: "Plan files", default: false },
675
+ { pattern: "docs/architecture*", description: "Architecture in docs/", default: false }
676
+ ];
677
+ CONTENT_SIGNALS = [
678
+ "roadmap",
679
+ "milestone",
680
+ "progress",
681
+ "status",
682
+ "todo",
683
+ "architecture",
684
+ "overview",
685
+ "getting started"
686
+ ];
687
+ DEFAULT_ONBOARDING_CONFIG = {
688
+ experienceLevel: "beginner",
689
+ globalKeyPatterns: ["STATUS*", "README*", "TODO*"],
690
+ autoBehaviors: {
691
+ refreshStaleSnapshots: true,
692
+ contextRestoreOnCompact: true,
693
+ trackNewKeyFiles: "auto"
694
+ },
695
+ projects: {}
696
+ };
697
+ }
698
+ });
699
+
700
+ // src/cli.ts
701
+ init_esm_shims();
702
+ import { Command } from "commander";
703
+ import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, statSync as statSync2, unlinkSync, readdirSync as readdirSync2, mkdirSync as mkdirSync2 } from "fs";
704
+ import * as fs from "fs";
705
+ import { homedir as homedir2 } from "os";
706
+ import { join as join4, resolve, basename } from "path";
707
+ import * as path from "path";
708
+ import { execSync } from "child_process";
709
+
710
+ // src/core/config.ts
711
+ init_esm_shims();
712
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
713
+ import { homedir } from "os";
714
+ import { join } from "path";
715
+ var DEFAULT_CONFIG = {
716
+ provider: "ollama",
717
+ providers: {
718
+ ollama: {
719
+ baseUrl: "http://localhost:11434",
720
+ model: "qwen2.5-coder:7b"
721
+ }
722
+ },
723
+ defaults: {
724
+ maxTurns: 15,
725
+ turnTimeoutMs: 6e4,
726
+ snapshotExtensions: ["ts", "tsx", "js", "jsx", "rs", "py", "go", "java", "rb", "php", "swift", "kt", "scala", "c", "cpp", "h", "hpp", "cs", "md"],
727
+ excludePatterns: [
728
+ "node_modules",
729
+ ".git",
730
+ "target",
731
+ "dist",
732
+ "build",
733
+ ".next",
734
+ "coverage",
735
+ "__pycache__",
736
+ ".venv",
737
+ "vendor"
738
+ ]
739
+ }
740
+ };
741
+ function getConfigDir() {
742
+ return join(homedir(), ".argus");
743
+ }
744
+ function getConfigPath() {
745
+ return join(getConfigDir(), "config.json");
746
+ }
747
+ function ensureConfigDir() {
748
+ const dir = getConfigDir();
749
+ if (!existsSync(dir)) {
750
+ mkdirSync(dir, { recursive: true });
751
+ }
752
+ }
753
+ function loadConfig() {
754
+ const configPath = getConfigPath();
755
+ if (!existsSync(configPath)) {
756
+ return DEFAULT_CONFIG;
757
+ }
758
+ try {
759
+ const content = readFileSync(configPath, "utf-8");
760
+ const loaded = JSON.parse(content);
761
+ return {
762
+ ...DEFAULT_CONFIG,
763
+ ...loaded,
764
+ providers: {
765
+ ...DEFAULT_CONFIG.providers,
766
+ ...loaded.providers
767
+ },
768
+ defaults: {
769
+ ...DEFAULT_CONFIG.defaults,
770
+ ...loaded.defaults
771
+ }
772
+ };
773
+ } catch {
774
+ return DEFAULT_CONFIG;
775
+ }
776
+ }
777
+ function saveConfig(config) {
778
+ ensureConfigDir();
779
+ const configPath = getConfigPath();
780
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
781
+ }
782
+ function validateConfig(config) {
783
+ const errors = [];
784
+ const providerConfig = config.providers[config.provider];
785
+ if (!providerConfig) {
786
+ errors.push(`Provider "${config.provider}" is not configured`);
787
+ return errors;
788
+ }
789
+ if (config.provider !== "ollama" && !providerConfig.apiKey) {
790
+ errors.push(`API key is required for provider "${config.provider}"`);
791
+ }
792
+ if (!providerConfig.model) {
793
+ errors.push(`Model is required for provider "${config.provider}"`);
794
+ }
795
+ return errors;
796
+ }
797
+ var PROVIDER_DEFAULTS = {
798
+ zai: {
799
+ baseUrl: "https://api.z.ai/api/coding/paas/v4",
800
+ model: "glm-4.7"
801
+ },
802
+ anthropic: {
803
+ baseUrl: "https://api.anthropic.com",
804
+ model: "claude-sonnet-4-20250514"
805
+ },
806
+ openai: {
807
+ baseUrl: "https://api.openai.com/v1",
808
+ model: "gpt-4o"
809
+ },
810
+ deepseek: {
811
+ baseUrl: "https://api.deepseek.com",
812
+ model: "deepseek-chat"
813
+ },
814
+ ollama: {
815
+ baseUrl: "http://localhost:11434",
816
+ model: "qwen2.5-coder:7b"
817
+ }
818
+ };
819
+
820
+ // src/core/snapshot.ts
821
+ init_esm_shims();
822
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
823
+ import { join as join2, relative, extname } from "path";
824
+ var DEFAULT_OPTIONS = {
825
+ extensions: ["ts", "tsx", "js", "jsx", "rs", "py", "go", "java", "rb", "php", "swift", "kt", "scala", "c", "cpp", "h", "hpp", "cs", "md", "json"],
826
+ excludePatterns: [
827
+ "node_modules",
828
+ ".git",
829
+ "target",
830
+ "dist",
831
+ "build",
832
+ ".next",
833
+ "coverage",
834
+ "__pycache__",
835
+ ".venv",
836
+ "vendor",
837
+ ".DS_Store",
838
+ "*.lock",
839
+ "package-lock.json",
840
+ "*.min.js",
841
+ "*.min.css"
842
+ ],
843
+ maxFileSize: 1024 * 1024,
844
+ // 1MB
845
+ includeHidden: false
846
+ };
847
+ function shouldExclude(filePath, patterns) {
848
+ const normalizedPath = filePath.replace(/\\/g, "/");
849
+ for (const pattern of patterns) {
850
+ if (pattern.startsWith("*")) {
851
+ const suffix = pattern.slice(1);
852
+ if (normalizedPath.endsWith(suffix)) return true;
853
+ } else if (normalizedPath.includes(`/${pattern}/`) || normalizedPath.endsWith(`/${pattern}`) || normalizedPath === pattern) {
854
+ return true;
855
+ }
856
+ }
857
+ return false;
858
+ }
859
+ function hasValidExtension(filePath, extensions) {
860
+ const ext = extname(filePath).slice(1).toLowerCase();
861
+ return extensions.includes(ext);
862
+ }
863
+ function collectFiles(dir, options, baseDir = dir) {
864
+ const files = [];
865
+ try {
866
+ const entries = readdirSync(dir, { withFileTypes: true });
867
+ for (const entry of entries) {
868
+ const fullPath = join2(dir, entry.name);
869
+ const relativePath = relative(baseDir, fullPath);
870
+ if (!options.includeHidden && entry.name.startsWith(".")) {
871
+ continue;
872
+ }
873
+ if (shouldExclude(relativePath, options.excludePatterns)) {
874
+ continue;
875
+ }
876
+ if (entry.isDirectory()) {
877
+ files.push(...collectFiles(fullPath, options, baseDir));
878
+ } else if (entry.isFile()) {
879
+ if (!hasValidExtension(entry.name, options.extensions)) {
880
+ continue;
881
+ }
882
+ try {
883
+ const stats = statSync(fullPath);
884
+ if (stats.size > options.maxFileSize) {
885
+ continue;
886
+ }
887
+ } catch {
888
+ continue;
889
+ }
890
+ files.push(fullPath);
891
+ }
892
+ }
893
+ } catch (error) {
894
+ }
895
+ return files.sort();
896
+ }
897
+ function createSnapshot(projectPath, outputPath, options = {}) {
898
+ const mergedOptions = {
899
+ ...DEFAULT_OPTIONS,
900
+ ...options
901
+ };
902
+ if (!existsSync2(projectPath)) {
903
+ throw new Error(`Project path does not exist: ${projectPath}`);
904
+ }
905
+ const stats = statSync(projectPath);
906
+ if (!stats.isDirectory()) {
907
+ throw new Error(`Project path is not a directory: ${projectPath}`);
908
+ }
909
+ const files = collectFiles(projectPath, mergedOptions);
910
+ const lines = [];
911
+ lines.push("================================================================================");
912
+ lines.push("CODEBASE SNAPSHOT");
913
+ lines.push(`Project: ${projectPath}`);
914
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
915
+ lines.push(`Extensions: ${mergedOptions.extensions.join(", ")}`);
916
+ lines.push(`Files: ${files.length}`);
917
+ lines.push("================================================================================");
918
+ lines.push("");
919
+ for (const filePath of files) {
920
+ const relativePath = relative(projectPath, filePath);
921
+ lines.push("");
922
+ lines.push("================================================================================");
923
+ lines.push(`FILE: ./${relativePath}`);
924
+ lines.push("================================================================================");
925
+ try {
926
+ const content2 = readFileSync2(filePath, "utf-8");
927
+ lines.push(content2);
928
+ } catch (error) {
929
+ lines.push("[Unable to read file]");
930
+ }
931
+ }
932
+ const content = lines.join("\n");
933
+ writeFileSync2(outputPath, content);
934
+ const totalLines = content.split("\n").length;
935
+ const totalSize = Buffer.byteLength(content, "utf-8");
936
+ return {
937
+ outputPath,
938
+ fileCount: files.length,
939
+ totalLines,
940
+ totalSize,
941
+ files: files.map((f) => relative(projectPath, f))
942
+ };
943
+ }
944
+
945
+ // src/core/enhanced-snapshot.ts
946
+ init_esm_shims();
947
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
948
+ import { join as join3, dirname, extname as extname2 } from "path";
949
+ function parseImports(content, filePath) {
950
+ const imports = [];
951
+ const lines = content.split("\n");
952
+ const patterns = [
953
+ // import { a, b } from 'module'
954
+ /import\s+(?:type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/g,
955
+ // import * as name from 'module'
956
+ /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,
957
+ // import defaultExport from 'module'
958
+ /import\s+(?:type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/g,
959
+ // import 'module' (side-effect)
960
+ /import\s+['"]([^'"]+)['"]/g,
961
+ // require('module')
962
+ /(?:const|let|var)\s+(?:{([^}]+)}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
963
+ ];
964
+ for (const line of lines) {
965
+ const trimmed = line.trim();
966
+ if (!trimmed.startsWith("import") && !trimmed.includes("require(")) continue;
967
+ let match = /import\s+(type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/.exec(trimmed);
968
+ if (match) {
969
+ const isType = !!match[1];
970
+ const symbols = match[2].split(",").map((s) => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
971
+ const target = match[3];
972
+ imports.push({
973
+ source: filePath,
974
+ target,
975
+ symbols,
976
+ isDefault: false,
977
+ isType
978
+ });
979
+ continue;
980
+ }
981
+ match = /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/.exec(trimmed);
982
+ if (match) {
983
+ imports.push({
984
+ source: filePath,
985
+ target: match[2],
986
+ symbols: ["*"],
987
+ isDefault: false,
988
+ isType: false
989
+ });
990
+ continue;
991
+ }
992
+ match = /import\s+(type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/.exec(trimmed);
993
+ if (match && !trimmed.includes("{")) {
994
+ imports.push({
995
+ source: filePath,
996
+ target: match[3],
997
+ symbols: [match[2]],
998
+ isDefault: true,
999
+ isType: !!match[1]
1000
+ });
1001
+ continue;
1002
+ }
1003
+ match = /^import\s+['"]([^'"]+)['"]/.exec(trimmed);
1004
+ if (match) {
1005
+ imports.push({
1006
+ source: filePath,
1007
+ target: match[1],
1008
+ symbols: [],
1009
+ isDefault: false,
1010
+ isType: false
1011
+ });
1012
+ }
1013
+ }
1014
+ return imports;
1015
+ }
1016
+ function parseExports(content, filePath) {
1017
+ const exports = [];
1018
+ const lines = content.split("\n");
1019
+ for (let i = 0; i < lines.length; i++) {
1020
+ const line = lines[i];
1021
+ const trimmed = line.trim();
1022
+ let match = /export\s+(?:async\s+)?function\s+(\w+)\s*(\([^)]*\))/.exec(trimmed);
1023
+ if (match) {
1024
+ exports.push({
1025
+ file: filePath,
1026
+ symbol: match[1],
1027
+ type: "function",
1028
+ signature: `function ${match[1]}${match[2]}`,
1029
+ line: i + 1
1030
+ });
1031
+ continue;
1032
+ }
1033
+ match = /export\s+class\s+(\w+)/.exec(trimmed);
1034
+ if (match) {
1035
+ exports.push({
1036
+ file: filePath,
1037
+ symbol: match[1],
1038
+ type: "class",
1039
+ line: i + 1
1040
+ });
1041
+ continue;
1042
+ }
1043
+ match = /export\s+(const|let|var)\s+(\w+)/.exec(trimmed);
1044
+ if (match) {
1045
+ exports.push({
1046
+ file: filePath,
1047
+ symbol: match[2],
1048
+ type: match[1],
1049
+ line: i + 1
1050
+ });
1051
+ continue;
1052
+ }
1053
+ match = /export\s+(type|interface)\s+(\w+)/.exec(trimmed);
1054
+ if (match) {
1055
+ exports.push({
1056
+ file: filePath,
1057
+ symbol: match[2],
1058
+ type: match[1],
1059
+ line: i + 1
1060
+ });
1061
+ continue;
1062
+ }
1063
+ match = /export\s+enum\s+(\w+)/.exec(trimmed);
1064
+ if (match) {
1065
+ exports.push({
1066
+ file: filePath,
1067
+ symbol: match[1],
1068
+ type: "enum",
1069
+ line: i + 1
1070
+ });
1071
+ continue;
1072
+ }
1073
+ if (/export\s+default/.test(trimmed)) {
1074
+ match = /export\s+default\s+(?:function\s+)?(\w+)?/.exec(trimmed);
1075
+ exports.push({
1076
+ file: filePath,
1077
+ symbol: match?.[1] || "default",
1078
+ type: "default",
1079
+ line: i + 1
1080
+ });
1081
+ }
1082
+ }
1083
+ return exports;
1084
+ }
1085
+ function resolveImportPath(importPath, fromFile, projectFiles) {
1086
+ if (!importPath.startsWith(".")) return void 0;
1087
+ const fromDir = dirname(fromFile);
1088
+ let resolved = join3(fromDir, importPath);
1089
+ const extensions = [".ts", ".tsx", ".js", ".jsx", "", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
1090
+ for (const ext of extensions) {
1091
+ const candidate = resolved + ext;
1092
+ if (projectFiles.includes(candidate) || projectFiles.includes("./" + candidate)) {
1093
+ return candidate;
1094
+ }
1095
+ }
1096
+ return void 0;
1097
+ }
1098
+ function createEnhancedSnapshot(projectPath, outputPath, options = {}) {
1099
+ const baseResult = createSnapshot(projectPath, outputPath, options);
1100
+ const allImports = [];
1101
+ const allExports = [];
1102
+ const fileIndex = {};
1103
+ const projectFiles = baseResult.files.map((f) => "./" + f);
1104
+ for (const relPath of baseResult.files) {
1105
+ const fullPath = join3(projectPath, relPath);
1106
+ const ext = extname2(relPath).toLowerCase();
1107
+ if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
1108
+ continue;
1109
+ }
1110
+ try {
1111
+ const content = readFileSync3(fullPath, "utf-8");
1112
+ const imports = parseImports(content, relPath);
1113
+ const exports = parseExports(content, relPath);
1114
+ for (const imp of imports) {
1115
+ imp.resolved = resolveImportPath(imp.target, relPath, projectFiles);
1116
+ }
1117
+ allImports.push(...imports);
1118
+ allExports.push(...exports);
1119
+ fileIndex[relPath] = {
1120
+ path: relPath,
1121
+ imports,
1122
+ exports,
1123
+ size: content.length,
1124
+ lines: content.split("\n").length
1125
+ };
1126
+ } catch {
1127
+ }
1128
+ }
1129
+ const importGraph = {};
1130
+ for (const imp of allImports) {
1131
+ if (imp.resolved) {
1132
+ if (!importGraph[imp.source]) importGraph[imp.source] = [];
1133
+ if (!importGraph[imp.source].includes(imp.resolved)) {
1134
+ importGraph[imp.source].push(imp.resolved);
1135
+ }
1136
+ }
1137
+ }
1138
+ const exportGraph = {};
1139
+ for (const imp of allImports) {
1140
+ if (imp.resolved) {
1141
+ if (!exportGraph[imp.resolved]) exportGraph[imp.resolved] = [];
1142
+ if (!exportGraph[imp.resolved].includes(imp.source)) {
1143
+ exportGraph[imp.resolved].push(imp.source);
1144
+ }
1145
+ }
1146
+ }
1147
+ const symbolIndex = {};
1148
+ for (const exp of allExports) {
1149
+ if (!symbolIndex[exp.symbol]) symbolIndex[exp.symbol] = [];
1150
+ if (!symbolIndex[exp.symbol].includes(exp.file)) {
1151
+ symbolIndex[exp.symbol].push(exp.file);
1152
+ }
1153
+ }
1154
+ const metadataSection = `
1155
+
1156
+ ================================================================================
1157
+ METADATA: IMPORT GRAPH
1158
+ ================================================================================
1159
+ ${Object.entries(importGraph).map(([file, imports]) => `${file}:
1160
+ ${imports.map((i) => ` \u2192 ${i}`).join("\n")}`).join("\n\n")}
1161
+
1162
+ ================================================================================
1163
+ METADATA: EXPORT INDEX
1164
+ ================================================================================
1165
+ ${Object.entries(symbolIndex).map(([symbol, files]) => `${symbol}: ${files.join(", ")}`).join("\n")}
1166
+
1167
+ ================================================================================
1168
+ METADATA: FILE EXPORTS
1169
+ ================================================================================
1170
+ ${allExports.map((e) => `${e.file}:${e.line} - ${e.type} ${e.symbol}${e.signature ? ` ${e.signature}` : ""}`).join("\n")}
1171
+
1172
+ ================================================================================
1173
+ METADATA: WHO IMPORTS WHOM
1174
+ ================================================================================
1175
+ ${Object.entries(exportGraph).map(([file, importers]) => `${file} is imported by:
1176
+ ${importers.map((i) => ` \u2190 ${i}`).join("\n")}`).join("\n\n")}
1177
+ `;
1178
+ const existingContent = readFileSync3(outputPath, "utf-8");
1179
+ writeFileSync3(outputPath, existingContent + metadataSection);
1180
+ return {
1181
+ ...baseResult,
1182
+ metadata: {
1183
+ imports: allImports,
1184
+ exports: allExports,
1185
+ fileIndex,
1186
+ importGraph,
1187
+ exportGraph,
1188
+ symbolIndex
1189
+ }
1190
+ };
1191
+ }
1192
+
1193
+ // src/core/engine.ts
1194
+ init_esm_shims();
1195
+ import { readFileSync as readFileSync4 } from "fs";
1196
+
1197
+ // src/core/prompts.ts
1198
+ init_esm_shims();
1199
+ var NUCLEUS_COMMANDS = `
1200
+ COMMANDS (output ONE per turn):
1201
+ (grep "pattern") - Find lines matching regex
1202
+ (grep "pattern" "i") - Case-insensitive search
1203
+ (count RESULTS) - Count matches
1204
+ (take RESULTS n) - First n results
1205
+ (filter RESULTS (lambda (x) (match x.line "pattern" 0))) - Filter results
1206
+ (map RESULTS (lambda (x) x.line)) - Extract just the lines
1207
+
1208
+ VARIABLES: RESULTS = last result, _1 _2 _3 = results from turn 1,2,3
1209
+
1210
+ TO ANSWER: <<<FINAL>>>your answer<<<END>>>
1211
+ `;
1212
+ var CODEBASE_ANALYSIS_PROMPT = `You are analyzing a SOFTWARE CODEBASE snapshot to help a developer understand it.
1213
+
1214
+ The snapshot contains source files concatenated with "FILE: ./path/to/file" markers.
1215
+
1216
+ ${NUCLEUS_COMMANDS}
1217
+
1218
+ ## STRATEGY FOR CODEBASE SNAPSHOTS
1219
+
1220
+ **To find modules/directories:**
1221
+ (grep "FILE:.*src/[^/]+/") - top-level source dirs
1222
+ (grep "FILE:.*mod\\.rs") - Rust modules
1223
+ (grep "FILE:.*index\\.(ts|js)") - JS/TS modules
1224
+
1225
+ **To find implementations:**
1226
+ (grep "fn function_name") - Rust functions
1227
+ (grep "function|const.*=>") - JS functions
1228
+ (grep "class ClassName") - Classes
1229
+ (grep "struct |type |interface") - Type definitions
1230
+
1231
+ **To understand structure:**
1232
+ (grep "FILE:") - List all files
1233
+ (grep "use |import |require") - Find dependencies
1234
+ (grep "pub |export") - Public APIs
1235
+
1236
+ ## RULES
1237
+ 1. Output ONLY a Nucleus command OR a final answer
1238
+ 2. NO explanations, NO markdown formatting in commands
1239
+ 3. MUST provide final answer by turn 8
1240
+ 4. If turn 6+, start summarizing what you found
1241
+
1242
+ ## EXAMPLE SESSION
1243
+ Turn 1: (grep "FILE:.*src/[^/]+/mod\\.rs")
1244
+ Turn 2: (take RESULTS 15)
1245
+ Turn 3: <<<FINAL>>>The codebase has these main modules:
1246
+ - src/auth/ - Authentication handling
1247
+ - src/api/ - API endpoints
1248
+ - src/db/ - Database layer
1249
+ ...<<<END>>>
1250
+ `;
1251
+ var ARCHITECTURE_PROMPT = `You are generating an ARCHITECTURE SUMMARY of a codebase.
1252
+
1253
+ ${NUCLEUS_COMMANDS}
1254
+
1255
+ ## YOUR TASK
1256
+ Create a summary suitable for CLAUDE.md that helps Claude Code understand this project after context compaction.
1257
+
1258
+ ## SEARCH STRATEGY (do these in order)
1259
+ 1. (grep "FILE:.*mod\\.rs|FILE:.*index\\.(ts|js)") - Find module entry points
1260
+ 2. (take RESULTS 20) - Limit results
1261
+ 3. Based on file paths, provide your summary
1262
+
1263
+ ## OUTPUT FORMAT
1264
+ Your final answer should be structured like:
1265
+
1266
+ ## Modules
1267
+ - **module_name/** - Brief description based on files found
1268
+
1269
+ ## Key Patterns
1270
+ - Pattern observations from the code
1271
+
1272
+ ## Important Files
1273
+ - List key files and their apparent purpose
1274
+
1275
+ PROVIDE FINAL ANSWER BY TURN 6.
1276
+ `;
1277
+ var IMPLEMENTATION_PROMPT = `You are finding HOW something works in a codebase.
1278
+
1279
+ ${NUCLEUS_COMMANDS}
1280
+
1281
+ ## STRATEGY
1282
+ 1. (grep "FILE:.*keyword") - Find files related to the concept
1283
+ 2. (grep "keyword") - Find all mentions
1284
+ 3. (take RESULTS 30) - Limit if too many results
1285
+ 4. Look for function definitions, structs, classes
1286
+ 5. PROVIDE FINAL ANSWER based on file paths and code patterns found
1287
+
1288
+ ## IMPORTANT
1289
+ - You have 12 turns maximum
1290
+ - By turn 8, START WRITING YOUR FINAL ANSWER
1291
+ - Use what you've found - don't keep searching indefinitely
1292
+ - It's better to give a partial answer than no answer
1293
+
1294
+ ## OUTPUT FORMAT
1295
+ Your final answer should explain:
1296
+ - Which files contain the implementation
1297
+ - Key functions/structs/classes involved
1298
+ - Basic flow of how it works (based on what you found)
1299
+ `;
1300
+ var COUNT_PROMPT = `You are counting items in a codebase.
1301
+
1302
+ ${NUCLEUS_COMMANDS}
1303
+
1304
+ ## STRATEGY
1305
+ 1. (grep "pattern")
1306
+ 2. (count RESULTS)
1307
+ 3. <<<FINAL>>>There are N items matching the pattern.<<<END>>>
1308
+
1309
+ THIS SHOULD TAKE 2-3 TURNS MAXIMUM.
1310
+ `;
1311
+ var SEARCH_PROMPT = `You are searching for specific code.
1312
+
1313
+ ${NUCLEUS_COMMANDS}
1314
+
1315
+ ## STRATEGY
1316
+ 1. (grep "pattern")
1317
+ 2. (take RESULTS 20) if too many
1318
+ 3. Report what you found with file paths
1319
+
1320
+ PROVIDE FINAL ANSWER BY TURN 4.
1321
+ `;
1322
+ function selectPrompt(query) {
1323
+ const q = query.toLowerCase();
1324
+ if (/how many|count|number of|total|how much/.test(q)) {
1325
+ return COUNT_PROMPT;
1326
+ }
1327
+ if (/^(find|search|show|list|where is|locate)\b/.test(q) && q.length < 50) {
1328
+ return SEARCH_PROMPT;
1329
+ }
1330
+ if (/architect|structure|overview|module|organization|main.*component|summar|layout/.test(q)) {
1331
+ return ARCHITECTURE_PROMPT;
1332
+ }
1333
+ if (/how does|how is|implement|work|handle|process|flow/.test(q)) {
1334
+ return IMPLEMENTATION_PROMPT;
1335
+ }
1336
+ return CODEBASE_ANALYSIS_PROMPT;
1337
+ }
1338
+ function buildSystemPrompt(query) {
1339
+ return selectPrompt(query);
1340
+ }
1341
+ function getTurnLimit(query) {
1342
+ const q = query.toLowerCase();
1343
+ if (/how many|count/.test(q)) return 5;
1344
+ if (/^(find|search|show|list)\b/.test(q) && q.length < 50) return 6;
1345
+ if (/architect|overview|structure|module/.test(q)) return 12;
1346
+ if (/how does|how is|implement|work/.test(q)) return 12;
1347
+ return 12;
1348
+ }
1349
+
1350
+ // src/core/engine.ts
1351
+ function executeNucleus(command, content, bindings) {
1352
+ const parsed = parseSExpression(command);
1353
+ if (!parsed) {
1354
+ throw new Error(`Failed to parse command: ${command}`);
1355
+ }
1356
+ return evaluateExpr(parsed, content, bindings);
1357
+ }
1358
+ function parseSExpression(input) {
1359
+ const tokens = tokenize(input.trim());
1360
+ if (tokens.length === 0) return null;
1361
+ let pos = 0;
1362
+ function parse() {
1363
+ const token = tokens[pos++];
1364
+ if (token === "(") {
1365
+ const list = [];
1366
+ while (tokens[pos] !== ")" && pos < tokens.length) {
1367
+ list.push(parse());
1368
+ }
1369
+ pos++;
1370
+ return list;
1371
+ } else if (token.startsWith('"')) {
1372
+ return token.slice(1, -1).replace(/\\"/g, '"');
1373
+ } else if (/^-?\d+(\.\d+)?$/.test(token)) {
1374
+ return token;
1375
+ } else {
1376
+ return token;
1377
+ }
1378
+ }
1379
+ return parse();
1380
+ }
1381
+ function tokenize(input) {
1382
+ const tokens = [];
1383
+ let i = 0;
1384
+ while (i < input.length) {
1385
+ const char = input[i];
1386
+ if (/\s/.test(char)) {
1387
+ i++;
1388
+ continue;
1389
+ }
1390
+ if (char === "(" || char === ")") {
1391
+ tokens.push(char);
1392
+ i++;
1393
+ continue;
1394
+ }
1395
+ if (char === '"') {
1396
+ let str = '"';
1397
+ i++;
1398
+ while (i < input.length && input[i] !== '"') {
1399
+ if (input[i] === "\\" && i + 1 < input.length) {
1400
+ str += input[i] + input[i + 1];
1401
+ i += 2;
1402
+ } else {
1403
+ str += input[i];
1404
+ i++;
1405
+ }
1406
+ }
1407
+ str += '"';
1408
+ i++;
1409
+ tokens.push(str);
1410
+ continue;
1411
+ }
1412
+ let sym = "";
1413
+ while (i < input.length && !/[\s()]/.test(input[i])) {
1414
+ sym += input[i];
1415
+ i++;
1416
+ }
1417
+ tokens.push(sym);
1418
+ }
1419
+ return tokens;
1420
+ }
1421
+ function evaluateExpr(expr, content, bindings) {
1422
+ if (typeof expr === "string") {
1423
+ if (bindings.has(expr)) {
1424
+ return bindings.get(expr);
1425
+ }
1426
+ if (/^-?\d+(\.\d+)?$/.test(expr)) {
1427
+ return parseFloat(expr);
1428
+ }
1429
+ return expr;
1430
+ }
1431
+ if (!Array.isArray(expr) || expr.length === 0) {
1432
+ return expr;
1433
+ }
1434
+ const [op, ...args] = expr;
1435
+ switch (op) {
1436
+ case "grep": {
1437
+ const pattern = evaluateExpr(args[0], content, bindings);
1438
+ const flags = args[1] ? evaluateExpr(args[1], content, bindings) : "";
1439
+ const regex = new RegExp(pattern, flags + "g");
1440
+ const lines = content.split("\n");
1441
+ const matches = [];
1442
+ let charIndex = 0;
1443
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
1444
+ const line = lines[lineNum];
1445
+ let match;
1446
+ const lineRegex = new RegExp(pattern, flags + "g");
1447
+ while ((match = lineRegex.exec(line)) !== null) {
1448
+ matches.push({
1449
+ match: match[0],
1450
+ line,
1451
+ lineNum: lineNum + 1,
1452
+ index: charIndex + match.index,
1453
+ groups: match.slice(1)
1454
+ });
1455
+ }
1456
+ charIndex += line.length + 1;
1457
+ }
1458
+ return matches;
1459
+ }
1460
+ case "count": {
1461
+ const arr = evaluateExpr(args[0], content, bindings);
1462
+ if (Array.isArray(arr)) return arr.length;
1463
+ return 0;
1464
+ }
1465
+ case "map": {
1466
+ const arr = evaluateExpr(args[0], content, bindings);
1467
+ const lambdaExpr = args[1];
1468
+ if (!Array.isArray(lambdaExpr) || lambdaExpr[0] !== "lambda") {
1469
+ throw new Error("map requires a lambda expression");
1470
+ }
1471
+ const params = lambdaExpr[1];
1472
+ const body = lambdaExpr[2];
1473
+ const paramName = Array.isArray(params) ? params[0] : params;
1474
+ return arr.map((item) => {
1475
+ const localBindings = new Map(bindings);
1476
+ localBindings.set(paramName, item);
1477
+ return evaluateExpr(body, content, localBindings);
1478
+ });
1479
+ }
1480
+ case "filter": {
1481
+ const arr = evaluateExpr(args[0], content, bindings);
1482
+ const lambdaExpr = args[1];
1483
+ if (!Array.isArray(lambdaExpr) || lambdaExpr[0] !== "lambda") {
1484
+ throw new Error("filter requires a lambda expression");
1485
+ }
1486
+ const params = lambdaExpr[1];
1487
+ const body = lambdaExpr[2];
1488
+ const paramName = Array.isArray(params) ? params[0] : params;
1489
+ return arr.filter((item) => {
1490
+ const localBindings = new Map(bindings);
1491
+ localBindings.set(paramName, item);
1492
+ return evaluateExpr(body, content, localBindings);
1493
+ });
1494
+ }
1495
+ case "first": {
1496
+ const arr = evaluateExpr(args[0], content, bindings);
1497
+ return arr[0];
1498
+ }
1499
+ case "last": {
1500
+ const arr = evaluateExpr(args[0], content, bindings);
1501
+ return arr[arr.length - 1];
1502
+ }
1503
+ case "take": {
1504
+ const arr = evaluateExpr(args[0], content, bindings);
1505
+ const n = evaluateExpr(args[1], content, bindings);
1506
+ return arr.slice(0, n);
1507
+ }
1508
+ case "sort": {
1509
+ const arr = evaluateExpr(args[0], content, bindings);
1510
+ const key = evaluateExpr(args[1], content, bindings);
1511
+ return [...arr].sort((a, b) => {
1512
+ const aVal = a[key];
1513
+ const bVal = b[key];
1514
+ if (typeof aVal === "number" && typeof bVal === "number") {
1515
+ return aVal - bVal;
1516
+ }
1517
+ return String(aVal).localeCompare(String(bVal));
1518
+ });
1519
+ }
1520
+ case "match": {
1521
+ const str = evaluateExpr(args[0], content, bindings);
1522
+ const strValue = typeof str === "object" && str !== null && "line" in str ? str.line : String(str);
1523
+ const pattern = evaluateExpr(args[1], content, bindings);
1524
+ const group = args[2] ? evaluateExpr(args[2], content, bindings) : 0;
1525
+ const regex = new RegExp(pattern);
1526
+ const match = strValue.match(regex);
1527
+ if (match) {
1528
+ return match[group] || null;
1529
+ }
1530
+ return null;
1531
+ }
1532
+ default:
1533
+ throw new Error(`Unknown command: ${op}`);
1534
+ }
1535
+ }
1536
+ function extractCommand(response) {
1537
+ const finalMatch = response.match(/<<<FINAL>>>([\s\S]*?)<<<END>>>/);
1538
+ if (finalMatch) {
1539
+ return { finalAnswer: finalMatch[1].trim() };
1540
+ }
1541
+ const sexpMatch = response.match(/\([^)]*(?:\([^)]*\)[^)]*)*\)/);
1542
+ if (sexpMatch) {
1543
+ return { command: sexpMatch[0] };
1544
+ }
1545
+ return {};
1546
+ }
1547
+ async function analyze(provider, documentPath, query, options = {}) {
1548
+ const {
1549
+ maxTurns = 15,
1550
+ verbose = false,
1551
+ onProgress
1552
+ } = options;
1553
+ const dynamicLimit = Math.min(getTurnLimit(query), maxTurns);
1554
+ const content = readFileSync4(documentPath, "utf-8");
1555
+ const fileCount = (content.match(/^FILE:/gm) || []).length;
1556
+ const lineCount = content.split("\n").length;
1557
+ const bindings = /* @__PURE__ */ new Map();
1558
+ const commands = [];
1559
+ const messages = [
1560
+ {
1561
+ role: "system",
1562
+ content: buildSystemPrompt(query)
1563
+ },
1564
+ {
1565
+ role: "user",
1566
+ content: `CODEBASE SNAPSHOT:
1567
+ - Total size: ${content.length.toLocaleString()} characters
1568
+ - Files: ${fileCount}
1569
+ - Lines: ${lineCount.toLocaleString()}
1570
+
1571
+ Files are marked with "FILE: ./path/to/file" headers.
1572
+
1573
+ QUERY: ${query}
1574
+
1575
+ Begin analysis. You have ${dynamicLimit} turns maximum - provide final answer before then.`
1576
+ }
1577
+ ];
1578
+ for (let turn = 1; turn <= dynamicLimit; turn++) {
1579
+ const isLastTurn = turn === dynamicLimit;
1580
+ const isNearEnd = turn >= dynamicLimit - 2;
1581
+ if (verbose) {
1582
+ console.log(`
1583
+ [Turn ${turn}/${dynamicLimit}] Querying LLM...`);
1584
+ }
1585
+ const result = await provider.complete(messages);
1586
+ const response = result.content;
1587
+ if (verbose) {
1588
+ console.log(`[Turn ${turn}] Response: ${response.slice(0, 200)}...`);
1589
+ }
1590
+ const extracted = extractCommand(response);
1591
+ if (extracted.finalAnswer) {
1592
+ return {
1593
+ answer: extracted.finalAnswer,
1594
+ turns: turn,
1595
+ commands,
1596
+ success: true
1597
+ };
1598
+ }
1599
+ if (!extracted.command) {
1600
+ messages.push({ role: "assistant", content: response });
1601
+ messages.push({ role: "user", content: "Please provide a Nucleus command or final answer." });
1602
+ continue;
1603
+ }
1604
+ const command = extracted.command;
1605
+ commands.push(command);
1606
+ if (verbose) {
1607
+ console.log(`[Turn ${turn}] Command: ${command}`);
1608
+ }
1609
+ try {
1610
+ const cmdResult = executeNucleus(command, content, bindings);
1611
+ bindings.set("RESULTS", cmdResult);
1612
+ bindings.set(`_${turn}`, cmdResult);
1613
+ const resultStr = JSON.stringify(cmdResult, null, 2);
1614
+ const truncatedResult = resultStr.length > 2e3 ? resultStr.slice(0, 2e3) + "...[truncated]" : resultStr;
1615
+ if (verbose) {
1616
+ console.log(`[Turn ${turn}] Result: ${truncatedResult.slice(0, 500)}...`);
1617
+ }
1618
+ onProgress?.(turn, command, cmdResult);
1619
+ messages.push({ role: "assistant", content: command });
1620
+ let userMessage = `Result:
1621
+ ${truncatedResult}`;
1622
+ if (isNearEnd && !isLastTurn) {
1623
+ userMessage += `
1624
+
1625
+ \u26A0\uFE0F ${dynamicLimit - turn} turns remaining. Start forming your final answer.`;
1626
+ }
1627
+ messages.push({ role: "user", content: userMessage });
1628
+ if (isLastTurn) {
1629
+ messages.push({
1630
+ role: "user",
1631
+ content: "STOP SEARCHING. Based on everything you found, provide your final answer NOW using <<<FINAL>>>your answer<<<END>>>"
1632
+ });
1633
+ const finalResult = await provider.complete(messages);
1634
+ const finalExtracted = extractCommand(finalResult.content);
1635
+ if (finalExtracted.finalAnswer) {
1636
+ return {
1637
+ answer: finalExtracted.finalAnswer,
1638
+ turns: turn,
1639
+ commands,
1640
+ success: true
1641
+ };
1642
+ }
1643
+ return {
1644
+ answer: finalResult.content,
1645
+ turns: turn,
1646
+ commands,
1647
+ success: true
1648
+ };
1649
+ }
1650
+ } catch (error) {
1651
+ const errMsg = error instanceof Error ? error.message : String(error);
1652
+ if (verbose) {
1653
+ console.log(`[Turn ${turn}] Error: ${errMsg}`);
1654
+ }
1655
+ messages.push({ role: "assistant", content: command });
1656
+ messages.push({ role: "user", content: `Error executing command: ${errMsg}` });
1657
+ }
1658
+ }
1659
+ return {
1660
+ answer: "Maximum turns reached without final answer",
1661
+ turns: dynamicLimit,
1662
+ commands,
1663
+ success: false,
1664
+ error: "Max turns reached"
1665
+ };
1666
+ }
1667
+ function searchDocument(documentPath, pattern, options = {}) {
1668
+ const content = readFileSync4(documentPath, "utf-8");
1669
+ const flags = options.caseInsensitive ? "gi" : "g";
1670
+ const regex = new RegExp(pattern, flags);
1671
+ const lines = content.split("\n");
1672
+ const matches = [];
1673
+ let charIndex = 0;
1674
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
1675
+ const line = lines[lineNum];
1676
+ let match;
1677
+ const lineRegex = new RegExp(pattern, flags);
1678
+ while ((match = lineRegex.exec(line)) !== null) {
1679
+ matches.push({
1680
+ match: match[0],
1681
+ line,
1682
+ lineNum: lineNum + 1,
1683
+ index: charIndex + match.index,
1684
+ groups: match.slice(1)
1685
+ });
1686
+ if (options.maxResults && matches.length >= options.maxResults) {
1687
+ return matches;
1688
+ }
1689
+ }
1690
+ charIndex += line.length + 1;
1691
+ }
1692
+ return matches;
1693
+ }
1694
+
1695
+ // src/providers/index.ts
1696
+ init_esm_shims();
1697
+
1698
+ // src/providers/openai-compatible.ts
1699
+ init_esm_shims();
1700
+ var OpenAICompatibleProvider = class {
1701
+ name;
1702
+ config;
1703
+ constructor(name, config) {
1704
+ this.name = name;
1705
+ this.config = config;
1706
+ if (!config.apiKey) {
1707
+ throw new Error(`API key is required for ${name} provider`);
1708
+ }
1709
+ if (!config.baseUrl) {
1710
+ throw new Error(`Base URL is required for ${name} provider`);
1711
+ }
1712
+ }
1713
+ async complete(messages, options) {
1714
+ const endpoint = `${this.config.baseUrl}/chat/completions`;
1715
+ const body = {
1716
+ model: this.config.model,
1717
+ messages: messages.map((m) => ({
1718
+ role: m.role,
1719
+ content: m.content
1720
+ })),
1721
+ temperature: options?.temperature ?? this.config.options?.temperature ?? 0.2,
1722
+ max_tokens: options?.maxTokens ?? this.config.options?.max_tokens ?? 4096,
1723
+ ...options?.stopSequences && { stop: options.stopSequences }
1724
+ };
1725
+ const response = await fetch(endpoint, {
1726
+ method: "POST",
1727
+ headers: {
1728
+ "Content-Type": "application/json",
1729
+ "Authorization": `Bearer ${this.config.apiKey}`
1730
+ },
1731
+ body: JSON.stringify(body)
1732
+ });
1733
+ if (!response.ok) {
1734
+ const errorText = await response.text();
1735
+ throw new Error(`${this.name} API error (${response.status}): ${errorText}`);
1736
+ }
1737
+ const data = await response.json();
1738
+ const choice = data.choices[0];
1739
+ return {
1740
+ content: choice.message.content || "",
1741
+ finishReason: choice.finish_reason === "stop" ? "stop" : choice.finish_reason === "length" ? "length" : "error",
1742
+ usage: data.usage ? {
1743
+ promptTokens: data.usage.prompt_tokens,
1744
+ completionTokens: data.usage.completion_tokens,
1745
+ totalTokens: data.usage.total_tokens
1746
+ } : void 0
1747
+ };
1748
+ }
1749
+ async healthCheck() {
1750
+ try {
1751
+ const result = await this.complete([
1752
+ { role: "user", content: 'Say "ok"' }
1753
+ ], { maxTokens: 10 });
1754
+ return result.content.length > 0;
1755
+ } catch {
1756
+ return false;
1757
+ }
1758
+ }
1759
+ };
1760
+ function createZAIProvider(config) {
1761
+ return new OpenAICompatibleProvider("ZAI", {
1762
+ ...config,
1763
+ baseUrl: config.baseUrl || "https://api.z.ai/api/coding/paas/v4",
1764
+ model: config.model || "glm-4.7"
1765
+ });
1766
+ }
1767
+ function createOpenAIProvider(config) {
1768
+ return new OpenAICompatibleProvider("OpenAI", {
1769
+ ...config,
1770
+ baseUrl: config.baseUrl || "https://api.openai.com/v1",
1771
+ model: config.model || "gpt-4o"
1772
+ });
1773
+ }
1774
+ function createDeepSeekProvider(config) {
1775
+ return new OpenAICompatibleProvider("DeepSeek", {
1776
+ ...config,
1777
+ baseUrl: config.baseUrl || "https://api.deepseek.com",
1778
+ model: config.model || "deepseek-chat"
1779
+ });
1780
+ }
1781
+
1782
+ // src/providers/ollama.ts
1783
+ init_esm_shims();
1784
+ var OllamaProvider = class {
1785
+ name = "Ollama";
1786
+ config;
1787
+ constructor(config) {
1788
+ this.config = {
1789
+ ...config,
1790
+ baseUrl: config.baseUrl || "http://localhost:11434",
1791
+ model: config.model || "qwen2.5-coder:7b"
1792
+ };
1793
+ }
1794
+ async complete(messages, options) {
1795
+ const endpoint = `${this.config.baseUrl}/api/chat`;
1796
+ const body = {
1797
+ model: this.config.model,
1798
+ messages: messages.map((m) => ({
1799
+ role: m.role,
1800
+ content: m.content
1801
+ })),
1802
+ stream: false,
1803
+ options: {
1804
+ temperature: options?.temperature ?? this.config.options?.temperature ?? 0.2,
1805
+ num_ctx: this.config.options?.num_ctx ?? 8192
1806
+ }
1807
+ };
1808
+ const response = await fetch(endpoint, {
1809
+ method: "POST",
1810
+ headers: {
1811
+ "Content-Type": "application/json"
1812
+ },
1813
+ body: JSON.stringify(body)
1814
+ });
1815
+ if (!response.ok) {
1816
+ const errorText = await response.text();
1817
+ throw new Error(`Ollama API error (${response.status}): ${errorText}`);
1818
+ }
1819
+ const data = await response.json();
1820
+ return {
1821
+ content: data.message.content || "",
1822
+ finishReason: data.done ? "stop" : "error",
1823
+ usage: data.eval_count ? {
1824
+ promptTokens: data.prompt_eval_count || 0,
1825
+ completionTokens: data.eval_count,
1826
+ totalTokens: (data.prompt_eval_count || 0) + data.eval_count
1827
+ } : void 0
1828
+ };
1829
+ }
1830
+ async healthCheck() {
1831
+ try {
1832
+ const response = await fetch(`${this.config.baseUrl}/api/tags`);
1833
+ if (!response.ok) return false;
1834
+ const data = await response.json();
1835
+ const hasModel = data.models.some(
1836
+ (m) => m.name === this.config.model || m.name.startsWith(this.config.model + ":")
1837
+ );
1838
+ return hasModel;
1839
+ } catch {
1840
+ return false;
1841
+ }
1842
+ }
1843
+ /**
1844
+ * List available models
1845
+ */
1846
+ async listModels() {
1847
+ try {
1848
+ const response = await fetch(`${this.config.baseUrl}/api/tags`);
1849
+ if (!response.ok) return [];
1850
+ const data = await response.json();
1851
+ return data.models.map((m) => m.name);
1852
+ } catch {
1853
+ return [];
1854
+ }
1855
+ }
1856
+ };
1857
+ function createOllamaProvider(config) {
1858
+ return new OllamaProvider(config);
1859
+ }
1860
+
1861
+ // src/providers/anthropic.ts
1862
+ init_esm_shims();
1863
+ var AnthropicProvider = class {
1864
+ name = "Anthropic";
1865
+ config;
1866
+ constructor(config) {
1867
+ if (!config.apiKey) {
1868
+ throw new Error("API key is required for Anthropic provider");
1869
+ }
1870
+ this.config = {
1871
+ ...config,
1872
+ baseUrl: config.baseUrl || "https://api.anthropic.com",
1873
+ model: config.model || "claude-sonnet-4-20250514"
1874
+ };
1875
+ }
1876
+ async complete(messages, options) {
1877
+ const endpoint = `${this.config.baseUrl}/v1/messages`;
1878
+ const systemMessage = messages.find((m) => m.role === "system");
1879
+ const nonSystemMessages = messages.filter((m) => m.role !== "system");
1880
+ const body = {
1881
+ model: this.config.model,
1882
+ max_tokens: options?.maxTokens ?? this.config.options?.max_tokens ?? 4096,
1883
+ ...systemMessage && { system: systemMessage.content },
1884
+ messages: nonSystemMessages.map((m) => ({
1885
+ role: m.role,
1886
+ content: m.content
1887
+ })),
1888
+ ...options?.temperature !== void 0 && { temperature: options.temperature },
1889
+ ...options?.stopSequences && { stop_sequences: options.stopSequences }
1890
+ };
1891
+ const response = await fetch(endpoint, {
1892
+ method: "POST",
1893
+ headers: {
1894
+ "Content-Type": "application/json",
1895
+ "x-api-key": this.config.apiKey,
1896
+ "anthropic-version": "2023-06-01"
1897
+ },
1898
+ body: JSON.stringify(body)
1899
+ });
1900
+ if (!response.ok) {
1901
+ const errorText = await response.text();
1902
+ throw new Error(`Anthropic API error (${response.status}): ${errorText}`);
1903
+ }
1904
+ const data = await response.json();
1905
+ const textContent = data.content.filter((c) => c.type === "text").map((c) => c.text).join("");
1906
+ return {
1907
+ content: textContent,
1908
+ finishReason: data.stop_reason === "end_turn" ? "stop" : data.stop_reason === "max_tokens" ? "length" : "error",
1909
+ usage: {
1910
+ promptTokens: data.usage.input_tokens,
1911
+ completionTokens: data.usage.output_tokens,
1912
+ totalTokens: data.usage.input_tokens + data.usage.output_tokens
1913
+ }
1914
+ };
1915
+ }
1916
+ async healthCheck() {
1917
+ try {
1918
+ const result = await this.complete([
1919
+ { role: "user", content: 'Say "ok"' }
1920
+ ], { maxTokens: 10 });
1921
+ return result.content.length > 0;
1922
+ } catch {
1923
+ return false;
1924
+ }
1925
+ }
1926
+ };
1927
+ function createAnthropicProvider(config) {
1928
+ return new AnthropicProvider(config);
1929
+ }
1930
+
1931
+ // src/providers/index.ts
1932
+ function createProvider(config) {
1933
+ const providerType = config.provider;
1934
+ const providerConfig = config.providers[providerType];
1935
+ if (!providerConfig) {
1936
+ throw new Error(`No configuration found for provider: ${providerType}`);
1937
+ }
1938
+ return createProviderByType(providerType, providerConfig);
1939
+ }
1940
+ function createProviderByType(type, config) {
1941
+ switch (type) {
1942
+ case "zai":
1943
+ return createZAIProvider(config);
1944
+ case "openai":
1945
+ return createOpenAIProvider(config);
1946
+ case "deepseek":
1947
+ return createDeepSeekProvider(config);
1948
+ case "ollama":
1949
+ return createOllamaProvider(config);
1950
+ case "anthropic":
1951
+ return createAnthropicProvider(config);
1952
+ default:
1953
+ throw new Error(`Unknown provider type: ${type}`);
1954
+ }
1955
+ }
1956
+ function getProviderDisplayName(type) {
1957
+ switch (type) {
1958
+ case "zai":
1959
+ return "ZAI (GLM)";
1960
+ case "openai":
1961
+ return "OpenAI";
1962
+ case "deepseek":
1963
+ return "DeepSeek";
1964
+ case "ollama":
1965
+ return "Ollama (Local)";
1966
+ case "anthropic":
1967
+ return "Anthropic (Claude)";
1968
+ default:
1969
+ return type;
1970
+ }
1971
+ }
1972
+ function listProviderTypes() {
1973
+ return ["zai", "anthropic", "openai", "deepseek", "ollama"];
1974
+ }
1975
+
1976
+ // src/cli.ts
1977
+ init_onboarding();
1978
+ var program = new Command();
1979
+ program.name("argus").description("Codebase Intelligence Beyond Context Limits").version("1.2.0");
1980
+ program.command("init").description("Interactive setup wizard").action(async () => {
1981
+ console.log("\n\u{1F52E} Argus Setup Wizard\n");
1982
+ console.log("This will configure your AI provider and create ~/.argus/config.json\n");
1983
+ const inquirer = await import("inquirer");
1984
+ const providers = listProviderTypes();
1985
+ const providerChoices = providers.map((p) => ({
1986
+ name: `${getProviderDisplayName(p)} - ${getProviderDescription(p)}`,
1987
+ value: p
1988
+ }));
1989
+ const answers = await inquirer.default.prompt([
1990
+ {
1991
+ type: "list",
1992
+ name: "provider",
1993
+ message: "Select your AI provider:",
1994
+ choices: providerChoices
1995
+ },
1996
+ {
1997
+ type: "input",
1998
+ name: "apiKey",
1999
+ message: "Enter your API key:",
2000
+ when: (ans) => ans.provider !== "ollama",
2001
+ validate: (input) => input.length > 0 || "API key is required"
2002
+ },
2003
+ {
2004
+ type: "input",
2005
+ name: "model",
2006
+ message: "Enter model name (leave empty for default):",
2007
+ default: (ans) => PROVIDER_DEFAULTS[ans.provider]?.model || ""
2008
+ },
2009
+ {
2010
+ type: "input",
2011
+ name: "baseUrl",
2012
+ message: "Enter custom base URL (leave empty for default):",
2013
+ when: (ans) => ans.provider === "ollama",
2014
+ default: "http://localhost:11434"
2015
+ }
2016
+ ]);
2017
+ const config = {
2018
+ provider: answers.provider,
2019
+ providers: {
2020
+ [answers.provider]: {
2021
+ ...answers.apiKey && { apiKey: answers.apiKey },
2022
+ ...answers.baseUrl && { baseUrl: answers.baseUrl },
2023
+ model: answers.model || PROVIDER_DEFAULTS[answers.provider]?.model || "",
2024
+ ...PROVIDER_DEFAULTS[answers.provider]
2025
+ }
2026
+ },
2027
+ defaults: {
2028
+ maxTurns: 15,
2029
+ turnTimeoutMs: 6e4,
2030
+ snapshotExtensions: ["ts", "tsx", "js", "jsx", "rs", "py", "go", "java", "rb", "php", "md"],
2031
+ excludePatterns: ["node_modules", ".git", "target", "dist", "build", ".next"]
2032
+ }
2033
+ };
2034
+ saveConfig(config);
2035
+ console.log(`
2036
+ \u2705 Configuration saved to ${getConfigPath()}`);
2037
+ console.log("\n\u{1F50D} Testing connection...");
2038
+ try {
2039
+ const provider = createProvider(config);
2040
+ const healthy = await provider.healthCheck();
2041
+ if (healthy) {
2042
+ console.log("\u2705 Connection successful!\n");
2043
+ } else {
2044
+ console.log("\u26A0\uFE0F Connection test failed. Please check your configuration.\n");
2045
+ }
2046
+ } catch (error) {
2047
+ console.log(`\u26A0\uFE0F Connection test failed: ${error instanceof Error ? error.message : error}
2048
+ `);
2049
+ }
2050
+ console.log("Next steps:");
2051
+ console.log(" argus snapshot ./my-project -o snapshot.txt");
2052
+ console.log(' argus analyze snapshot.txt "What are the main modules?"');
2053
+ console.log(" argus mcp install # Add to Claude Code");
2054
+ });
2055
+ program.command("update").description("Update Argus to the latest version").action(() => {
2056
+ console.log("\n\u{1F504} Updating Argus...\n");
2057
+ try {
2058
+ execSync("npm install -g https://github.com/sashabogi/argus/tarball/main", { stdio: "inherit" });
2059
+ console.log("\n\u2705 Argus updated successfully!");
2060
+ console.log("\nRun `argus --version` to check the new version.");
2061
+ } catch (error) {
2062
+ console.error("\n\u274C Update failed. Try manually:");
2063
+ console.error(" npm install -g https://github.com/sashabogi/argus/tarball/main");
2064
+ process.exit(1);
2065
+ }
2066
+ });
2067
+ program.command("analyze <path> <query>").description("Analyze a codebase or snapshot with AI").option("-p, --provider <provider>", "Override default provider").option("-t, --max-turns <n>", "Maximum reasoning turns", "15").option("-v, --verbose", "Show detailed execution logs").action(async (path2, query, opts) => {
2068
+ const config = loadConfig();
2069
+ if (opts.provider) {
2070
+ config.provider = opts.provider;
2071
+ }
2072
+ const errors = validateConfig(config);
2073
+ if (errors.length > 0) {
2074
+ console.error("Configuration errors:");
2075
+ errors.forEach((e) => console.error(` - ${e}`));
2076
+ console.error("\nRun `argus init` to configure.");
2077
+ process.exit(1);
2078
+ }
2079
+ const resolvedPath = resolve(path2);
2080
+ if (!existsSync4(resolvedPath)) {
2081
+ console.error(`File not found: ${resolvedPath}`);
2082
+ process.exit(1);
2083
+ }
2084
+ let snapshotPath = resolvedPath;
2085
+ let tempSnapshot = false;
2086
+ const stats = statSync2(resolvedPath);
2087
+ if (stats.isDirectory()) {
2088
+ console.log("\u{1F4F8} Creating snapshot of codebase...");
2089
+ snapshotPath = join4(homedir2(), ".argus", `temp-${Date.now()}.txt`);
2090
+ ensureConfigDir();
2091
+ const result = createSnapshot(resolvedPath, snapshotPath, {
2092
+ extensions: config.defaults.snapshotExtensions,
2093
+ excludePatterns: config.defaults.excludePatterns
2094
+ });
2095
+ console.log(` ${result.fileCount} files, ${formatSize(result.totalSize)}`);
2096
+ tempSnapshot = true;
2097
+ }
2098
+ console.log(`
2099
+ \u{1F50D} Analyzing with ${getProviderDisplayName(config.provider)}...`);
2100
+ console.log(` Query: ${query}
2101
+ `);
2102
+ try {
2103
+ const provider = createProvider(config);
2104
+ const result = await analyze(provider, snapshotPath, query, {
2105
+ maxTurns: parseInt(opts.maxTurns),
2106
+ verbose: opts.verbose,
2107
+ onProgress: (turn, cmd) => {
2108
+ if (!opts.verbose) {
2109
+ process.stdout.write(`\r Turn ${turn}: ${cmd.slice(0, 50)}...`);
2110
+ }
2111
+ }
2112
+ });
2113
+ if (!opts.verbose) {
2114
+ console.log("\n");
2115
+ }
2116
+ if (result.success) {
2117
+ console.log("\u{1F4CB} Answer:\n");
2118
+ console.log(result.answer);
2119
+ console.log(`
2120
+ (${result.turns} turns, ${result.commands.length} commands)`);
2121
+ } else {
2122
+ console.log("\u26A0\uFE0F Analysis incomplete:");
2123
+ console.log(result.answer);
2124
+ if (result.error) {
2125
+ console.log(`Error: ${result.error}`);
2126
+ }
2127
+ }
2128
+ } finally {
2129
+ if (tempSnapshot && existsSync4(snapshotPath)) {
2130
+ unlinkSync(snapshotPath);
2131
+ }
2132
+ }
2133
+ });
2134
+ program.command("snapshot <path>").description("Create a codebase snapshot for analysis").option("-o, --output <file>", "Output file path").option("-e, --extensions <exts>", "File extensions to include (comma-separated)").option("--exclude <patterns>", "Patterns to exclude (comma-separated)").option("--enhanced", "Include structural metadata (import graph, exports index)").action((path2, opts) => {
2135
+ const config = loadConfig();
2136
+ const resolvedPath = resolve(path2);
2137
+ if (!existsSync4(resolvedPath)) {
2138
+ console.error(`Path not found: ${resolvedPath}`);
2139
+ process.exit(1);
2140
+ }
2141
+ const outputPath = opts.output || `${basename(resolvedPath)}-snapshot.txt`;
2142
+ console.log("\u{1F4F8} Creating codebase snapshot...");
2143
+ console.log(` Source: ${resolvedPath}`);
2144
+ console.log(` Output: ${outputPath}`);
2145
+ const extensions = opts.extensions ? opts.extensions.split(",").map((e) => e.trim()) : config.defaults.snapshotExtensions;
2146
+ const excludePatterns = opts.exclude ? opts.exclude.split(",").map((p) => p.trim()) : config.defaults.excludePatterns;
2147
+ if (opts.enhanced) {
2148
+ console.log(" Mode: Enhanced (with import graph & exports index)");
2149
+ }
2150
+ const result = opts.enhanced ? createEnhancedSnapshot(resolvedPath, outputPath, { extensions, excludePatterns }) : createSnapshot(resolvedPath, outputPath, { extensions, excludePatterns });
2151
+ console.log(`
2152
+ \u2705 Snapshot created!`);
2153
+ console.log(` Files: ${result.fileCount}`);
2154
+ console.log(` Lines: ${result.totalLines.toLocaleString()}`);
2155
+ console.log(` Size: ${formatSize(result.totalSize)}`);
2156
+ if (opts.enhanced && "metadata" in result) {
2157
+ const meta = result.metadata;
2158
+ console.log(`
2159
+ \u{1F4CA} Structural Metadata:`);
2160
+ console.log(` Imports tracked: ${meta.imports.length}`);
2161
+ console.log(` Exports indexed: ${meta.exports.length}`);
2162
+ console.log(` Symbols indexed: ${Object.keys(meta.symbolIndex).length}`);
2163
+ }
2164
+ console.log(`
2165
+ Analyze with:`);
2166
+ console.log(` argus analyze ${outputPath} "Your query here"`);
2167
+ });
2168
+ program.command("query <snapshot> <query>").description("Query an existing snapshot").option("-v, --verbose", "Show detailed execution logs").action(async (snapshot, query, opts) => {
2169
+ await program.commands.find((c) => c.name() === "analyze")?.parseAsync([
2170
+ snapshot,
2171
+ query,
2172
+ ...opts.verbose ? ["-v"] : []
2173
+ ], { from: "user" });
2174
+ });
2175
+ program.command("search <snapshot> <pattern>").description("Fast grep search (no AI)").option("-i, --ignore-case", "Case-insensitive search").option("-n, --max-results <n>", "Maximum results", "50").action((snapshot, pattern, opts) => {
2176
+ const resolvedPath = resolve(snapshot);
2177
+ if (!existsSync4(resolvedPath)) {
2178
+ console.error(`File not found: ${resolvedPath}`);
2179
+ process.exit(1);
2180
+ }
2181
+ console.log(`\u{1F50D} Searching for: ${pattern}
2182
+ `);
2183
+ const matches = searchDocument(resolvedPath, pattern, {
2184
+ caseInsensitive: opts.ignoreCase,
2185
+ maxResults: parseInt(opts.maxResults)
2186
+ });
2187
+ if (matches.length === 0) {
2188
+ console.log("No matches found.");
2189
+ return;
2190
+ }
2191
+ console.log(`Found ${matches.length} matches:
2192
+ `);
2193
+ for (const match of matches) {
2194
+ console.log(`${match.lineNum}: ${match.line.trim()}`);
2195
+ }
2196
+ });
2197
+ program.command("status [path]").description("Check if snapshot is up to date").option("-s, --snapshot <file>", "Snapshot file to check", ".argus/snapshot.txt").action((path2, opts) => {
2198
+ const projectPath = path2 ? resolve(path2) : process.cwd();
2199
+ const snapshotPath = resolve(projectPath, opts.snapshot);
2200
+ console.log("\u{1F4CA} Argus Status\n");
2201
+ if (!existsSync4(snapshotPath)) {
2202
+ console.log("\u274C No snapshot found at:", snapshotPath);
2203
+ console.log("\nCreate one with:");
2204
+ console.log(` argus snapshot ${projectPath} -o ${snapshotPath}`);
2205
+ return;
2206
+ }
2207
+ const snapshotStats = statSync2(snapshotPath);
2208
+ const snapshotAge = Date.now() - snapshotStats.mtimeMs;
2209
+ const ageHours = Math.floor(snapshotAge / (1e3 * 60 * 60));
2210
+ const ageDays = Math.floor(ageHours / 24);
2211
+ console.log("Snapshot:", snapshotPath);
2212
+ console.log("Size:", formatSize(snapshotStats.size));
2213
+ if (ageDays > 0) {
2214
+ console.log("Age:", `${ageDays} day${ageDays > 1 ? "s" : ""} ago`);
2215
+ } else if (ageHours > 0) {
2216
+ console.log("Age:", `${ageHours} hour${ageHours > 1 ? "s" : ""} ago`);
2217
+ } else {
2218
+ console.log("Age:", "Less than an hour ago");
2219
+ }
2220
+ const config = loadConfig();
2221
+ let modifiedCount = 0;
2222
+ let newCount = 0;
2223
+ function checkDir(dir) {
2224
+ try {
2225
+ const entries = readdirSync2(dir, { withFileTypes: true });
2226
+ for (const entry of entries) {
2227
+ const fullPath = join4(dir, entry.name);
2228
+ if (config.defaults.excludePatterns.some((p) => fullPath.includes(p))) {
2229
+ continue;
2230
+ }
2231
+ if (entry.isDirectory()) {
2232
+ checkDir(fullPath);
2233
+ } else if (entry.isFile()) {
2234
+ const ext = entry.name.split(".").pop() || "";
2235
+ if (config.defaults.snapshotExtensions.includes(ext)) {
2236
+ const fileStats = statSync2(fullPath);
2237
+ if (fileStats.mtimeMs > snapshotStats.mtimeMs) {
2238
+ modifiedCount++;
2239
+ }
2240
+ if (fileStats.birthtimeMs > snapshotStats.mtimeMs) {
2241
+ newCount++;
2242
+ }
2243
+ }
2244
+ }
2245
+ }
2246
+ } catch {
2247
+ }
2248
+ }
2249
+ checkDir(projectPath);
2250
+ console.log("\nChanges since snapshot:");
2251
+ if (modifiedCount === 0 && newCount === 0) {
2252
+ console.log(" \u2705 No changes detected - snapshot is current");
2253
+ } else {
2254
+ if (newCount > 0) {
2255
+ console.log(` \u{1F4C4} ${newCount} new file${newCount > 1 ? "s" : ""}`);
2256
+ }
2257
+ if (modifiedCount > newCount) {
2258
+ console.log(` \u270F\uFE0F ${modifiedCount - newCount} modified file${modifiedCount - newCount > 1 ? "s" : ""}`);
2259
+ }
2260
+ console.log("\n\u26A0\uFE0F Snapshot may be stale. Refresh with:");
2261
+ console.log(` argus snapshot ${projectPath} -o ${snapshotPath}`);
2262
+ }
2263
+ if (ageDays >= 7) {
2264
+ console.log("\n\u{1F4A1} Tip: Snapshot is over a week old. Consider refreshing.");
2265
+ }
2266
+ });
2267
+ var mcpCommand = program.command("mcp").description("Manage Claude Code MCP integration");
2268
+ mcpCommand.command("install").description("Install Argus as an MCP server for Claude Code (global)").option("--no-claude-md", "Skip global CLAUDE.md injection").option("--no-onboarding", "Skip interactive onboarding").option("--reset-onboarding", "Re-run onboarding even if already completed").action(async (opts) => {
2269
+ let config = loadConfig();
2270
+ const shouldOnboard = opts.resetOnboarding || !config.onboardingComplete && opts.onboarding !== false;
2271
+ if (shouldOnboard) {
2272
+ try {
2273
+ const onboardingConfig = await runGlobalOnboarding();
2274
+ config.onboarding = onboardingConfig;
2275
+ config.onboardingComplete = true;
2276
+ saveConfig(config);
2277
+ } catch (error) {
2278
+ console.log("\n\u26A0\uFE0F Interactive onboarding skipped (non-interactive terminal)");
2279
+ console.log(" Using default settings. Run `argus mcp install --reset-onboarding` to configure later.\n");
2280
+ config.onboarding = DEFAULT_ONBOARDING_CONFIG;
2281
+ config.onboardingComplete = true;
2282
+ saveConfig(config);
2283
+ }
2284
+ }
2285
+ const errors = validateConfig(config);
2286
+ if (errors.length > 0) {
2287
+ console.error("Configuration errors - run `argus init` first:");
2288
+ errors.forEach((e) => console.error(` - ${e}`));
2289
+ process.exit(1);
2290
+ }
2291
+ const wrapperPath = join4(homedir2(), ".argus", "argus-mcp-wrapper");
2292
+ const providerConfig = config.providers[config.provider];
2293
+ let envVars = "";
2294
+ if (providerConfig?.apiKey) {
2295
+ envVars += `export ARGUS_API_KEY="${providerConfig.apiKey}"
2296
+ `;
2297
+ }
2298
+ if (providerConfig?.baseUrl) {
2299
+ envVars += `export ARGUS_BASE_URL="${providerConfig.baseUrl}"
2300
+ `;
2301
+ }
2302
+ const wrapperScript = `#!/bin/bash
2303
+ # Argus MCP Wrapper - Auto-generated
2304
+ export PATH="/opt/homebrew/bin:/usr/local/bin:$HOME/.npm-global/bin:$PATH"
2305
+ export ARGUS_PROVIDER="${config.provider}"
2306
+ export ARGUS_MODEL="${providerConfig?.model || ""}"
2307
+ ${envVars}
2308
+ exec argus-mcp "$@"
2309
+ `;
2310
+ ensureConfigDir();
2311
+ writeFileSync4(wrapperPath, wrapperScript, { mode: 493 });
2312
+ try {
2313
+ execSync(`claude mcp remove argus -s user 2>/dev/null || true`, { stdio: "ignore" });
2314
+ execSync(`claude mcp add argus -s user -- "${wrapperPath}"`, { stdio: "inherit" });
2315
+ console.log("\n\u2705 Argus MCP server installed for Claude Code!");
2316
+ } catch {
2317
+ console.log("\n\u26A0\uFE0F Could not automatically add to Claude Code.");
2318
+ console.log("Add manually by running:");
2319
+ console.log(` claude mcp add argus -s user -- "${wrapperPath}"`);
2320
+ }
2321
+ if (opts.claudeMd !== false) {
2322
+ const globalClaudeMdPath = join4(homedir2(), ".claude", "CLAUDE.md");
2323
+ if (existsSync4(globalClaudeMdPath)) {
2324
+ let content = readFileSync5(globalClaudeMdPath, "utf-8");
2325
+ if (content.includes("## Codebase Intelligence (Argus)")) {
2326
+ console.log("\u2713 Global CLAUDE.md already has Argus section");
2327
+ } else {
2328
+ content += GLOBAL_CLAUDE_MD_ARGUS_SECTION;
2329
+ writeFileSync4(globalClaudeMdPath, content);
2330
+ console.log("\u2705 Added Argus section to global ~/.claude/CLAUDE.md");
2331
+ console.log(" \u2192 This applies to ALL projects and ALL sub-agents");
2332
+ }
2333
+ } else {
2334
+ const claudeDir = join4(homedir2(), ".claude");
2335
+ if (!existsSync4(claudeDir)) {
2336
+ mkdirSync2(claudeDir, { recursive: true });
2337
+ }
2338
+ writeFileSync4(globalClaudeMdPath, GLOBAL_CLAUDE_MD_ARGUS_SECTION.trim());
2339
+ console.log("\u2705 Created global ~/.claude/CLAUDE.md with Argus section");
2340
+ }
2341
+ }
2342
+ console.log("\n\u{1F4CB} Next: Run `argus setup .` in any project to create a snapshot");
2343
+ });
2344
+ mcpCommand.command("uninstall").description("Remove Argus from Claude Code").action(() => {
2345
+ try {
2346
+ execSync("claude mcp remove argus -s user", { stdio: "inherit" });
2347
+ console.log("\n\u2705 Argus MCP server removed from Claude Code.");
2348
+ } catch {
2349
+ console.log("\n\u26A0\uFE0F Could not remove from Claude Code.");
2350
+ console.log("Remove manually by running:");
2351
+ console.log(" claude mcp remove argus -s user");
2352
+ }
2353
+ });
2354
+ var contextCommand = program.command("context").description("Generate architectural context for CLAUDE.md (survives compaction)");
2355
+ contextCommand.command("generate <path>").description("Generate architecture summary for a project").option("-o, --output <file>", "Output file (default: stdout)").option("-f, --format <format>", "Output format: markdown, json", "markdown").action(async (path2, opts) => {
2356
+ const config = loadConfig();
2357
+ const errors = validateConfig(config);
2358
+ if (errors.length > 0) {
2359
+ console.error("Configuration errors - run `argus init` first:");
2360
+ errors.forEach((e) => console.error(` - ${e}`));
2361
+ process.exit(1);
2362
+ }
2363
+ const resolvedPath = resolve(path2);
2364
+ if (!existsSync4(resolvedPath)) {
2365
+ console.error(`Path not found: ${resolvedPath}`);
2366
+ process.exit(1);
2367
+ }
2368
+ console.error("\u{1F4F8} Creating snapshot...");
2369
+ const snapshotPath = join4(homedir2(), ".argus", `context-${Date.now()}.txt`);
2370
+ ensureConfigDir();
2371
+ const snapshotResult = createSnapshot(resolvedPath, snapshotPath, {
2372
+ extensions: config.defaults.snapshotExtensions,
2373
+ excludePatterns: config.defaults.excludePatterns
2374
+ });
2375
+ console.error(` ${snapshotResult.fileCount} files, ${formatSize(snapshotResult.totalSize)}`);
2376
+ console.error("\u{1F9E0} Analyzing architecture...\n");
2377
+ try {
2378
+ const provider = createProvider(config);
2379
+ const moduleQuery = `List all the main directories/modules under src/ or the main source folder.
2380
+ For each module, based on its file names and code, describe its purpose in ONE sentence.
2381
+ Format as a bullet list: - **module_name/** - description`;
2382
+ const moduleResult = await analyze(provider, snapshotPath, moduleQuery, {
2383
+ maxTurns: 10,
2384
+ onProgress: (turn) => {
2385
+ process.stderr.write(`\r Analyzing modules (turn ${turn})...`);
2386
+ }
2387
+ });
2388
+ console.error("\n");
2389
+ const patternQuery = `What are the main coding patterns and conventions used? Look for:
2390
+ - Error handling approach
2391
+ - State management
2392
+ - API/data patterns
2393
+ - Testing patterns
2394
+ Keep it brief - one line per pattern.`;
2395
+ const patternResult = await analyze(provider, snapshotPath, patternQuery, {
2396
+ maxTurns: 8,
2397
+ onProgress: (turn) => {
2398
+ process.stderr.write(`\r Analyzing patterns (turn ${turn})...`);
2399
+ }
2400
+ });
2401
+ console.error("\n");
2402
+ const filesQuery = `What are the 5-10 most important files that a developer should understand first?
2403
+ List with file paths and one-line descriptions.`;
2404
+ const filesResult = await analyze(provider, snapshotPath, filesQuery, {
2405
+ maxTurns: 8,
2406
+ onProgress: (turn) => {
2407
+ process.stderr.write(`\r Finding key files (turn ${turn})...`);
2408
+ }
2409
+ });
2410
+ console.error("\n");
2411
+ const projectName = basename(resolvedPath);
2412
+ const output = generateContextMarkdown(projectName, {
2413
+ modules: moduleResult.answer || "Unable to analyze modules",
2414
+ patterns: patternResult.answer || "Unable to analyze patterns",
2415
+ keyFiles: filesResult.answer || "Unable to identify key files",
2416
+ fileCount: snapshotResult.fileCount,
2417
+ lineCount: snapshotResult.totalLines
2418
+ });
2419
+ if (opts.output) {
2420
+ writeFileSync4(opts.output, output);
2421
+ console.error(`\u2705 Context saved to ${opts.output}`);
2422
+ } else {
2423
+ console.log(output);
2424
+ }
2425
+ } finally {
2426
+ if (existsSync4(snapshotPath)) {
2427
+ unlinkSync(snapshotPath);
2428
+ }
2429
+ }
2430
+ });
2431
+ contextCommand.command("inject <path>").description("Add/update architecture section in CLAUDE.md").action(async (path2) => {
2432
+ const resolvedPath = resolve(path2);
2433
+ const claudeMdPath = join4(resolvedPath, "CLAUDE.md");
2434
+ console.error("Generating context...\n");
2435
+ const { execSync: execSync2 } = await import("child_process");
2436
+ try {
2437
+ const contextOutput = execSync2(
2438
+ `argus context generate "${resolvedPath}"`,
2439
+ { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
2440
+ );
2441
+ const marker = "<!-- ARGUS:CONTEXT -->";
2442
+ const endMarker = "<!-- /ARGUS:CONTEXT -->";
2443
+ const wrappedContext = `${marker}
2444
+ ${contextOutput}
2445
+ ${endMarker}`;
2446
+ if (existsSync4(claudeMdPath)) {
2447
+ let existing = readFileSync5(claudeMdPath, "utf-8");
2448
+ const markerRegex = new RegExp(`${marker}[\\s\\S]*?${endMarker}`, "g");
2449
+ if (markerRegex.test(existing)) {
2450
+ existing = existing.replace(markerRegex, wrappedContext);
2451
+ } else {
2452
+ existing = existing.trim() + "\n\n" + wrappedContext;
2453
+ }
2454
+ writeFileSync4(claudeMdPath, existing);
2455
+ console.log(`\u2705 Updated ${claudeMdPath}`);
2456
+ } else {
2457
+ writeFileSync4(claudeMdPath, wrappedContext);
2458
+ console.log(`\u2705 Created ${claudeMdPath}`);
2459
+ }
2460
+ console.log("\nClaude Code will now have persistent architectural knowledge!");
2461
+ console.log("This section survives compaction and restarts.");
2462
+ } catch (error) {
2463
+ console.error("Failed to generate context:", error);
2464
+ process.exit(1);
2465
+ }
2466
+ });
2467
+ contextCommand.command("refresh <path>").description("Regenerate architecture context (run after major changes)").action(async (path2) => {
2468
+ const resolvedPath = resolve(path2);
2469
+ console.log("Refreshing codebase context...\n");
2470
+ execSync(`argus context inject "${resolvedPath}"`, { stdio: "inherit" });
2471
+ });
2472
+ function generateContextMarkdown(projectName, data) {
2473
+ return `## Codebase Intelligence (Auto-generated by Argus)
2474
+
2475
+ > **This section provides architectural context that survives context compaction.**
2476
+ > Regenerate with: \`argus context refresh .\`
2477
+
2478
+ ### Project: ${projectName}
2479
+ - **Files:** ${data.fileCount}
2480
+ - **Lines:** ${data.lineCount.toLocaleString()}
2481
+
2482
+ ### Module Structure
2483
+
2484
+ ${data.modules}
2485
+
2486
+ ### Key Patterns & Conventions
2487
+
2488
+ ${data.patterns}
2489
+
2490
+ ### Important Files to Understand
2491
+
2492
+ ${data.keyFiles}
2493
+
2494
+ ### Using Argus for On-Demand Queries
2495
+
2496
+ When you need more specific information about this codebase:
2497
+
2498
+ \`\`\`bash
2499
+ # Find where something is implemented
2500
+ argus analyze . "Where is authentication handled?"
2501
+
2502
+ # Understand a specific module
2503
+ argus analyze . "What does the cognition/ module do?"
2504
+
2505
+ # Find code patterns
2506
+ argus search .argus/snapshot.txt "async fn.*Result"
2507
+ \`\`\`
2508
+
2509
+ ### After Compaction Checklist
2510
+
2511
+ If your context was compacted or you're starting fresh:
2512
+ 1. \u2705 This architecture section is still available (you're reading it)
2513
+ 2. Query @argus for specific questions about the codebase
2514
+ 3. Don't re-scan the entire codebase - use targeted queries
2515
+
2516
+ `;
2517
+ }
2518
+ program.command("config [key] [value]").description("View or modify configuration").action((key, value) => {
2519
+ const config = loadConfig();
2520
+ if (!key) {
2521
+ console.log("Current configuration:\n");
2522
+ console.log(JSON.stringify(config, null, 2));
2523
+ console.log(`
2524
+ Config file: ${getConfigPath()}`);
2525
+ return;
2526
+ }
2527
+ if (!value) {
2528
+ const parts2 = key.split(".");
2529
+ let current2 = config;
2530
+ for (const part of parts2) {
2531
+ if (current2 && typeof current2 === "object" && part in current2) {
2532
+ current2 = current2[part];
2533
+ } else {
2534
+ console.error(`Key not found: ${key}`);
2535
+ process.exit(1);
2536
+ }
2537
+ }
2538
+ console.log(JSON.stringify(current2, null, 2));
2539
+ return;
2540
+ }
2541
+ const parts = key.split(".");
2542
+ let current = config;
2543
+ for (let i = 0; i < parts.length - 1; i++) {
2544
+ const part = parts[i];
2545
+ if (!(part in current)) {
2546
+ current[part] = {};
2547
+ }
2548
+ current = current[part];
2549
+ }
2550
+ try {
2551
+ current[parts[parts.length - 1]] = JSON.parse(value);
2552
+ } catch {
2553
+ current[parts[parts.length - 1]] = value;
2554
+ }
2555
+ saveConfig(config);
2556
+ console.log(`\u2705 Set ${key} = ${value}`);
2557
+ });
2558
+ function formatSize(bytes) {
2559
+ if (bytes < 1024) return `${bytes} B`;
2560
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2561
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2562
+ }
2563
+ function getProviderDescription(provider) {
2564
+ switch (provider) {
2565
+ case "zai":
2566
+ return "GLM-4.7, best value for code";
2567
+ case "anthropic":
2568
+ return "Claude, highest quality";
2569
+ case "openai":
2570
+ return "GPT-4o, general purpose";
2571
+ case "deepseek":
2572
+ return "Budget-friendly";
2573
+ case "ollama":
2574
+ return "Free, local, private";
2575
+ default:
2576
+ return "";
2577
+ }
2578
+ }
2579
+ var GLOBAL_CLAUDE_MD_ARGUS_SECTION = `
2580
+
2581
+ ## Codebase Intelligence (Argus) \u2014 ALL AGENTS
2582
+
2583
+ > **This applies to the main session AND all sub-agents/tasks regardless of type.**
2584
+
2585
+ ### The Rule: Argus Before Multi-File Exploration
2586
+
2587
+ **Before reading more than 3 files to understand a codebase, use Argus MCP tools:**
2588
+
2589
+ 1. **Check for snapshot**: Look for \`.argus/snapshot.txt\` in the project
2590
+ 2. **Search first** (FREE): \`search_codebase(".argus/snapshot.txt", "pattern")\`
2591
+ 3. **Understand if needed** (~500 tokens): \`analyze_codebase(".argus/snapshot.txt", "How does X work?")\`
2592
+ 4. **Then read specific files**: Only the files Argus identified as relevant
2593
+
2594
+ ### Why This Matters
2595
+
2596
+ | Without Argus | With Argus |
2597
+ |---------------|------------|
2598
+ | Read 15+ files = 5,000-50,000 tokens | search \u2192 read 2 files = 100-500 tokens |
2599
+ | Slow, expensive, loses context | Fast, cheap, efficient |
2600
+
2601
+ ### For Sub-Agents
2602
+
2603
+ If you are a sub-agent (coder, tester, reviewer, debugger, or ANY other type):
2604
+ 1. Check if \`.argus/snapshot.txt\` exists in the project
2605
+ 2. If exploring the codebase, use \`search_codebase\` first (it's free)
2606
+ 3. This rule applies regardless of your specific role or instructions
2607
+
2608
+ ### Quick Reference
2609
+
2610
+ | Tool | Cost | Use For |
2611
+ |------|------|---------|
2612
+ | \`search_codebase\` | FREE | Finding files, patterns, definitions |
2613
+ | \`analyze_codebase\` | ~500 tokens | Architecture questions, understanding flows |
2614
+
2615
+ ### No Snapshot?
2616
+
2617
+ If \`.argus/snapshot.txt\` doesn't exist, proceed normally with native tools.
2618
+ The user can create one with: \`argus setup .\`
2619
+ `;
2620
+ var CLAUDE_MD_ARGUS_SECTION = `
2621
+ ## Codebase Intelligence (Argus) \u2014 APPLIES TO ALL AGENTS
2622
+
2623
+ > **This section applies to the main conversation AND all sub-agents/tasks.**
2624
+ > Any agent exploring the codebase should use Argus tools.
2625
+
2626
+ ### The Rule: Argus Before Multi-File Exploration
2627
+
2628
+ **Before reading more than 3 files to understand the codebase, use Argus MCP tools:**
2629
+
2630
+ \`\`\`
2631
+ # Step 1: Search (FREE, instant)
2632
+ search_codebase(".argus/snapshot.txt", "pattern-or-keyword")
2633
+
2634
+ # Step 2: If needed, understand architecture (~500 tokens)
2635
+ analyze_codebase(".argus/snapshot.txt", "How does X work?")
2636
+
2637
+ # Step 3: Read only the specific files you need
2638
+ Read(the-file-you-found.ts)
2639
+ \`\`\`
2640
+
2641
+ ### Why This Matters
2642
+
2643
+ | Approach | Tokens | Speed |
2644
+ |----------|--------|-------|
2645
+ | Read 15 files to find something | 5,000-50,000 | Slow |
2646
+ | search_codebase \u2192 Read 2 files | 100-500 | Fast |
2647
+
2648
+ ### Available Tools
2649
+
2650
+ | Tool | Use For | Cost |
2651
+ |------|---------|------|
2652
+ | \`search_codebase\` | Find files, patterns, definitions | **FREE** |
2653
+ | \`analyze_codebase\` | Architecture, "how does X work" | ~500 tokens |
2654
+ | \`create_snapshot\` | Refresh after major changes | ~100 tokens |
2655
+
2656
+ ### When to Use What
2657
+
2658
+ **Use Argus (\`.argus/snapshot.txt\`) for:**
2659
+ - Finding where something is defined or used
2660
+ - Understanding how modules connect
2661
+ - Debugging: "where is this function called?"
2662
+ - Architecture questions
2663
+ - After context compaction
2664
+
2665
+ **Use native Read/Search for:**
2666
+ - Single file you already know
2667
+ - Quick edits to known locations
2668
+ - Files you just created
2669
+
2670
+ ### For Sub-Agents / Background Tasks
2671
+
2672
+ If you are a sub-agent or background task:
2673
+ 1. Check if \`.argus/snapshot.txt\` exists
2674
+ 2. Use \`search_codebase\` before reading multiple files
2675
+ 3. This applies regardless of your specific role (coder, tester, reviewer, etc.)
2676
+
2677
+ ### Keeping Updated
2678
+
2679
+ \`\`\`bash
2680
+ argus status . # Check if stale
2681
+ argus snapshot . -o .argus/snapshot.txt # Refresh
2682
+ \`\`\`
2683
+ `;
2684
+ program.command("setup [path]").description("Set up Argus for a project (snapshot + key files + CLAUDE.md + .gitignore)").option("--no-claude-md", "Skip CLAUDE.md injection").option("--no-gitignore", "Skip .gitignore update").option("--no-onboarding", "Skip interactive key file selection").action(async (pathArg, opts) => {
2685
+ const projectPath = pathArg ? resolve(pathArg) : process.cwd();
2686
+ console.log("\u{1F680} Setting up Argus for project...\n");
2687
+ console.log(` Project: ${projectPath}
2688
+ `);
2689
+ const config = loadConfig();
2690
+ const onboardingConfig = config.onboarding || DEFAULT_ONBOARDING_CONFIG;
2691
+ const argusDir = join4(projectPath, ".argus");
2692
+ if (!existsSync4(argusDir)) {
2693
+ mkdirSync2(argusDir, { recursive: true });
2694
+ console.log("\u2705 Created .argus/ directory");
2695
+ } else {
2696
+ console.log("\u2713 .argus/ directory exists");
2697
+ }
2698
+ let projectConfig = onboardingConfig.projects[projectPath];
2699
+ if (!projectConfig && opts.onboarding !== false) {
2700
+ try {
2701
+ projectConfig = await runProjectOnboarding(projectPath, onboardingConfig, fs, path);
2702
+ config.onboarding = config.onboarding || DEFAULT_ONBOARDING_CONFIG;
2703
+ config.onboarding.projects[projectPath] = projectConfig;
2704
+ saveConfig(config);
2705
+ if (projectConfig.keyFiles.length > 0) {
2706
+ console.log(`
2707
+ \u2705 Tracking ${projectConfig.keyFiles.length} key file(s) for this project`);
2708
+ }
2709
+ } catch {
2710
+ console.log("\n\u26A0\uFE0F Interactive selection skipped (non-interactive terminal)");
2711
+ const detected = detectPotentialKeyFiles(projectPath, onboardingConfig.globalKeyPatterns, fs, path);
2712
+ projectConfig = {
2713
+ keyFiles: detected.filter((d) => d.matchedPattern).map((d) => d.path),
2714
+ customPatterns: [],
2715
+ lastScanDate: (/* @__PURE__ */ new Date()).toISOString()
2716
+ };
2717
+ }
2718
+ } else if (projectConfig) {
2719
+ console.log(`\u2713 Using existing project configuration (${projectConfig.keyFiles.length} key files)`);
2720
+ }
2721
+ const snapshotPath = join4(argusDir, "snapshot.txt");
2722
+ console.log("\n\u{1F4F8} Creating codebase snapshot...");
2723
+ const result = createSnapshot(projectPath, snapshotPath, {
2724
+ extensions: config.defaults.snapshotExtensions,
2725
+ excludePatterns: config.defaults.excludePatterns
2726
+ });
2727
+ console.log(`\u2705 Snapshot created: ${result.fileCount} files, ${result.totalLines.toLocaleString()} lines`);
2728
+ if (projectConfig && projectConfig.keyFiles.length > 0) {
2729
+ const keyFilesPath = join4(argusDir, "key-files.json");
2730
+ writeFileSync4(keyFilesPath, JSON.stringify({
2731
+ keyFiles: projectConfig.keyFiles,
2732
+ customPatterns: projectConfig.customPatterns,
2733
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
2734
+ }, null, 2));
2735
+ console.log("\u2705 Saved key files list to .argus/key-files.json");
2736
+ }
2737
+ if (opts.gitignore !== false) {
2738
+ const gitignorePath = join4(projectPath, ".gitignore");
2739
+ let gitignoreContent = "";
2740
+ if (existsSync4(gitignorePath)) {
2741
+ gitignoreContent = readFileSync5(gitignorePath, "utf-8");
2742
+ }
2743
+ if (!gitignoreContent.includes(".argus")) {
2744
+ const addition = gitignoreContent.endsWith("\n") ? "" : "\n";
2745
+ writeFileSync4(gitignorePath, gitignoreContent + addition + "\n# Argus codebase intelligence\n.argus/\n");
2746
+ console.log("\u2705 Added .argus/ to .gitignore");
2747
+ } else {
2748
+ console.log("\u2713 .argus/ already in .gitignore");
2749
+ }
2750
+ }
2751
+ if (opts.claudeMd !== false) {
2752
+ const claudeMdPath = join4(projectPath, "CLAUDE.md");
2753
+ if (existsSync4(claudeMdPath)) {
2754
+ let claudeMdContent = readFileSync5(claudeMdPath, "utf-8");
2755
+ if (claudeMdContent.includes("Codebase Intelligence (Argus)")) {
2756
+ console.log("\u2713 CLAUDE.md already has Argus section");
2757
+ } else {
2758
+ const firstHeadingMatch = claudeMdContent.match(/^#[^#].*$/m);
2759
+ if (firstHeadingMatch && firstHeadingMatch.index !== void 0) {
2760
+ const afterFirstHeading = claudeMdContent.indexOf("\n## ", firstHeadingMatch.index + 1);
2761
+ if (afterFirstHeading > 0) {
2762
+ claudeMdContent = claudeMdContent.slice(0, afterFirstHeading) + "\n" + CLAUDE_MD_ARGUS_SECTION + "\n" + claudeMdContent.slice(afterFirstHeading);
2763
+ } else {
2764
+ claudeMdContent += "\n" + CLAUDE_MD_ARGUS_SECTION;
2765
+ }
2766
+ } else {
2767
+ claudeMdContent += "\n" + CLAUDE_MD_ARGUS_SECTION;
2768
+ }
2769
+ writeFileSync4(claudeMdPath, claudeMdContent);
2770
+ console.log("\u2705 Added Argus section to CLAUDE.md");
2771
+ }
2772
+ } else {
2773
+ const newClaudeMd = `# Project Intelligence
2774
+
2775
+ This project uses Argus for efficient codebase analysis.
2776
+ ${CLAUDE_MD_ARGUS_SECTION}`;
2777
+ writeFileSync4(claudeMdPath, newClaudeMd);
2778
+ console.log("\u2705 Created CLAUDE.md with Argus section");
2779
+ }
2780
+ }
2781
+ console.log("\n\u{1F389} Argus setup complete!\n");
2782
+ console.log("Next steps:");
2783
+ console.log(" 1. Restart Claude Code to pick up CLAUDE.md changes");
2784
+ console.log(" 2. Ask Claude about your codebase architecture");
2785
+ console.log(" 3. Run `argus status` periodically to check if snapshot needs refresh");
2786
+ if (projectConfig && projectConfig.keyFiles.length > 0) {
2787
+ console.log(`
2788
+ \u{1F4A1} Key files tracked for context restoration:`);
2789
+ projectConfig.keyFiles.slice(0, 5).forEach((f) => console.log(` \u2022 ${f}`));
2790
+ if (projectConfig.keyFiles.length > 5) {
2791
+ console.log(` ... and ${projectConfig.keyFiles.length - 5} more`);
2792
+ }
2793
+ }
2794
+ });
2795
+ program.parse();
2796
+ //# sourceMappingURL=cli.mjs.map