@sporesec/arcana 3.0.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +25 -298
- package/dist/command-defs.d.ts +28 -0
- package/dist/command-defs.js +414 -0
- package/dist/commands/audit.js +18 -4
- package/dist/commands/clean.d.ts +1 -0
- package/dist/commands/clean.js +80 -0
- package/dist/commands/compress.d.ts +5 -0
- package/dist/commands/compress.js +38 -0
- package/dist/commands/config.js +40 -26
- package/dist/commands/create.js +2 -0
- package/dist/commands/curate.d.ts +39 -0
- package/dist/commands/curate.js +222 -0
- package/dist/commands/diff.js +2 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +61 -2
- package/dist/commands/import-cmd.js +5 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.js +107 -0
- package/dist/commands/info.js +19 -8
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +71 -0
- package/dist/commands/install.js +2 -0
- package/dist/commands/list.js +8 -0
- package/dist/commands/load.d.ts +10 -0
- package/dist/commands/load.js +130 -0
- package/dist/commands/lock.js +35 -24
- package/dist/commands/mcp.d.ts +4 -0
- package/dist/commands/mcp.js +87 -0
- package/dist/commands/outdated.js +8 -6
- package/dist/commands/providers.js +29 -21
- package/dist/commands/recommend.js +11 -3
- package/dist/commands/remember.d.ts +12 -0
- package/dist/commands/remember.js +111 -0
- package/dist/commands/scan.d.ts +2 -0
- package/dist/commands/scan.js +46 -8
- package/dist/commands/search.js +6 -0
- package/dist/commands/uninstall.js +36 -0
- package/dist/commands/update.js +27 -0
- package/dist/commands/validate.js +8 -0
- package/dist/commands/verify.js +2 -0
- package/dist/compress/engine.d.ts +21 -0
- package/dist/compress/engine.js +106 -0
- package/dist/compress/index.d.ts +7 -0
- package/dist/compress/index.js +10 -0
- package/dist/compress/rules/generic.d.ts +1 -0
- package/dist/compress/rules/generic.js +9 -0
- package/dist/compress/rules/git.d.ts +1 -0
- package/dist/compress/rules/git.js +113 -0
- package/dist/compress/rules/npm.d.ts +1 -0
- package/dist/compress/rules/npm.js +99 -0
- package/dist/compress/rules/test-runner.d.ts +1 -0
- package/dist/compress/rules/test-runner.js +103 -0
- package/dist/compress/rules/tsc.d.ts +1 -0
- package/dist/compress/rules/tsc.js +39 -0
- package/dist/compress/tracker.d.ts +16 -0
- package/dist/compress/tracker.js +45 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +29 -0
- package/dist/interactive/helpers.js +1 -0
- package/dist/interactive/menu.js +6 -1
- package/dist/interactive/optimize-flow.js +4 -4
- package/dist/mcp/install.d.ts +10 -0
- package/dist/mcp/install.js +109 -0
- package/dist/mcp/registry.d.ts +11 -0
- package/dist/mcp/registry.js +27 -0
- package/dist/providers/anthropics.d.ts +4 -0
- package/dist/providers/anthropics.js +10 -0
- package/dist/registry.js +4 -0
- package/dist/session/trim.d.ts +23 -0
- package/dist/session/trim.js +132 -0
- package/dist/utils/cache.js +2 -2
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +33 -14
- package/dist/utils/help.js +16 -8
- package/dist/utils/install-core.js +23 -1
- package/dist/utils/memory.d.ts +25 -0
- package/dist/utils/memory.js +103 -0
- package/dist/utils/project-context.js +4 -0
- package/dist/utils/scanner.d.ts +22 -1
- package/dist/utils/scanner.js +81 -9
- package/dist/utils/sessions.d.ts +2 -0
- package/dist/utils/sessions.js +36 -0
- package/dist/utils/ui.js +5 -0
- package/dist/utils/usage.d.ts +17 -0
- package/dist/utils/usage.js +83 -0
- package/package.json +42 -7
- package/dist/command-registry.d.ts +0 -10
- package/dist/command-registry.js +0 -65
- package/dist/commands/benchmark.d.ts +0 -4
- package/dist/commands/benchmark.js +0 -178
- package/dist/commands/compact.d.ts +0 -6
- package/dist/commands/compact.js +0 -239
- package/dist/commands/optimize.d.ts +0 -3
- package/dist/commands/optimize.js +0 -356
- package/dist/commands/profile.d.ts +0 -3
- package/dist/commands/profile.js +0 -274
- package/dist/commands/stats.d.ts +0 -3
- package/dist/commands/stats.js +0 -210
- package/dist/commands/team.d.ts +0 -3
- package/dist/commands/team.js +0 -291
- package/dist/interactive.d.ts +0 -1
- package/dist/interactive.js +0 -841
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { registerRule } from "../engine.js";
|
|
2
|
+
registerRule({
|
|
3
|
+
name: "git-status",
|
|
4
|
+
tools: ["git"],
|
|
5
|
+
compress(lines) {
|
|
6
|
+
// Detect git status output and compact it
|
|
7
|
+
const isStatus = lines.some((l) => /^(On branch|Changes|Untracked|modified:|new file:|deleted:)/.test(l.trim()));
|
|
8
|
+
if (!isStatus)
|
|
9
|
+
return lines;
|
|
10
|
+
const result = [];
|
|
11
|
+
let branch = "";
|
|
12
|
+
const staged = [];
|
|
13
|
+
const unstaged = [];
|
|
14
|
+
const untracked = [];
|
|
15
|
+
let section = "none";
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
if (trimmed.startsWith("On branch ")) {
|
|
19
|
+
branch = trimmed.replace("On branch ", "");
|
|
20
|
+
}
|
|
21
|
+
else if (trimmed.startsWith("Changes to be committed")) {
|
|
22
|
+
section = "staged";
|
|
23
|
+
}
|
|
24
|
+
else if (trimmed.startsWith("Changes not staged")) {
|
|
25
|
+
section = "unstaged";
|
|
26
|
+
}
|
|
27
|
+
else if (trimmed.startsWith("Untracked files")) {
|
|
28
|
+
section = "untracked";
|
|
29
|
+
}
|
|
30
|
+
else if (/^\s*(modified|new file|deleted|renamed|copied):/.test(line)) {
|
|
31
|
+
const file = trimmed.replace(/^(modified|new file|deleted|renamed|copied):\s*/, "");
|
|
32
|
+
if (section === "staged")
|
|
33
|
+
staged.push(file);
|
|
34
|
+
else
|
|
35
|
+
unstaged.push(file);
|
|
36
|
+
}
|
|
37
|
+
else if (section === "untracked" && trimmed && !trimmed.startsWith("(") && !trimmed.startsWith("no changes")) {
|
|
38
|
+
untracked.push(trimmed);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (branch)
|
|
42
|
+
result.push(`branch: ${branch}`);
|
|
43
|
+
if (staged.length > 0)
|
|
44
|
+
result.push(`staged (${staged.length}): ${staged.slice(0, 5).join(", ")}${staged.length > 5 ? ` +${staged.length - 5}` : ""}`);
|
|
45
|
+
if (unstaged.length > 0)
|
|
46
|
+
result.push(`modified (${unstaged.length}): ${unstaged.slice(0, 5).join(", ")}${unstaged.length > 5 ? ` +${unstaged.length - 5}` : ""}`);
|
|
47
|
+
if (untracked.length > 0)
|
|
48
|
+
result.push(`untracked (${untracked.length}): ${untracked.slice(0, 3).join(", ")}${untracked.length > 3 ? ` +${untracked.length - 3}` : ""}`);
|
|
49
|
+
if (result.length === 0)
|
|
50
|
+
result.push("clean");
|
|
51
|
+
return result.length > 0 ? result : lines;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
registerRule({
|
|
55
|
+
name: "git-log",
|
|
56
|
+
tools: ["git"],
|
|
57
|
+
compress(lines) {
|
|
58
|
+
// Detect git log output (lines starting with "commit ")
|
|
59
|
+
const isLog = lines.some((l) => /^commit [0-9a-f]{40}$/.test(l.trim()));
|
|
60
|
+
if (!isLog)
|
|
61
|
+
return lines;
|
|
62
|
+
const result = [];
|
|
63
|
+
let currentHash = "";
|
|
64
|
+
let currentMsg = "";
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
if (/^commit [0-9a-f]{40}$/.test(trimmed)) {
|
|
68
|
+
if (currentHash && currentMsg) {
|
|
69
|
+
result.push(`${currentHash.slice(0, 7)} ${currentMsg}`);
|
|
70
|
+
}
|
|
71
|
+
currentHash = trimmed.replace("commit ", "");
|
|
72
|
+
currentMsg = "";
|
|
73
|
+
}
|
|
74
|
+
else if (trimmed &&
|
|
75
|
+
!trimmed.startsWith("Author:") &&
|
|
76
|
+
!trimmed.startsWith("Date:") &&
|
|
77
|
+
!trimmed.startsWith("Merge:")) {
|
|
78
|
+
if (!currentMsg)
|
|
79
|
+
currentMsg = trimmed;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (currentHash && currentMsg) {
|
|
83
|
+
result.push(`${currentHash.slice(0, 7)} ${currentMsg}`);
|
|
84
|
+
}
|
|
85
|
+
return result.length > 0 ? result : lines;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
registerRule({
|
|
89
|
+
name: "git-diff-stat",
|
|
90
|
+
tools: ["git"],
|
|
91
|
+
compress(lines) {
|
|
92
|
+
// Compact large diffs: keep stat summary, trim hunks
|
|
93
|
+
const statLine = lines.findIndex((l) => /^\s*\d+ files? changed/.test(l));
|
|
94
|
+
if (statLine === -1)
|
|
95
|
+
return lines;
|
|
96
|
+
// Keep everything up to and including the stat summary
|
|
97
|
+
const result = lines.slice(0, statLine + 1);
|
|
98
|
+
// After stat, only keep hunk headers and first 3 lines of each hunk
|
|
99
|
+
let hunkLines = 0;
|
|
100
|
+
for (let i = statLine + 1; i < lines.length; i++) {
|
|
101
|
+
const line = lines[i];
|
|
102
|
+
if (line.startsWith("diff --git") || line.startsWith("@@")) {
|
|
103
|
+
result.push(line);
|
|
104
|
+
hunkLines = 0;
|
|
105
|
+
}
|
|
106
|
+
else if (hunkLines < 3) {
|
|
107
|
+
result.push(line);
|
|
108
|
+
hunkLines++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
},
|
|
113
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { registerRule } from "../engine.js";
|
|
2
|
+
registerRule({
|
|
3
|
+
name: "npm-install",
|
|
4
|
+
tools: ["npm", "pnpm", "yarn"],
|
|
5
|
+
compress(lines) {
|
|
6
|
+
// Compact npm/pnpm install output
|
|
7
|
+
const isInstall = lines.some((l) => /^(added|removed|up to date|Packages:|Progress:)/.test(l.trim()));
|
|
8
|
+
if (!isInstall)
|
|
9
|
+
return lines;
|
|
10
|
+
const result = [];
|
|
11
|
+
const warnings = [];
|
|
12
|
+
let summary = "";
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
// Keep summary lines
|
|
16
|
+
if (/^(added|removed|up to date|Done in)/.test(trimmed)) {
|
|
17
|
+
summary = trimmed;
|
|
18
|
+
}
|
|
19
|
+
// Keep dependency sections
|
|
20
|
+
else if (/^(dependencies|devDependencies|peerDependencies):/.test(trimmed)) {
|
|
21
|
+
result.push(trimmed);
|
|
22
|
+
}
|
|
23
|
+
// Keep actual package additions (+ package@version)
|
|
24
|
+
else if (trimmed.startsWith("+ ")) {
|
|
25
|
+
result.push(trimmed);
|
|
26
|
+
}
|
|
27
|
+
// Collect warnings
|
|
28
|
+
else if (/^(WARN|warn|npm warn)/.test(trimmed)) {
|
|
29
|
+
if (warnings.length < 3)
|
|
30
|
+
warnings.push(trimmed);
|
|
31
|
+
}
|
|
32
|
+
// Skip progress bars, http lines, timing
|
|
33
|
+
}
|
|
34
|
+
if (warnings.length > 0) {
|
|
35
|
+
result.push(`warnings (${warnings.length}): ${warnings[0]}`);
|
|
36
|
+
}
|
|
37
|
+
if (summary)
|
|
38
|
+
result.push(summary);
|
|
39
|
+
return result.length > 0 ? result : lines;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
registerRule({
|
|
43
|
+
name: "npm-test",
|
|
44
|
+
tools: ["npm", "pnpm"],
|
|
45
|
+
compress(lines) {
|
|
46
|
+
// Compact test runner output: keep failures and summary
|
|
47
|
+
const hasSummary = lines.some((l) => /Tests?\s+\d+/.test(l) || /\d+ (passed|failed|skipped)/.test(l));
|
|
48
|
+
if (!hasSummary)
|
|
49
|
+
return lines;
|
|
50
|
+
const result = [];
|
|
51
|
+
const failures = [];
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
const trimmed = line.trim();
|
|
54
|
+
// Keep summary lines
|
|
55
|
+
if (/Tests?\s+\d+/.test(trimmed) || /\d+ (passed|failed|skipped)/.test(trimmed)) {
|
|
56
|
+
result.push(line);
|
|
57
|
+
}
|
|
58
|
+
// Keep test file results
|
|
59
|
+
else if (/^[✓✗×]|PASS|FAIL/.test(trimmed)) {
|
|
60
|
+
if (/FAIL|✗|×/.test(trimmed)) {
|
|
61
|
+
failures.push(line);
|
|
62
|
+
}
|
|
63
|
+
// Skip individual passing tests
|
|
64
|
+
}
|
|
65
|
+
// Keep error details
|
|
66
|
+
else if (/^(Error|AssertionError|Expected|Received|at )/.test(trimmed)) {
|
|
67
|
+
result.push(line);
|
|
68
|
+
}
|
|
69
|
+
// Keep duration
|
|
70
|
+
else if (/Duration|Time/.test(trimmed)) {
|
|
71
|
+
result.push(line);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Show failures first, then summary
|
|
75
|
+
return [...failures, ...result].length > 0 ? [...failures, ...result] : lines;
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
registerRule({
|
|
79
|
+
name: "npm-audit",
|
|
80
|
+
tools: ["npm", "pnpm"],
|
|
81
|
+
compress(lines) {
|
|
82
|
+
const isAudit = lines.some((l) => /vulnerabilit/.test(l));
|
|
83
|
+
if (!isAudit)
|
|
84
|
+
return lines;
|
|
85
|
+
const result = [];
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
const trimmed = line.trim();
|
|
88
|
+
// Keep vulnerability summary
|
|
89
|
+
if (/\d+ vulnerabilit/.test(trimmed) || /found 0/.test(trimmed)) {
|
|
90
|
+
result.push(trimmed);
|
|
91
|
+
}
|
|
92
|
+
// Keep severity breakdown
|
|
93
|
+
else if (/^(critical|high|moderate|low)\s*\|?\s*\d+/.test(trimmed)) {
|
|
94
|
+
result.push(trimmed);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return result.length > 0 ? result : lines;
|
|
98
|
+
},
|
|
99
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { registerRule } from "../engine.js";
|
|
2
|
+
registerRule({
|
|
3
|
+
name: "vitest",
|
|
4
|
+
tools: ["vitest"],
|
|
5
|
+
compress(lines) {
|
|
6
|
+
const isVitest = lines.some((l) => /Test Files|Tests\s+\d+/.test(l));
|
|
7
|
+
if (!isVitest)
|
|
8
|
+
return lines;
|
|
9
|
+
const result = [];
|
|
10
|
+
const failures = [];
|
|
11
|
+
for (const line of lines) {
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
// Keep file-level results
|
|
14
|
+
if (/^[✓✗×].*\.test\./.test(trimmed) || /FAIL/.test(trimmed)) {
|
|
15
|
+
if (/✗|×|FAIL/.test(trimmed))
|
|
16
|
+
failures.push(line);
|
|
17
|
+
}
|
|
18
|
+
// Keep summary
|
|
19
|
+
else if (/Test Files|Tests\s+\d+|Duration|Start at/.test(trimmed)) {
|
|
20
|
+
result.push(line);
|
|
21
|
+
}
|
|
22
|
+
// Keep assertion errors
|
|
23
|
+
else if (/^(AssertionError|Error|Expected|Received|expect\()/.test(trimmed)) {
|
|
24
|
+
result.push(line);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return [...failures, ...result].length > 0 ? [...failures, ...result] : lines;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
registerRule({
|
|
31
|
+
name: "jest",
|
|
32
|
+
tools: ["jest"],
|
|
33
|
+
compress(lines) {
|
|
34
|
+
const isJest = lines.some((l) => /Test Suites:|Tests:/.test(l));
|
|
35
|
+
if (!isJest)
|
|
36
|
+
return lines;
|
|
37
|
+
const result = [];
|
|
38
|
+
const failures = [];
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
const trimmed = line.trim();
|
|
41
|
+
if (/^(FAIL|PASS)/.test(trimmed)) {
|
|
42
|
+
if (trimmed.startsWith("FAIL"))
|
|
43
|
+
failures.push(line);
|
|
44
|
+
}
|
|
45
|
+
else if (/Test Suites:|Tests:|Snapshots:|Time:/.test(trimmed)) {
|
|
46
|
+
result.push(line);
|
|
47
|
+
}
|
|
48
|
+
else if (/●/.test(line)) {
|
|
49
|
+
// Jest failure markers
|
|
50
|
+
failures.push(line);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return [...failures, ...result].length > 0 ? [...failures, ...result] : lines;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
registerRule({
|
|
57
|
+
name: "pytest",
|
|
58
|
+
tools: ["pytest", "python"],
|
|
59
|
+
compress(lines) {
|
|
60
|
+
const isPytest = lines.some((l) => /passed|failed|error/.test(l) && /=+/.test(l));
|
|
61
|
+
if (!isPytest)
|
|
62
|
+
return lines;
|
|
63
|
+
const result = [];
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
const trimmed = line.trim();
|
|
66
|
+
// Keep summary line
|
|
67
|
+
if (/^=+.*=+$/.test(trimmed) && /(passed|failed|error|warning)/.test(trimmed)) {
|
|
68
|
+
result.push(line);
|
|
69
|
+
}
|
|
70
|
+
// Keep FAILED markers
|
|
71
|
+
else if (/^FAILED/.test(trimmed)) {
|
|
72
|
+
result.push(line);
|
|
73
|
+
}
|
|
74
|
+
// Keep short test results section
|
|
75
|
+
else if (/^(ERRORS|FAILURES|SHORT TEST SUMMARY)/.test(trimmed)) {
|
|
76
|
+
result.push(line);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result.length > 0 ? result : lines;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
registerRule({
|
|
83
|
+
name: "go-test",
|
|
84
|
+
tools: ["go"],
|
|
85
|
+
compress(lines) {
|
|
86
|
+
const isGoTest = lines.some((l) => /^(ok|FAIL|---)\s/.test(l.trim()));
|
|
87
|
+
if (!isGoTest)
|
|
88
|
+
return lines;
|
|
89
|
+
const result = [];
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
const trimmed = line.trim();
|
|
92
|
+
// Keep package results
|
|
93
|
+
if (/^(ok|FAIL)\s/.test(trimmed)) {
|
|
94
|
+
result.push(line);
|
|
95
|
+
}
|
|
96
|
+
// Keep individual test failures
|
|
97
|
+
else if (/^--- FAIL/.test(trimmed)) {
|
|
98
|
+
result.push(line);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result.length > 0 ? result : lines;
|
|
102
|
+
},
|
|
103
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { registerRule } from "../engine.js";
|
|
2
|
+
registerRule({
|
|
3
|
+
name: "tsc-errors",
|
|
4
|
+
tools: ["tsc", "typescript"],
|
|
5
|
+
compress(lines) {
|
|
6
|
+
// Compact TypeScript compiler errors: group by file, show first error per file
|
|
7
|
+
const isTs = lines.some((l) => /\.tsx?:\d+:\d+/.test(l) || /error TS\d+/.test(l));
|
|
8
|
+
if (!isTs)
|
|
9
|
+
return lines;
|
|
10
|
+
const byFile = new Map();
|
|
11
|
+
let summary = "";
|
|
12
|
+
for (const line of lines) {
|
|
13
|
+
const match = line.match(/^(.+\.tsx?)[:(\s]+(\d+)/);
|
|
14
|
+
if (match) {
|
|
15
|
+
const file = match[1];
|
|
16
|
+
if (!byFile.has(file))
|
|
17
|
+
byFile.set(file, []);
|
|
18
|
+
byFile.get(file).push(line.trim());
|
|
19
|
+
}
|
|
20
|
+
if (/Found \d+ error/.test(line)) {
|
|
21
|
+
summary = line.trim();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const result = [];
|
|
25
|
+
for (const [file, errors] of byFile) {
|
|
26
|
+
result.push(`${file} (${errors.length} error${errors.length > 1 ? "s" : ""}):`);
|
|
27
|
+
// Show first 2 errors per file
|
|
28
|
+
for (const err of errors.slice(0, 2)) {
|
|
29
|
+
result.push(` ${err}`);
|
|
30
|
+
}
|
|
31
|
+
if (errors.length > 2) {
|
|
32
|
+
result.push(` ...+${errors.length - 2} more`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (summary)
|
|
36
|
+
result.push(summary);
|
|
37
|
+
return result.length > 0 ? result : lines;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface ToolStats {
|
|
2
|
+
calls: number;
|
|
3
|
+
savedTokens: number;
|
|
4
|
+
}
|
|
5
|
+
interface CompressionStats {
|
|
6
|
+
totalInputTokens: number;
|
|
7
|
+
totalOutputTokens: number;
|
|
8
|
+
totalSaved: number;
|
|
9
|
+
byTool: Record<string, ToolStats>;
|
|
10
|
+
}
|
|
11
|
+
export declare function recordCompression(tool: string, inputTokens: number, outputTokens: number): void;
|
|
12
|
+
export declare function getCompressionStats(): CompressionStats & {
|
|
13
|
+
savingsPct: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function resetCompressionStats(): void;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { atomicWriteSync } from "../utils/atomic.js";
|
|
5
|
+
function statsPath() {
|
|
6
|
+
return join(homedir(), ".arcana", "compression-stats.json");
|
|
7
|
+
}
|
|
8
|
+
function readStats() {
|
|
9
|
+
const p = statsPath();
|
|
10
|
+
if (!existsSync(p)) {
|
|
11
|
+
return { totalInputTokens: 0, totalOutputTokens: 0, totalSaved: 0, byTool: {} };
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return { totalInputTokens: 0, totalOutputTokens: 0, totalSaved: 0, byTool: {} };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function writeStats(stats) {
|
|
21
|
+
const dir = join(homedir(), ".arcana");
|
|
22
|
+
if (!existsSync(dir))
|
|
23
|
+
mkdirSync(dir, { recursive: true });
|
|
24
|
+
atomicWriteSync(statsPath(), JSON.stringify(stats, null, 2));
|
|
25
|
+
}
|
|
26
|
+
export function recordCompression(tool, inputTokens, outputTokens) {
|
|
27
|
+
const stats = readStats();
|
|
28
|
+
stats.totalInputTokens += inputTokens;
|
|
29
|
+
stats.totalOutputTokens += outputTokens;
|
|
30
|
+
stats.totalSaved += inputTokens - outputTokens;
|
|
31
|
+
if (!stats.byTool[tool]) {
|
|
32
|
+
stats.byTool[tool] = { calls: 0, savedTokens: 0 };
|
|
33
|
+
}
|
|
34
|
+
stats.byTool[tool].calls++;
|
|
35
|
+
stats.byTool[tool].savedTokens += inputTokens - outputTokens;
|
|
36
|
+
writeStats(stats);
|
|
37
|
+
}
|
|
38
|
+
export function getCompressionStats() {
|
|
39
|
+
const stats = readStats();
|
|
40
|
+
const savingsPct = stats.totalInputTokens > 0 ? Math.round((stats.totalSaved / stats.totalInputTokens) * 100) : 0;
|
|
41
|
+
return { ...stats, savingsPct };
|
|
42
|
+
}
|
|
43
|
+
export function resetCompressionStats() {
|
|
44
|
+
writeStats({ totalInputTokens: 0, totalOutputTokens: 0, totalSaved: 0, byTool: {} });
|
|
45
|
+
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -7,4 +7,16 @@ export declare const PRUNE_KEEP_NEWEST = 3;
|
|
|
7
7
|
export declare const LARGE_SKILL_KB_THRESHOLD = 50;
|
|
8
8
|
export declare const TOKENS_PER_KB = 256;
|
|
9
9
|
export declare const CONTEXT_WINDOW_TOKENS = 200000;
|
|
10
|
+
export declare const INDEX_FILENAME = "_index.md";
|
|
11
|
+
export declare const LOADED_FILENAME = "_loaded.md";
|
|
12
|
+
export declare const ACTIVE_FILENAME = "_active.md";
|
|
13
|
+
export declare const CONTEXT_BUDGET_PCT = 30;
|
|
14
|
+
export declare const MODEL_CONTEXTS: Record<string, number>;
|
|
15
|
+
export declare const SKILL_NAME_REGEX: RegExp;
|
|
16
|
+
export declare const SKILL_MAX_LINES = 300;
|
|
17
|
+
export declare const JACCARD_THRESHOLD = 0.5;
|
|
18
|
+
export declare const CACHE_MAX_AGE_MS: number;
|
|
19
|
+
export declare const MEMORY_MAX_LINES = 200;
|
|
20
|
+
export declare const AGENT_BLOAT_PERCENT = 70;
|
|
21
|
+
export declare const DISK_WARN_BYTES: number;
|
|
10
22
|
export declare const DESCRIPTION_TRUNCATION = 50;
|
package/dist/constants.js
CHANGED
|
@@ -9,5 +9,34 @@ export const PRUNE_KEEP_NEWEST = 3;
|
|
|
9
9
|
export const LARGE_SKILL_KB_THRESHOLD = 50;
|
|
10
10
|
export const TOKENS_PER_KB = 256;
|
|
11
11
|
export const CONTEXT_WINDOW_TOKENS = 200_000;
|
|
12
|
+
// Progressive disclosure
|
|
13
|
+
export const INDEX_FILENAME = "_index.md";
|
|
14
|
+
export const LOADED_FILENAME = "_loaded.md";
|
|
15
|
+
export const ACTIVE_FILENAME = "_active.md";
|
|
16
|
+
// Context curation budget
|
|
17
|
+
export const CONTEXT_BUDGET_PCT = 30; // Max % of model context for skills
|
|
18
|
+
export const MODEL_CONTEXTS = {
|
|
19
|
+
// Claude (March 2026): 200K standard, 1M beta for Opus/Sonnet
|
|
20
|
+
"claude-opus-4.6": 200_000,
|
|
21
|
+
"claude-sonnet-4.6": 200_000,
|
|
22
|
+
"claude-haiku-4.5": 200_000,
|
|
23
|
+
// OpenAI (March 2026)
|
|
24
|
+
"gpt-5.4": 1_000_000,
|
|
25
|
+
// Google Gemini (March 2026)
|
|
26
|
+
"gemini-3.1-pro": 1_000_000,
|
|
27
|
+
"gemini-3.1-flash": 1_000_000,
|
|
28
|
+
"gemini-3.1-thinking": 1_000_000,
|
|
29
|
+
default: 200_000,
|
|
30
|
+
};
|
|
31
|
+
// Validation
|
|
32
|
+
export const SKILL_NAME_REGEX = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
33
|
+
export const SKILL_MAX_LINES = 300;
|
|
34
|
+
export const JACCARD_THRESHOLD = 0.5;
|
|
35
|
+
// Cache
|
|
36
|
+
export const CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
37
|
+
// Diagnostics thresholds
|
|
38
|
+
export const MEMORY_MAX_LINES = 200;
|
|
39
|
+
export const AGENT_BLOAT_PERCENT = 70;
|
|
40
|
+
export const DISK_WARN_BYTES = 1 * 1024 * 1024 * 1024; // 1 GB
|
|
12
41
|
// Display
|
|
13
42
|
export const DESCRIPTION_TRUNCATION = 50;
|
|
@@ -85,6 +85,7 @@ export function buildMenuOptions(installedCount, _availableCount) {
|
|
|
85
85
|
if (!isNew) {
|
|
86
86
|
options.push({ value: "setup", label: "Get Started", hint: "detect project, add more skills" });
|
|
87
87
|
}
|
|
88
|
+
options.push({ value: "curate", label: "Curate context", hint: "auto-select skills for token budget" });
|
|
88
89
|
options.push({ value: "health", label: "Health check" });
|
|
89
90
|
options.push({ value: "optimize", label: "Token budget" });
|
|
90
91
|
options.push({ value: "ref", label: "CLI reference" });
|
package/dist/interactive/menu.js
CHANGED
|
@@ -3,7 +3,7 @@ import chalk from "chalk";
|
|
|
3
3
|
import { renderBanner } from "../utils/help.js";
|
|
4
4
|
import { loadConfig } from "../utils/config.js";
|
|
5
5
|
import { getProviders, clearProviderCache } from "../registry.js";
|
|
6
|
-
import { getCliReference } from "../command-
|
|
6
|
+
import { getCliReference } from "../command-defs.js";
|
|
7
7
|
import { AMBER, countInstalled, buildMenuOptions } from "./helpers.js";
|
|
8
8
|
import { SKILL_CATEGORIES } from "./categories.js";
|
|
9
9
|
import { browseByCategory } from "./browse.js";
|
|
@@ -97,6 +97,11 @@ export async function showInteractiveMenu(version) {
|
|
|
97
97
|
case "optimize":
|
|
98
98
|
await optimizeInteractive();
|
|
99
99
|
break;
|
|
100
|
+
case "curate": {
|
|
101
|
+
const { curateCommand } = await import("../commands/curate.js");
|
|
102
|
+
await curateCommand({});
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
100
105
|
case "ref":
|
|
101
106
|
p.note(getCliReference(), "CLI Reference");
|
|
102
107
|
break;
|
|
@@ -24,13 +24,13 @@ export async function optimizeInteractive() {
|
|
|
24
24
|
const action = await p.select({
|
|
25
25
|
message: "What next?",
|
|
26
26
|
options: [
|
|
27
|
-
{ value: "
|
|
27
|
+
{ value: "doctor", label: "Run doctor report" },
|
|
28
28
|
{ value: "__back", label: "Back" },
|
|
29
29
|
],
|
|
30
30
|
});
|
|
31
31
|
handleCancel(action);
|
|
32
|
-
if (action === "
|
|
33
|
-
const {
|
|
34
|
-
await
|
|
32
|
+
if (action === "doctor") {
|
|
33
|
+
const { doctorCommand } = await import("../commands/doctor.js");
|
|
34
|
+
await doctorCommand({ json: false });
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Install an MCP server into the appropriate tool config. */
|
|
2
|
+
export declare function installMcpServer(name: string, tool: "claude" | "cursor", cwd: string): {
|
|
3
|
+
installed: boolean;
|
|
4
|
+
path: string;
|
|
5
|
+
error?: string;
|
|
6
|
+
};
|
|
7
|
+
/** List MCP servers configured in a tool's config. */
|
|
8
|
+
export declare function listConfiguredServers(tool: "claude" | "cursor", cwd: string): string[];
|
|
9
|
+
/** Remove an MCP server from tool config. */
|
|
10
|
+
export declare function removeMcpServer(name: string, tool: "claude" | "cursor", cwd: string): boolean;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { atomicWriteSync } from "../utils/atomic.js";
|
|
5
|
+
import { getServerDef } from "./registry.js";
|
|
6
|
+
/** Get the path to Claude Code's MCP config file. */
|
|
7
|
+
function getClaudeMcpPath() {
|
|
8
|
+
return join(homedir(), ".claude.json");
|
|
9
|
+
}
|
|
10
|
+
/** Get the path to Cursor's MCP config file. */
|
|
11
|
+
function getCursorMcpPath(cwd) {
|
|
12
|
+
return join(cwd, ".cursor", "mcp.json");
|
|
13
|
+
}
|
|
14
|
+
/** Read existing MCP config from a JSON file. */
|
|
15
|
+
function readMcpConfig(filePath) {
|
|
16
|
+
if (!existsSync(filePath))
|
|
17
|
+
return { mcpServers: {} };
|
|
18
|
+
try {
|
|
19
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
20
|
+
return {
|
|
21
|
+
mcpServers: data.mcpServers ?? {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return { mcpServers: {} };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Write MCP server config to a tool's config file. */
|
|
29
|
+
function writeMcpServer(filePath, name, def) {
|
|
30
|
+
// Read existing config, preserving all other keys
|
|
31
|
+
let fullConfig = {};
|
|
32
|
+
if (existsSync(filePath)) {
|
|
33
|
+
try {
|
|
34
|
+
fullConfig = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
fullConfig = {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!fullConfig.mcpServers)
|
|
41
|
+
fullConfig.mcpServers = {};
|
|
42
|
+
const servers = fullConfig.mcpServers;
|
|
43
|
+
const entry = {
|
|
44
|
+
command: def.command,
|
|
45
|
+
args: def.args,
|
|
46
|
+
};
|
|
47
|
+
// Add env placeholders for keys that need configuration
|
|
48
|
+
if (def.envKeys && def.envKeys.length > 0) {
|
|
49
|
+
const env = {};
|
|
50
|
+
for (const key of def.envKeys) {
|
|
51
|
+
env[key] = process.env[key] ?? `<your-${key.toLowerCase().replace(/_/g, "-")}>`;
|
|
52
|
+
}
|
|
53
|
+
entry.env = env;
|
|
54
|
+
}
|
|
55
|
+
servers[name] = entry;
|
|
56
|
+
const dir = join(filePath, "..");
|
|
57
|
+
if (!existsSync(dir))
|
|
58
|
+
mkdirSync(dir, { recursive: true });
|
|
59
|
+
atomicWriteSync(filePath, JSON.stringify(fullConfig, null, 2));
|
|
60
|
+
}
|
|
61
|
+
/** Install an MCP server into the appropriate tool config. */
|
|
62
|
+
export function installMcpServer(name, tool, cwd) {
|
|
63
|
+
const def = getServerDef(name);
|
|
64
|
+
if (!def) {
|
|
65
|
+
return {
|
|
66
|
+
installed: false,
|
|
67
|
+
path: "",
|
|
68
|
+
error: `Unknown MCP server: ${name}. Use 'arcana mcp list' to see available servers.`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const filePath = tool === "claude" ? getClaudeMcpPath() : getCursorMcpPath(cwd);
|
|
72
|
+
// Check if already installed
|
|
73
|
+
const existing = readMcpConfig(filePath);
|
|
74
|
+
if (existing.mcpServers[name]) {
|
|
75
|
+
return { installed: true, path: filePath, error: `${name} already configured in ${filePath}` };
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
writeMcpServer(filePath, name, def);
|
|
79
|
+
return { installed: true, path: filePath };
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
return { installed: false, path: filePath, error: err instanceof Error ? err.message : "Write failed" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** List MCP servers configured in a tool's config. */
|
|
86
|
+
export function listConfiguredServers(tool, cwd) {
|
|
87
|
+
const filePath = tool === "claude" ? getClaudeMcpPath() : getCursorMcpPath(cwd);
|
|
88
|
+
const config = readMcpConfig(filePath);
|
|
89
|
+
return Object.keys(config.mcpServers);
|
|
90
|
+
}
|
|
91
|
+
/** Remove an MCP server from tool config. */
|
|
92
|
+
export function removeMcpServer(name, tool, cwd) {
|
|
93
|
+
const filePath = tool === "claude" ? getClaudeMcpPath() : getCursorMcpPath(cwd);
|
|
94
|
+
if (!existsSync(filePath))
|
|
95
|
+
return false;
|
|
96
|
+
let fullConfig;
|
|
97
|
+
try {
|
|
98
|
+
fullConfig = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const servers = fullConfig.mcpServers;
|
|
104
|
+
if (!servers || !servers[name])
|
|
105
|
+
return false;
|
|
106
|
+
delete servers[name];
|
|
107
|
+
atomicWriteSync(filePath, JSON.stringify(fullConfig, null, 2));
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Built-in registry of recommended MCP servers. */
|
|
2
|
+
export interface McpServerDef {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
envKeys?: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare const MCP_REGISTRY: McpServerDef[];
|
|
10
|
+
export declare function getServerDef(name: string): McpServerDef | undefined;
|
|
11
|
+
export declare function listRegistry(): McpServerDef[];
|