@sporesec/arcana 3.0.2 → 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
package/dist/commands/scan.js
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getInstallDir } from "../utils/fs.js";
|
|
4
|
-
import {
|
|
4
|
+
import { scanSkillContentFull, formatScanResults } from "../utils/scanner.js";
|
|
5
5
|
import { ui, banner } from "../utils/ui.js";
|
|
6
6
|
export async function scanCommand(skill, opts) {
|
|
7
|
+
/* v8 ignore start */
|
|
7
8
|
if (!opts.json) {
|
|
8
9
|
banner();
|
|
9
|
-
console.log(ui.bold(" Security Scan\n")
|
|
10
|
+
console.log(ui.bold(" Security Scan") + (opts.strict ? ui.dim(" (strict mode)") : "") + "\n");
|
|
10
11
|
}
|
|
12
|
+
/* v8 ignore stop */
|
|
11
13
|
const installDir = getInstallDir();
|
|
12
14
|
if (!existsSync(installDir)) {
|
|
13
15
|
if (opts.json) {
|
|
14
16
|
console.log(JSON.stringify({ results: [] }));
|
|
15
17
|
}
|
|
16
18
|
else {
|
|
19
|
+
/* v8 ignore start */
|
|
17
20
|
console.log(ui.dim(" No skills installed."));
|
|
18
21
|
console.log();
|
|
22
|
+
/* v8 ignore stop */
|
|
19
23
|
}
|
|
20
24
|
return;
|
|
21
25
|
}
|
|
@@ -31,15 +35,18 @@ export async function scanCommand(skill, opts) {
|
|
|
31
35
|
console.log(JSON.stringify({ error: "Specify a skill name or use --all" }));
|
|
32
36
|
}
|
|
33
37
|
else {
|
|
38
|
+
/* v8 ignore start */
|
|
34
39
|
console.log(ui.error(" Specify a skill name or use --all"));
|
|
35
40
|
console.log(ui.dim(" Usage: arcana scan <skill>"));
|
|
36
41
|
console.log(ui.dim(" arcana scan --all [--json]"));
|
|
37
42
|
console.log();
|
|
43
|
+
/* v8 ignore stop */
|
|
38
44
|
}
|
|
39
45
|
process.exit(1);
|
|
40
46
|
}
|
|
41
47
|
const results = [];
|
|
42
48
|
let totalIssues = 0;
|
|
49
|
+
let totalSuppressed = 0;
|
|
43
50
|
let criticalCount = 0;
|
|
44
51
|
let highCount = 0;
|
|
45
52
|
let mediumCount = 0;
|
|
@@ -47,13 +54,13 @@ export async function scanCommand(skill, opts) {
|
|
|
47
54
|
for (const name of skills) {
|
|
48
55
|
const skillMd = join(installDir, name, "SKILL.md");
|
|
49
56
|
if (!existsSync(skillMd)) {
|
|
50
|
-
results.push({ skill: name, issues: [], error: "Missing SKILL.md" });
|
|
57
|
+
results.push({ skill: name, issues: [], suppressed: [], error: "Missing SKILL.md" });
|
|
51
58
|
continue;
|
|
52
59
|
}
|
|
53
60
|
try {
|
|
54
61
|
const content = readFileSync(skillMd, "utf-8");
|
|
55
|
-
const issues =
|
|
56
|
-
results.push({ skill: name, issues });
|
|
62
|
+
const { issues, suppressed } = scanSkillContentFull(content, { strict: opts.strict });
|
|
63
|
+
results.push({ skill: name, issues, suppressed });
|
|
57
64
|
if (issues.length === 0) {
|
|
58
65
|
cleanCount++;
|
|
59
66
|
}
|
|
@@ -63,13 +70,19 @@ export async function scanCommand(skill, opts) {
|
|
|
63
70
|
highCount += issues.filter((i) => i.level === "high").length;
|
|
64
71
|
mediumCount += issues.filter((i) => i.level === "medium").length;
|
|
65
72
|
}
|
|
73
|
+
totalSuppressed += suppressed.length;
|
|
66
74
|
}
|
|
67
75
|
catch (err) {
|
|
68
|
-
results.push({
|
|
76
|
+
results.push({
|
|
77
|
+
skill: name,
|
|
78
|
+
issues: [],
|
|
79
|
+
suppressed: [],
|
|
80
|
+
error: err instanceof Error ? err.message : "Read failed",
|
|
81
|
+
});
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
84
|
if (opts.json) {
|
|
72
|
-
|
|
85
|
+
const jsonOutput = {
|
|
73
86
|
summary: {
|
|
74
87
|
total: skills.length,
|
|
75
88
|
clean: cleanCount,
|
|
@@ -77,17 +90,30 @@ export async function scanCommand(skill, opts) {
|
|
|
77
90
|
critical: criticalCount,
|
|
78
91
|
high: highCount,
|
|
79
92
|
medium: mediumCount,
|
|
93
|
+
...(opts.verbose ? { suppressed: totalSuppressed } : {}),
|
|
80
94
|
},
|
|
81
95
|
results: results.map((r) => ({
|
|
82
96
|
skill: r.skill,
|
|
83
97
|
...(r.error ? { error: r.error } : {}),
|
|
84
98
|
issues: r.issues.map((i) => ({ level: i.level, category: i.category, detail: i.detail, line: i.line })),
|
|
99
|
+
...(opts.verbose
|
|
100
|
+
? {
|
|
101
|
+
suppressed: r.suppressed.map((i) => ({
|
|
102
|
+
level: i.level,
|
|
103
|
+
category: i.category,
|
|
104
|
+
detail: i.detail,
|
|
105
|
+
line: i.line,
|
|
106
|
+
})),
|
|
107
|
+
}
|
|
108
|
+
: {}),
|
|
85
109
|
})),
|
|
86
|
-
}
|
|
110
|
+
};
|
|
111
|
+
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
87
112
|
if (criticalCount > 0)
|
|
88
113
|
process.exit(1);
|
|
89
114
|
return;
|
|
90
115
|
}
|
|
116
|
+
/* v8 ignore start */
|
|
91
117
|
// Display results
|
|
92
118
|
for (const r of results) {
|
|
93
119
|
if (r.error) {
|
|
@@ -95,6 +121,12 @@ export async function scanCommand(skill, opts) {
|
|
|
95
121
|
continue;
|
|
96
122
|
}
|
|
97
123
|
console.log(formatScanResults(r.skill, r.issues));
|
|
124
|
+
if (opts.verbose && !opts.strict && r.suppressed.length > 0) {
|
|
125
|
+
for (const s of r.suppressed) {
|
|
126
|
+
const icon = s.level === "critical" ? "CRIT" : s.level === "high" ? "HIGH" : "MED";
|
|
127
|
+
console.log(` ${ui.dim(`[SKIP] [${icon}] ${s.category}: ${s.detail} (line ${s.line})`)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
98
130
|
}
|
|
99
131
|
console.log();
|
|
100
132
|
const parts = [];
|
|
@@ -106,11 +138,17 @@ export async function scanCommand(skill, opts) {
|
|
|
106
138
|
parts.push(ui.warn(`${highCount} high`));
|
|
107
139
|
if (mediumCount > 0)
|
|
108
140
|
parts.push(ui.dim(`${mediumCount} medium`));
|
|
141
|
+
if (totalSuppressed > 0 && !opts.strict)
|
|
142
|
+
parts.push(ui.dim(`${totalSuppressed} suppressed`));
|
|
109
143
|
console.log(` ${parts.join(ui.dim(" | "))}`);
|
|
110
144
|
if (totalIssues === 0) {
|
|
111
145
|
console.log(ui.success(" No security issues detected."));
|
|
112
146
|
}
|
|
147
|
+
if (!opts.strict) {
|
|
148
|
+
console.log(ui.dim(" Note: BAD/DON'T example blocks are skipped. Use --strict to scan everything."));
|
|
149
|
+
}
|
|
113
150
|
console.log();
|
|
114
151
|
if (criticalCount > 0)
|
|
115
152
|
process.exit(1);
|
|
153
|
+
/* v8 ignore stop */
|
|
116
154
|
}
|
package/dist/commands/search.js
CHANGED
|
@@ -3,6 +3,7 @@ import { isSkillInstalled } from "../utils/fs.js";
|
|
|
3
3
|
import { getProviders } from "../registry.js";
|
|
4
4
|
import { detectProjectContext } from "../utils/project-context.js";
|
|
5
5
|
export async function searchCommand(query, opts) {
|
|
6
|
+
/* v8 ignore next */
|
|
6
7
|
if (!opts.json)
|
|
7
8
|
banner();
|
|
8
9
|
const providers = getProviders(opts.provider);
|
|
@@ -10,6 +11,7 @@ export async function searchCommand(query, opts) {
|
|
|
10
11
|
for (const provider of providers)
|
|
11
12
|
provider.clearCache();
|
|
12
13
|
}
|
|
14
|
+
/* v8 ignore next */
|
|
13
15
|
const s = opts.json ? noopSpinner() : spinner(`Searching for "${query}"...`);
|
|
14
16
|
s.start();
|
|
15
17
|
let results = [];
|
|
@@ -29,9 +31,11 @@ export async function searchCommand(query, opts) {
|
|
|
29
31
|
console.log(JSON.stringify({ error: err instanceof Error ? err.message : "Search failed" }));
|
|
30
32
|
process.exit(1);
|
|
31
33
|
}
|
|
34
|
+
/* v8 ignore start */
|
|
32
35
|
s.fail("Search failed due to a network or provider error.");
|
|
33
36
|
printErrorWithHint(err, true);
|
|
34
37
|
process.exit(1);
|
|
38
|
+
/* v8 ignore stop */
|
|
35
39
|
}
|
|
36
40
|
// Filter by tag
|
|
37
41
|
if (opts.tag) {
|
|
@@ -62,6 +66,7 @@ export async function searchCommand(query, opts) {
|
|
|
62
66
|
}, null, 2));
|
|
63
67
|
return;
|
|
64
68
|
}
|
|
69
|
+
/* v8 ignore start */
|
|
65
70
|
if (results.length === 0) {
|
|
66
71
|
console.log(ui.dim(` No skills matching "${query}"${opts.tag ? ` with tag "${opts.tag}"` : ""}`));
|
|
67
72
|
}
|
|
@@ -78,4 +83,5 @@ export async function searchCommand(query, opts) {
|
|
|
78
83
|
table(rows);
|
|
79
84
|
}
|
|
80
85
|
console.log();
|
|
86
|
+
/* v8 ignore stop */
|
|
81
87
|
}
|
|
@@ -10,6 +10,7 @@ export async function uninstallCommand(skillNames, opts = {}) {
|
|
|
10
10
|
if (opts.json) {
|
|
11
11
|
return uninstallJson(skillNames);
|
|
12
12
|
}
|
|
13
|
+
/* v8 ignore start */
|
|
13
14
|
console.log(renderBanner());
|
|
14
15
|
console.log();
|
|
15
16
|
if (skillNames.length === 0) {
|
|
@@ -32,7 +33,9 @@ export async function uninstallCommand(skillNames, opts = {}) {
|
|
|
32
33
|
else {
|
|
33
34
|
await uninstallMultipleInteractive(skillNames, opts.yes);
|
|
34
35
|
}
|
|
36
|
+
/* v8 ignore stop */
|
|
35
37
|
}
|
|
38
|
+
/* v8 ignore start */
|
|
36
39
|
async function uninstallOneInteractive(skillName, skipConfirm) {
|
|
37
40
|
p.intro(chalk.bold("Uninstall skill"));
|
|
38
41
|
const skillDir = getSkillDir(skillName);
|
|
@@ -54,12 +57,29 @@ async function uninstallOneInteractive(skillName, skipConfirm) {
|
|
|
54
57
|
backupSkill(skillName);
|
|
55
58
|
rmSync(skillDir, { recursive: true, force: true });
|
|
56
59
|
const symlinksRemoved = removeSymlinksFor(skillName);
|
|
60
|
+
// Regenerate skill index and active curation
|
|
61
|
+
try {
|
|
62
|
+
const { regenerateIndex } = await import("./index.js");
|
|
63
|
+
regenerateIndex();
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* best-effort */
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const { regenerateActive } = await import("./curate.js");
|
|
70
|
+
regenerateActive();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* best-effort */
|
|
74
|
+
}
|
|
57
75
|
spin.stop(`Removed ${chalk.bold(skillName)}`);
|
|
58
76
|
if (symlinksRemoved > 0) {
|
|
59
77
|
p.log.info(`Removed ${symlinksRemoved} symlink${symlinksRemoved > 1 ? "s" : ""}`);
|
|
60
78
|
}
|
|
61
79
|
p.outro(`Next: ${chalk.cyan("arcana list --installed")}`);
|
|
62
80
|
}
|
|
81
|
+
/* v8 ignore stop */
|
|
82
|
+
/* v8 ignore start */
|
|
63
83
|
async function uninstallMultipleInteractive(skillNames, skipConfirm) {
|
|
64
84
|
p.intro(chalk.bold(`Uninstall ${skillNames.length} skills`));
|
|
65
85
|
const missing = [];
|
|
@@ -94,12 +114,28 @@ async function uninstallMultipleInteractive(skillNames, skipConfirm) {
|
|
|
94
114
|
rmSync(getSkillDir(skillName), { recursive: true, force: true });
|
|
95
115
|
totalSymlinks += removeSymlinksFor(skillName);
|
|
96
116
|
}
|
|
117
|
+
// Regenerate skill index and active curation
|
|
118
|
+
try {
|
|
119
|
+
const { regenerateIndex } = await import("./index.js");
|
|
120
|
+
regenerateIndex();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
/* best-effort */
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const { regenerateActive } = await import("./curate.js");
|
|
127
|
+
regenerateActive();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
/* best-effort */
|
|
131
|
+
}
|
|
97
132
|
spin.stop(`Removed ${toRemove.length} skills`);
|
|
98
133
|
if (totalSymlinks > 0) {
|
|
99
134
|
p.log.info(`Removed ${totalSymlinks} symlink${totalSymlinks > 1 ? "s" : ""}`);
|
|
100
135
|
}
|
|
101
136
|
p.outro(`Next: ${chalk.cyan("arcana list --installed")}`);
|
|
102
137
|
}
|
|
138
|
+
/* v8 ignore stop */
|
|
103
139
|
export function removeSymlinksFor(skillName) {
|
|
104
140
|
let removed = 0;
|
|
105
141
|
const expectedTarget = resolve(getSkillDir(skillName));
|
package/dist/commands/update.js
CHANGED
|
@@ -27,6 +27,7 @@ async function applyUpdate(skillName, remote, provider) {
|
|
|
27
27
|
return files;
|
|
28
28
|
}
|
|
29
29
|
export async function updateCommand(skills, opts) {
|
|
30
|
+
/* v8 ignore next */
|
|
30
31
|
if (!opts.json)
|
|
31
32
|
banner();
|
|
32
33
|
if (skills.length === 0 && !opts.all) {
|
|
@@ -34,10 +35,12 @@ export async function updateCommand(skills, opts) {
|
|
|
34
35
|
console.log(JSON.stringify({ error: "Specify a skill name or use --all" }));
|
|
35
36
|
}
|
|
36
37
|
else {
|
|
38
|
+
/* v8 ignore start */
|
|
37
39
|
console.log(ui.error(" Specify a skill name or use --all"));
|
|
38
40
|
console.log(ui.dim(" Usage: arcana update <skill> [skill2 ...]"));
|
|
39
41
|
console.log(ui.dim(" arcana update --all"));
|
|
40
42
|
console.log();
|
|
43
|
+
/* v8 ignore stop */
|
|
41
44
|
}
|
|
42
45
|
process.exit(1);
|
|
43
46
|
}
|
|
@@ -47,8 +50,10 @@ export async function updateCommand(skills, opts) {
|
|
|
47
50
|
console.log(JSON.stringify({ updated: [], upToDate: [], failed: [] }));
|
|
48
51
|
}
|
|
49
52
|
else {
|
|
53
|
+
/* v8 ignore start */
|
|
50
54
|
console.log(ui.dim(" No skills installed."));
|
|
51
55
|
console.log();
|
|
56
|
+
/* v8 ignore stop */
|
|
52
57
|
}
|
|
53
58
|
return;
|
|
54
59
|
}
|
|
@@ -77,8 +82,10 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
|
|
|
77
82
|
}));
|
|
78
83
|
}
|
|
79
84
|
else {
|
|
85
|
+
/* v8 ignore start */
|
|
80
86
|
console.log(ui.error(` ${err instanceof Error ? err.message : "Invalid skill name"}`));
|
|
81
87
|
console.log();
|
|
88
|
+
/* v8 ignore stop */
|
|
82
89
|
}
|
|
83
90
|
process.exit(1);
|
|
84
91
|
}
|
|
@@ -88,8 +95,10 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
|
|
|
88
95
|
console.log(JSON.stringify({ updated: [], upToDate: [], failed: [skillName], error: "Not installed" }));
|
|
89
96
|
}
|
|
90
97
|
else {
|
|
98
|
+
/* v8 ignore start */
|
|
91
99
|
console.log(ui.error(` Skill "${skillName}" is not installed.`));
|
|
92
100
|
console.log();
|
|
101
|
+
/* v8 ignore stop */
|
|
93
102
|
}
|
|
94
103
|
process.exit(1);
|
|
95
104
|
}
|
|
@@ -103,8 +112,10 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
|
|
|
103
112
|
console.log(JSON.stringify({ updated: [], upToDate: [], failed: [skillName], error: `Not found on ${providerName}` }));
|
|
104
113
|
}
|
|
105
114
|
else {
|
|
115
|
+
/* v8 ignore start */
|
|
106
116
|
s.fail(`Skill "${skillName}" not found on ${providerName}`);
|
|
107
117
|
console.log();
|
|
118
|
+
/* v8 ignore stop */
|
|
108
119
|
}
|
|
109
120
|
process.exit(1);
|
|
110
121
|
}
|
|
@@ -114,8 +125,10 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
|
|
|
114
125
|
console.log(JSON.stringify({ updated: [], upToDate: [skillName], failed: [] }));
|
|
115
126
|
}
|
|
116
127
|
else {
|
|
128
|
+
/* v8 ignore start */
|
|
117
129
|
s.info(`${ui.bold(skillName)} is already up to date (v${remote.version})`);
|
|
118
130
|
console.log();
|
|
131
|
+
/* v8 ignore stop */
|
|
119
132
|
}
|
|
120
133
|
return;
|
|
121
134
|
}
|
|
@@ -127,8 +140,10 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
|
|
|
127
140
|
}));
|
|
128
141
|
}
|
|
129
142
|
else {
|
|
143
|
+
/* v8 ignore start */
|
|
130
144
|
s.info(`${ui.bold(skillName)} would be updated: v${meta?.version ?? "unknown"} -> v${remote.version}`);
|
|
131
145
|
console.log();
|
|
146
|
+
/* v8 ignore stop */
|
|
132
147
|
}
|
|
133
148
|
return;
|
|
134
149
|
}
|
|
@@ -138,8 +153,10 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
|
|
|
138
153
|
console.log(JSON.stringify({ updated: [skillName], upToDate: [], failed: [] }));
|
|
139
154
|
}
|
|
140
155
|
else {
|
|
156
|
+
/* v8 ignore start */
|
|
141
157
|
s.succeed(`Updated ${ui.bold(skillName)} to v${remote.version} (${files.length} files)`);
|
|
142
158
|
console.log();
|
|
159
|
+
/* v8 ignore stop */
|
|
143
160
|
}
|
|
144
161
|
}
|
|
145
162
|
catch (err) {
|
|
@@ -152,10 +169,12 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
|
|
|
152
169
|
}));
|
|
153
170
|
}
|
|
154
171
|
else {
|
|
172
|
+
/* v8 ignore start */
|
|
155
173
|
s.fail(`Failed to update ${skillName}`);
|
|
156
174
|
if (err instanceof Error)
|
|
157
175
|
console.error(ui.dim(` ${err.message}`));
|
|
158
176
|
console.log();
|
|
177
|
+
/* v8 ignore stop */
|
|
159
178
|
}
|
|
160
179
|
process.exit(1);
|
|
161
180
|
}
|
|
@@ -181,8 +200,10 @@ async function updateBatch(skillNames, installDir, providerName, json, dryRun) {
|
|
|
181
200
|
}));
|
|
182
201
|
}
|
|
183
202
|
else {
|
|
203
|
+
/* v8 ignore start */
|
|
184
204
|
console.log(ui.error(` ${err instanceof Error ? err.message : "Invalid skill name"}`));
|
|
185
205
|
console.log();
|
|
206
|
+
/* v8 ignore stop */
|
|
186
207
|
}
|
|
187
208
|
process.exit(1);
|
|
188
209
|
}
|
|
@@ -194,8 +215,10 @@ async function updateBatch(skillNames, installDir, providerName, json, dryRun) {
|
|
|
194
215
|
console.log(JSON.stringify({ updated: [], upToDate: [], failed: [] }));
|
|
195
216
|
}
|
|
196
217
|
else {
|
|
218
|
+
/* v8 ignore start */
|
|
197
219
|
console.log(ui.dim(" No skills installed."));
|
|
198
220
|
console.log();
|
|
221
|
+
/* v8 ignore stop */
|
|
199
222
|
}
|
|
200
223
|
return;
|
|
201
224
|
}
|
|
@@ -308,6 +331,7 @@ async function updateBatch(skillNames, installDir, providerName, json, dryRun) {
|
|
|
308
331
|
}));
|
|
309
332
|
}
|
|
310
333
|
else {
|
|
334
|
+
/* v8 ignore start */
|
|
311
335
|
s.stop();
|
|
312
336
|
if (dryRunUpdates.length === 0) {
|
|
313
337
|
console.log(ui.dim(" All skills are up to date."));
|
|
@@ -322,6 +346,7 @@ async function updateBatch(skillNames, installDir, providerName, json, dryRun) {
|
|
|
322
346
|
}
|
|
323
347
|
}
|
|
324
348
|
console.log();
|
|
349
|
+
/* v8 ignore stop */
|
|
325
350
|
}
|
|
326
351
|
return;
|
|
327
352
|
}
|
|
@@ -334,6 +359,7 @@ async function updateBatch(skillNames, installDir, providerName, json, dryRun) {
|
|
|
334
359
|
}));
|
|
335
360
|
}
|
|
336
361
|
else {
|
|
362
|
+
/* v8 ignore start */
|
|
337
363
|
s.succeed(`Update complete`);
|
|
338
364
|
const parts = [`${updatedList.length} updated`, `${upToDateList.length} up to date`];
|
|
339
365
|
if (skippedList.length > 0)
|
|
@@ -342,6 +368,7 @@ async function updateBatch(skillNames, installDir, providerName, json, dryRun) {
|
|
|
342
368
|
parts.push(`${failedList.length} failed`);
|
|
343
369
|
console.log(ui.dim(` ${parts.join(", ")}`));
|
|
344
370
|
console.log();
|
|
371
|
+
/* v8 ignore stop */
|
|
345
372
|
}
|
|
346
373
|
if (failedList.length > 0)
|
|
347
374
|
process.exit(1);
|
|
@@ -6,6 +6,7 @@ import { atomicWriteSync } from "../utils/atomic.js";
|
|
|
6
6
|
import { ui, banner } from "../utils/ui.js";
|
|
7
7
|
import { scanSkillContent } from "../utils/scanner.js";
|
|
8
8
|
export async function validateCommand(skill, opts) {
|
|
9
|
+
/* v8 ignore next */
|
|
9
10
|
if (!opts.json)
|
|
10
11
|
banner();
|
|
11
12
|
const baseDir = opts.source ? resolve(opts.source) : getInstallDir();
|
|
@@ -14,8 +15,10 @@ export async function validateCommand(skill, opts) {
|
|
|
14
15
|
console.log(JSON.stringify({ results: [] }));
|
|
15
16
|
}
|
|
16
17
|
else {
|
|
18
|
+
/* v8 ignore start */
|
|
17
19
|
console.log(ui.dim(" No skills installed."));
|
|
18
20
|
console.log();
|
|
21
|
+
/* v8 ignore stop */
|
|
19
22
|
}
|
|
20
23
|
return;
|
|
21
24
|
}
|
|
@@ -38,10 +41,12 @@ export async function validateCommand(skill, opts) {
|
|
|
38
41
|
console.log(JSON.stringify({ error: "Specify a skill name or use --all" }));
|
|
39
42
|
}
|
|
40
43
|
else {
|
|
44
|
+
/* v8 ignore start */
|
|
41
45
|
console.log(ui.error(" Specify a skill name or use --all"));
|
|
42
46
|
console.log(ui.dim(" Usage: arcana validate <skill>"));
|
|
43
47
|
console.log(ui.dim(" arcana validate --all [--fix]"));
|
|
44
48
|
console.log();
|
|
49
|
+
/* v8 ignore stop */
|
|
45
50
|
}
|
|
46
51
|
process.exit(1);
|
|
47
52
|
}
|
|
@@ -121,6 +126,7 @@ export async function validateCommand(skill, opts) {
|
|
|
121
126
|
crossIssues = crossValidate(baseDir, marketplacePath);
|
|
122
127
|
}
|
|
123
128
|
else if (!opts.json) {
|
|
129
|
+
/* v8 ignore next */
|
|
124
130
|
console.log(ui.warn(" Could not find marketplace.json for cross-validation"));
|
|
125
131
|
}
|
|
126
132
|
}
|
|
@@ -156,6 +162,7 @@ export async function validateCommand(skill, opts) {
|
|
|
156
162
|
process.exit(1);
|
|
157
163
|
return;
|
|
158
164
|
}
|
|
165
|
+
/* v8 ignore start */
|
|
159
166
|
// Human-readable output
|
|
160
167
|
let passed = 0;
|
|
161
168
|
let warned = 0;
|
|
@@ -212,4 +219,5 @@ export async function validateCommand(skill, opts) {
|
|
|
212
219
|
console.log();
|
|
213
220
|
if (failed > 0 || hasCrossErrors)
|
|
214
221
|
process.exit(1);
|
|
222
|
+
/* v8 ignore stop */
|
|
215
223
|
}
|
package/dist/commands/verify.js
CHANGED
|
@@ -10,6 +10,7 @@ export async function verifyCommand(skillNames, opts) {
|
|
|
10
10
|
if (opts.json) {
|
|
11
11
|
return verifyJson(skillNames, opts);
|
|
12
12
|
}
|
|
13
|
+
/* v8 ignore start */
|
|
13
14
|
console.log(renderBanner());
|
|
14
15
|
console.log();
|
|
15
16
|
const installDir = getInstallDir();
|
|
@@ -77,6 +78,7 @@ export async function verifyCommand(skillNames, opts) {
|
|
|
77
78
|
if (modifiedCount > 0) {
|
|
78
79
|
process.exit(1);
|
|
79
80
|
}
|
|
81
|
+
/* v8 ignore stop */
|
|
80
82
|
}
|
|
81
83
|
async function verifyJson(skillNames, opts) {
|
|
82
84
|
const installDir = getInstallDir();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output compression engine (RTK concept).
|
|
3
|
+
* 4-stage pipeline: filter -> group -> truncate -> dedup
|
|
4
|
+
*/
|
|
5
|
+
export interface CompressRule {
|
|
6
|
+
name: string;
|
|
7
|
+
/** Tool names this rule applies to (e.g. "git", "npm") */
|
|
8
|
+
tools: string[];
|
|
9
|
+
/** Apply compression to lines of output */
|
|
10
|
+
compress(lines: string[]): string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function registerRule(rule: CompressRule): void;
|
|
13
|
+
/** Run the full 4-stage compression pipeline. */
|
|
14
|
+
export declare function compress(input: string, tool?: string, maxLines?: number): string;
|
|
15
|
+
/** Calculate compression stats. */
|
|
16
|
+
export declare function compressionStats(original: string, compressed: string): {
|
|
17
|
+
originalTokens: number;
|
|
18
|
+
compressedTokens: number;
|
|
19
|
+
savedTokens: number;
|
|
20
|
+
savedPct: number;
|
|
21
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output compression engine (RTK concept).
|
|
3
|
+
* 4-stage pipeline: filter -> group -> truncate -> dedup
|
|
4
|
+
*/
|
|
5
|
+
const rules = [];
|
|
6
|
+
export function registerRule(rule) {
|
|
7
|
+
rules.push(rule);
|
|
8
|
+
}
|
|
9
|
+
/** Strip ANSI escape codes */
|
|
10
|
+
function stripAnsi(str) {
|
|
11
|
+
// eslint-disable-next-line no-control-regex
|
|
12
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
13
|
+
}
|
|
14
|
+
/** Stage 1: Filter - remove noise lines */
|
|
15
|
+
function filterLines(lines) {
|
|
16
|
+
return lines.filter((line) => {
|
|
17
|
+
const clean = stripAnsi(line).trim();
|
|
18
|
+
// Remove empty lines in sequences of 2+
|
|
19
|
+
if (clean === "")
|
|
20
|
+
return true; // keep single blanks, dedup handles runs
|
|
21
|
+
// Remove progress bars and spinners
|
|
22
|
+
if (/^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏|\\/-]/.test(clean))
|
|
23
|
+
return false;
|
|
24
|
+
// Remove timing-only lines
|
|
25
|
+
if (/^\s*\d+(\.\d+)?\s*(ms|s|sec|seconds)\s*$/.test(clean))
|
|
26
|
+
return false;
|
|
27
|
+
return true;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Stage 2: Group - collapse similar consecutive lines */
|
|
31
|
+
function groupLines(lines) {
|
|
32
|
+
const result = [];
|
|
33
|
+
let lastPattern = "";
|
|
34
|
+
let count = 0;
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
// Normalize for grouping: strip numbers, hashes, timestamps
|
|
37
|
+
const pattern = stripAnsi(line)
|
|
38
|
+
.replace(/\b[0-9a-f]{7,40}\b/g, "<hash>")
|
|
39
|
+
.replace(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/g, "<time>")
|
|
40
|
+
.replace(/\d+/g, "<n>")
|
|
41
|
+
.trim();
|
|
42
|
+
if (pattern === lastPattern && count < 100) {
|
|
43
|
+
count++;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
if (count > 1) {
|
|
47
|
+
result.push(` ... (${count}x similar)`);
|
|
48
|
+
}
|
|
49
|
+
result.push(line);
|
|
50
|
+
lastPattern = pattern;
|
|
51
|
+
count = 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (count > 1) {
|
|
55
|
+
result.push(` ... (${count}x similar)`);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/** Stage 3: Truncate - cap output length */
|
|
60
|
+
function truncateLines(lines, maxLines) {
|
|
61
|
+
if (lines.length <= maxLines)
|
|
62
|
+
return lines;
|
|
63
|
+
const head = lines.slice(0, Math.floor(maxLines * 0.6));
|
|
64
|
+
const tail = lines.slice(-Math.floor(maxLines * 0.3));
|
|
65
|
+
const omitted = lines.length - head.length - tail.length;
|
|
66
|
+
return [...head, `\n... ${omitted} lines omitted ...\n`, ...tail];
|
|
67
|
+
}
|
|
68
|
+
/** Stage 4: Dedup - collapse consecutive blank lines */
|
|
69
|
+
function dedupBlanks(lines) {
|
|
70
|
+
const result = [];
|
|
71
|
+
let lastBlank = false;
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
const isBlank = stripAnsi(line).trim() === "";
|
|
74
|
+
if (isBlank && lastBlank)
|
|
75
|
+
continue;
|
|
76
|
+
result.push(line);
|
|
77
|
+
lastBlank = isBlank;
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
/** Run the full 4-stage compression pipeline. */
|
|
82
|
+
export function compress(input, tool, maxLines = 80) {
|
|
83
|
+
let lines = input.split("\n");
|
|
84
|
+
// Apply tool-specific rules first
|
|
85
|
+
if (tool) {
|
|
86
|
+
const matching = rules.filter((r) => r.tools.includes(tool));
|
|
87
|
+
for (const rule of matching) {
|
|
88
|
+
lines = rule.compress(lines);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Generic pipeline
|
|
92
|
+
lines = filterLines(lines);
|
|
93
|
+
lines = groupLines(lines);
|
|
94
|
+
lines = truncateLines(lines, maxLines);
|
|
95
|
+
lines = dedupBlanks(lines);
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
/** Calculate compression stats. */
|
|
99
|
+
export function compressionStats(original, compressed) {
|
|
100
|
+
// ~4 chars per token approximation
|
|
101
|
+
const originalTokens = Math.round(original.length / 4);
|
|
102
|
+
const compressedTokens = Math.round(compressed.length / 4);
|
|
103
|
+
const savedTokens = originalTokens - compressedTokens;
|
|
104
|
+
const savedPct = originalTokens > 0 ? Math.round((savedTokens / originalTokens) * 100) : 0;
|
|
105
|
+
return { originalTokens, compressedTokens, savedTokens, savedPct };
|
|
106
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { compress, compressionStats, registerRule } from "./engine.js";
|
|
2
|
+
export { recordCompression, getCompressionStats, resetCompressionStats } from "./tracker.js";
|
|
3
|
+
import "./rules/git.js";
|
|
4
|
+
import "./rules/npm.js";
|
|
5
|
+
import "./rules/tsc.js";
|
|
6
|
+
import "./rules/test-runner.js";
|
|
7
|
+
import "./rules/generic.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Re-export compression engine and load all rules
|
|
2
|
+
export { compress, compressionStats, registerRule } from "./engine.js";
|
|
3
|
+
export { recordCompression, getCompressionStats, resetCompressionStats } from "./tracker.js";
|
|
4
|
+
// Load built-in rules (side-effect imports).
|
|
5
|
+
// Static imports are required for esbuild compatibility.
|
|
6
|
+
import "./rules/git.js";
|
|
7
|
+
import "./rules/npm.js";
|
|
8
|
+
import "./rules/tsc.js";
|
|
9
|
+
import "./rules/test-runner.js";
|
|
10
|
+
import "./rules/generic.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|