@sporesec/arcana 2.4.0 → 3.0.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.d.ts +0 -1
- package/dist/cli.js +124 -9
- package/dist/command-registry.d.ts +10 -0
- package/dist/command-registry.js +65 -0
- package/dist/commands/audit.d.ts +2 -3
- package/dist/commands/audit.js +47 -14
- package/dist/commands/benchmark.d.ts +4 -0
- package/dist/commands/benchmark.js +178 -0
- package/dist/commands/clean.d.ts +0 -1
- package/dist/commands/clean.js +19 -8
- package/dist/commands/compact.d.ts +2 -1
- package/dist/commands/compact.js +74 -14
- package/dist/commands/completions.d.ts +3 -0
- package/dist/commands/completions.js +104 -0
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +15 -6
- package/dist/commands/create.d.ts +0 -1
- package/dist/commands/create.js +1 -1
- package/dist/commands/diff.d.ts +4 -0
- package/dist/commands/diff.js +166 -0
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/doctor.js +64 -23
- package/dist/commands/export-cmd.d.ts +4 -0
- package/dist/commands/export-cmd.js +66 -0
- package/dist/commands/import-cmd.d.ts +4 -0
- package/dist/commands/import-cmd.js +131 -0
- package/dist/commands/info.d.ts +0 -1
- package/dist/commands/info.js +29 -4
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.js +26 -33
- package/dist/commands/install.d.ts +1 -1
- package/dist/commands/install.js +118 -205
- package/dist/commands/list.d.ts +0 -1
- package/dist/commands/list.js +12 -4
- package/dist/commands/lock.d.ts +4 -0
- package/dist/commands/lock.js +171 -0
- package/dist/commands/optimize.d.ts +0 -1
- package/dist/commands/optimize.js +111 -20
- package/dist/commands/outdated.d.ts +4 -0
- package/dist/commands/outdated.js +159 -0
- package/dist/commands/profile.d.ts +3 -0
- package/dist/commands/profile.js +274 -0
- package/dist/commands/providers.d.ts +0 -1
- package/dist/commands/providers.js +1 -4
- package/dist/commands/recommend.d.ts +5 -0
- package/dist/commands/recommend.js +96 -0
- package/dist/commands/scan.d.ts +0 -1
- package/dist/commands/scan.js +13 -7
- package/dist/commands/search.d.ts +2 -1
- package/dist/commands/search.js +32 -9
- package/dist/commands/stats.d.ts +0 -1
- package/dist/commands/stats.js +24 -20
- package/dist/commands/team.d.ts +3 -0
- package/dist/commands/team.js +291 -0
- package/dist/commands/uninstall.d.ts +0 -1
- package/dist/commands/uninstall.js +18 -4
- package/dist/commands/update.d.ts +0 -1
- package/dist/commands/update.js +155 -155
- package/dist/commands/validate.d.ts +3 -1
- package/dist/commands/validate.js +90 -15
- package/dist/commands/verify.d.ts +4 -0
- package/dist/commands/verify.js +116 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/interactive/browse.d.ts +4 -0
- package/dist/interactive/browse.js +103 -0
- package/dist/interactive/categories.d.ts +4 -0
- package/dist/interactive/categories.js +87 -0
- package/dist/interactive/health.d.ts +1 -0
- package/dist/interactive/health.js +57 -0
- package/dist/interactive/helpers.d.ts +11 -0
- package/dist/interactive/helpers.js +66 -0
- package/dist/interactive/index.d.ts +1 -0
- package/dist/interactive/index.js +1 -0
- package/dist/interactive/manage.d.ts +2 -0
- package/dist/interactive/manage.js +187 -0
- package/dist/interactive/menu.d.ts +1 -0
- package/dist/interactive/menu.js +107 -0
- package/dist/interactive/search.d.ts +2 -0
- package/dist/interactive/search.js +66 -0
- package/dist/interactive/setup.d.ts +2 -0
- package/dist/interactive/setup.js +48 -0
- package/dist/interactive/skill-detail.d.ts +5 -0
- package/dist/interactive/skill-detail.js +126 -0
- package/dist/interactive.d.ts +0 -1
- package/dist/interactive.js +89 -66
- package/dist/providers/arcana.d.ts +0 -1
- package/dist/providers/arcana.js +0 -1
- package/dist/providers/base.d.ts +0 -1
- package/dist/providers/base.js +0 -1
- package/dist/providers/github.d.ts +0 -1
- package/dist/providers/github.js +8 -3
- package/dist/registry.d.ts +0 -1
- package/dist/registry.js +1 -4
- package/dist/types.d.ts +10 -1
- package/dist/types.js +0 -1
- package/dist/utils/atomic.d.ts +0 -1
- package/dist/utils/atomic.js +3 -2
- package/dist/utils/cache.d.ts +0 -1
- package/dist/utils/cache.js +3 -2
- package/dist/utils/config.d.ts +2 -1
- package/dist/utils/config.js +30 -5
- package/dist/utils/conflict-check.d.ts +8 -0
- package/dist/utils/conflict-check.js +72 -0
- package/dist/utils/errors.d.ts +0 -1
- package/dist/utils/errors.js +0 -1
- package/dist/utils/frontmatter.d.ts +0 -1
- package/dist/utils/frontmatter.js +44 -14
- package/dist/utils/fs.d.ts +0 -1
- package/dist/utils/fs.js +30 -11
- package/dist/utils/help.d.ts +0 -1
- package/dist/utils/help.js +15 -28
- package/dist/utils/history.d.ts +0 -1
- package/dist/utils/history.js +0 -1
- package/dist/utils/http.d.ts +0 -1
- package/dist/utils/http.js +14 -5
- package/dist/utils/install-core.d.ts +48 -0
- package/dist/utils/install-core.js +108 -0
- package/dist/utils/integrity.d.ts +17 -0
- package/dist/utils/integrity.js +84 -0
- package/dist/utils/parallel.d.ts +0 -1
- package/dist/utils/parallel.js +0 -1
- package/dist/utils/project-context.d.ts +19 -0
- package/dist/utils/project-context.js +283 -0
- package/dist/utils/quality.d.ts +27 -0
- package/dist/utils/quality.js +174 -0
- package/dist/utils/scanner.d.ts +0 -1
- package/dist/utils/scanner.js +138 -10
- package/dist/utils/scoring.d.ts +10 -0
- package/dist/utils/scoring.js +84 -0
- package/dist/utils/ui.d.ts +0 -1
- package/dist/utils/ui.js +11 -4
- package/dist/utils/validate.d.ts +0 -1
- package/dist/utils/validate.js +4 -1
- package/package.json +74 -62
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/audit.d.ts.map +0 -1
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/audit.test.d.ts +0 -2
- package/dist/commands/audit.test.d.ts.map +0 -1
- package/dist/commands/audit.test.js +0 -217
- package/dist/commands/audit.test.js.map +0 -1
- package/dist/commands/clean.d.ts.map +0 -1
- package/dist/commands/clean.js.map +0 -1
- package/dist/commands/compact.d.ts.map +0 -1
- package/dist/commands/compact.js.map +0 -1
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/info.d.ts.map +0 -1
- package/dist/commands/info.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/install.d.ts.map +0 -1
- package/dist/commands/install.js.map +0 -1
- package/dist/commands/list.d.ts.map +0 -1
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/optimize.d.ts.map +0 -1
- package/dist/commands/optimize.js.map +0 -1
- package/dist/commands/providers.d.ts.map +0 -1
- package/dist/commands/providers.js.map +0 -1
- package/dist/commands/scan.d.ts.map +0 -1
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/search.d.ts.map +0 -1
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/stats.d.ts.map +0 -1
- package/dist/commands/stats.js.map +0 -1
- package/dist/commands/uninstall.d.ts.map +0 -1
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/validate.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/interactive.d.ts.map +0 -1
- package/dist/interactive.js.map +0 -1
- package/dist/providers/arcana.d.ts.map +0 -1
- package/dist/providers/arcana.js.map +0 -1
- package/dist/providers/base.d.ts.map +0 -1
- package/dist/providers/base.js.map +0 -1
- package/dist/providers/github.d.ts.map +0 -1
- package/dist/providers/github.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/atomic.d.ts.map +0 -1
- package/dist/utils/atomic.js.map +0 -1
- package/dist/utils/atomic.test.d.ts +0 -2
- package/dist/utils/atomic.test.d.ts.map +0 -1
- package/dist/utils/atomic.test.js +0 -31
- package/dist/utils/atomic.test.js.map +0 -1
- package/dist/utils/cache.d.ts.map +0 -1
- package/dist/utils/cache.js.map +0 -1
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/config.test.d.ts +0 -2
- package/dist/utils/config.test.d.ts.map +0 -1
- package/dist/utils/config.test.js +0 -38
- package/dist/utils/config.test.js.map +0 -1
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/frontmatter.d.ts.map +0 -1
- package/dist/utils/frontmatter.js.map +0 -1
- package/dist/utils/frontmatter.test.d.ts +0 -2
- package/dist/utils/frontmatter.test.d.ts.map +0 -1
- package/dist/utils/frontmatter.test.js +0 -152
- package/dist/utils/frontmatter.test.js.map +0 -1
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/fs.test.d.ts +0 -2
- package/dist/utils/fs.test.d.ts.map +0 -1
- package/dist/utils/fs.test.js +0 -145
- package/dist/utils/fs.test.js.map +0 -1
- package/dist/utils/help.d.ts.map +0 -1
- package/dist/utils/help.js.map +0 -1
- package/dist/utils/help.test.d.ts +0 -2
- package/dist/utils/help.test.d.ts.map +0 -1
- package/dist/utils/help.test.js +0 -66
- package/dist/utils/help.test.js.map +0 -1
- package/dist/utils/history.d.ts.map +0 -1
- package/dist/utils/history.js.map +0 -1
- package/dist/utils/http.d.ts.map +0 -1
- package/dist/utils/http.js.map +0 -1
- package/dist/utils/http.test.d.ts +0 -2
- package/dist/utils/http.test.d.ts.map +0 -1
- package/dist/utils/http.test.js +0 -55
- package/dist/utils/http.test.js.map +0 -1
- package/dist/utils/parallel.d.ts.map +0 -1
- package/dist/utils/parallel.js.map +0 -1
- package/dist/utils/scanner.d.ts.map +0 -1
- package/dist/utils/scanner.js.map +0 -1
- package/dist/utils/ui.d.ts.map +0 -1
- package/dist/utils/ui.js.map +0 -1
- package/dist/utils/ui.test.d.ts +0 -2
- package/dist/utils/ui.test.d.ts.map +0 -1
- package/dist/utils/ui.test.js +0 -31
- package/dist/utils/ui.test.js.map +0 -1
- package/dist/utils/validate.d.ts.map +0 -1
- package/dist/utils/validate.js.map +0 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -9,14 +9,24 @@ function checkNodeVersion() {
|
|
|
9
9
|
if (major >= 18) {
|
|
10
10
|
return { name: "Node.js", status: "pass", message: `${process.version}` };
|
|
11
11
|
}
|
|
12
|
-
return {
|
|
12
|
+
return {
|
|
13
|
+
name: "Node.js",
|
|
14
|
+
status: "fail",
|
|
15
|
+
message: `${process.version} (need 18+)`,
|
|
16
|
+
fix: "Install Node.js 18 or later",
|
|
17
|
+
};
|
|
13
18
|
}
|
|
14
19
|
function checkInstallDir() {
|
|
15
20
|
const dir = getInstallDir();
|
|
16
21
|
if (!existsSync(dir)) {
|
|
17
|
-
return {
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
return {
|
|
23
|
+
name: "Skills directory",
|
|
24
|
+
status: "warn",
|
|
25
|
+
message: "~/.agents/skills/ not found",
|
|
26
|
+
fix: "Run: arcana install --all",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const skills = readdirSync(dir).filter((d) => statSync(join(dir, d)).isDirectory());
|
|
20
30
|
return { name: "Skills directory", status: "pass", message: `${skills.length} skills installed` };
|
|
21
31
|
}
|
|
22
32
|
function checkBrokenSymlinks() {
|
|
@@ -24,9 +34,14 @@ function checkBrokenSymlinks() {
|
|
|
24
34
|
if (symlinks.length === 0) {
|
|
25
35
|
return { name: "Symlinks", status: "pass", message: "No symlink directory" };
|
|
26
36
|
}
|
|
27
|
-
const broken = symlinks.filter(s => s.broken).length;
|
|
37
|
+
const broken = symlinks.filter((s) => s.broken).length;
|
|
28
38
|
if (broken > 0) {
|
|
29
|
-
return {
|
|
39
|
+
return {
|
|
40
|
+
name: "Symlinks",
|
|
41
|
+
status: "warn",
|
|
42
|
+
message: `${broken}/${symlinks.length} broken symlinks`,
|
|
43
|
+
fix: "Run: arcana clean",
|
|
44
|
+
};
|
|
30
45
|
}
|
|
31
46
|
return { name: "Symlinks", status: "pass", message: `${symlinks.length} symlinks ok` };
|
|
32
47
|
}
|
|
@@ -35,12 +50,22 @@ function checkGitConfig() {
|
|
|
35
50
|
const name = execSync("git config user.name", { encoding: "utf-8" }).trim();
|
|
36
51
|
const email = execSync("git config user.email", { encoding: "utf-8" }).trim();
|
|
37
52
|
if (!name || !email) {
|
|
38
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
name: "Git config",
|
|
55
|
+
status: "warn",
|
|
56
|
+
message: "Missing user.name or user.email",
|
|
57
|
+
fix: 'git config --global user.name "Your Name"',
|
|
58
|
+
};
|
|
39
59
|
}
|
|
40
60
|
return { name: "Git config", status: "pass", message: `${name} <${email}>` };
|
|
41
61
|
}
|
|
42
62
|
catch {
|
|
43
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
name: "Git config",
|
|
65
|
+
status: "warn",
|
|
66
|
+
message: "Git not found",
|
|
67
|
+
fix: "Install Git from https://git-scm.com",
|
|
68
|
+
};
|
|
44
69
|
}
|
|
45
70
|
}
|
|
46
71
|
function checkArcanaConfig() {
|
|
@@ -73,10 +98,16 @@ function checkDiskUsage() {
|
|
|
73
98
|
}
|
|
74
99
|
}
|
|
75
100
|
}
|
|
76
|
-
catch {
|
|
101
|
+
catch {
|
|
102
|
+
/* skip */
|
|
103
|
+
}
|
|
77
104
|
const mb = (totalSize / (1024 * 1024)).toFixed(1);
|
|
78
105
|
if (totalSize > DISK_USAGE_THRESHOLD_MB * 1024 * 1024) {
|
|
79
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
name: "Disk usage",
|
|
108
|
+
status: "warn",
|
|
109
|
+
message: `${mb} MB across ${dirCount} projects (threshold: ${DISK_USAGE_THRESHOLD_MB} MB). Try: arcana clean`,
|
|
110
|
+
};
|
|
80
111
|
}
|
|
81
112
|
return { name: "Disk usage", status: "pass", message: `${mb} MB across ${dirCount} projects` };
|
|
82
113
|
}
|
|
@@ -118,9 +149,8 @@ function checkSkillValidity() {
|
|
|
118
149
|
let fix;
|
|
119
150
|
if (missingMd.length > 0) {
|
|
120
151
|
details.push(`${missingMd.length} missing SKILL.md (${missingMd.join(", ")})`);
|
|
121
|
-
fix =
|
|
122
|
-
? `Run: arcana uninstall ${missingMd[0]}`
|
|
123
|
-
: `Run: arcana uninstall ${missingMd.join(" ")}`;
|
|
152
|
+
fix =
|
|
153
|
+
missingMd.length === 1 ? `Run: arcana uninstall ${missingMd[0]}` : `Run: arcana uninstall ${missingMd.join(" ")}`;
|
|
124
154
|
}
|
|
125
155
|
else {
|
|
126
156
|
fix = "Run: arcana validate --all --fix";
|
|
@@ -153,14 +183,21 @@ function checkSkillSizes() {
|
|
|
153
183
|
large.push({ name: entry, kb });
|
|
154
184
|
}
|
|
155
185
|
if (large.length === 0) {
|
|
156
|
-
return {
|
|
186
|
+
return {
|
|
187
|
+
name: "Skill sizes",
|
|
188
|
+
status: "pass",
|
|
189
|
+
message: `All skills under 50 KB (total: ${totalKB.toFixed(0)} KB, ~${Math.round((totalKB * 256) / 1000)}K tokens)`,
|
|
190
|
+
};
|
|
157
191
|
}
|
|
158
192
|
large.sort((a, b) => b.kb - a.kb);
|
|
159
|
-
const top3 = large
|
|
193
|
+
const top3 = large
|
|
194
|
+
.slice(0, 3)
|
|
195
|
+
.map((s) => `${s.name} (${s.kb.toFixed(0)} KB)`)
|
|
196
|
+
.join(", ");
|
|
160
197
|
return {
|
|
161
198
|
name: "Skill sizes",
|
|
162
199
|
status: "warn",
|
|
163
|
-
message: `${large.length} skills >50 KB (high token usage). Total: ${totalKB.toFixed(0)} KB (~${Math.round(totalKB * 256 / 1000)}K tokens). Largest: ${top3}`,
|
|
200
|
+
message: `${large.length} skills >50 KB (high token usage). Total: ${totalKB.toFixed(0)} KB (~${Math.round((totalKB * 256) / 1000)}K tokens). Largest: ${top3}`,
|
|
164
201
|
};
|
|
165
202
|
}
|
|
166
203
|
function checkOrphanedProjects() {
|
|
@@ -269,20 +306,25 @@ export async function doctorCommand(opts = {}) {
|
|
|
269
306
|
}
|
|
270
307
|
const checks = runDoctorChecks();
|
|
271
308
|
if (opts.json) {
|
|
272
|
-
console.log(JSON.stringify({
|
|
309
|
+
console.log(JSON.stringify({
|
|
310
|
+
checks: checks.map((c) => ({
|
|
311
|
+
name: c.name,
|
|
312
|
+
status: c.status,
|
|
313
|
+
message: c.message,
|
|
314
|
+
...(c.fix ? { fix: c.fix } : {}),
|
|
315
|
+
})),
|
|
316
|
+
}, null, 2));
|
|
273
317
|
return;
|
|
274
318
|
}
|
|
275
319
|
for (const check of checks) {
|
|
276
|
-
const icon = check.status === "pass" ? ui.success("[OK]")
|
|
277
|
-
: check.status === "warn" ? ui.warn("[!!]")
|
|
278
|
-
: ui.error("[XX]");
|
|
320
|
+
const icon = check.status === "pass" ? ui.success("[OK]") : check.status === "warn" ? ui.warn("[!!]") : ui.error("[XX]");
|
|
279
321
|
console.log(` ${icon} ${ui.bold(check.name)}: ${check.message}`);
|
|
280
322
|
if (check.fix) {
|
|
281
323
|
console.log(ui.dim(` Fix: ${check.fix}`));
|
|
282
324
|
}
|
|
283
325
|
}
|
|
284
|
-
const fails = checks.filter(c => c.status === "fail").length;
|
|
285
|
-
const warns = checks.filter(c => c.status === "warn").length;
|
|
326
|
+
const fails = checks.filter((c) => c.status === "fail").length;
|
|
327
|
+
const warns = checks.filter((c) => c.status === "warn").length;
|
|
286
328
|
console.log();
|
|
287
329
|
if (fails > 0) {
|
|
288
330
|
console.log(ui.error(` ${fails} issue${fails > 1 ? "s" : ""} found`));
|
|
@@ -298,4 +340,3 @@ export async function doctorCommand(opts = {}) {
|
|
|
298
340
|
suggest("arcana list");
|
|
299
341
|
}
|
|
300
342
|
}
|
|
301
|
-
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getInstallDir, readSkillMeta } from "../utils/fs.js";
|
|
4
|
+
import { readLockfile } from "../utils/integrity.js";
|
|
5
|
+
function getInstalledSkillDirs() {
|
|
6
|
+
const installDir = getInstallDir();
|
|
7
|
+
try {
|
|
8
|
+
return readdirSync(installDir).filter((d) => statSync(join(installDir, d)).isDirectory());
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function exportCommand(opts) {
|
|
15
|
+
const dirs = getInstalledSkillDirs();
|
|
16
|
+
if (dirs.length === 0) {
|
|
17
|
+
console.log(JSON.stringify({ skills: [], message: "No skills installed" }));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (opts.sbom) {
|
|
21
|
+
return exportSbom(dirs);
|
|
22
|
+
}
|
|
23
|
+
return exportManifest(dirs);
|
|
24
|
+
}
|
|
25
|
+
function exportManifest(dirs) {
|
|
26
|
+
const skills = [];
|
|
27
|
+
for (const name of dirs) {
|
|
28
|
+
const meta = readSkillMeta(name);
|
|
29
|
+
skills.push({
|
|
30
|
+
name,
|
|
31
|
+
version: meta?.version ?? "unknown",
|
|
32
|
+
source: meta?.source ?? "local",
|
|
33
|
+
description: meta?.description ?? "",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const manifest = {
|
|
37
|
+
exportedAt: new Date().toISOString(),
|
|
38
|
+
skillCount: skills.length,
|
|
39
|
+
skills,
|
|
40
|
+
};
|
|
41
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
42
|
+
}
|
|
43
|
+
function exportSbom(dirs) {
|
|
44
|
+
const lockEntries = readLockfile();
|
|
45
|
+
const lockMap = new Map(lockEntries.map((e) => [e.skill, e]));
|
|
46
|
+
const packages = [];
|
|
47
|
+
for (const name of dirs) {
|
|
48
|
+
const meta = readSkillMeta(name);
|
|
49
|
+
const lock = lockMap.get(name);
|
|
50
|
+
packages.push({
|
|
51
|
+
name,
|
|
52
|
+
version: meta?.version ?? lock?.version ?? "unknown",
|
|
53
|
+
source: meta?.source ?? lock?.source ?? "local",
|
|
54
|
+
hash: lock?.hash ?? "unknown",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const sbom = {
|
|
58
|
+
spdxVersion: "SPDX-2.3",
|
|
59
|
+
dataLicense: "CC0-1.0",
|
|
60
|
+
name: "arcana-skills-sbom",
|
|
61
|
+
documentNamespace: `https://arcana.dev/sbom/${Date.now()}`,
|
|
62
|
+
createdAt: new Date().toISOString(),
|
|
63
|
+
packages,
|
|
64
|
+
};
|
|
65
|
+
console.log(JSON.stringify(sbom, null, 2));
|
|
66
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { getProvider } from "../registry.js";
|
|
3
|
+
import { installSkill, writeSkillMeta } from "../utils/fs.js";
|
|
4
|
+
import { loadConfig } from "../utils/config.js";
|
|
5
|
+
import { updateLockEntry } from "../utils/integrity.js";
|
|
6
|
+
import { validateSlug } from "../utils/validate.js";
|
|
7
|
+
function parseManifest(raw) {
|
|
8
|
+
const parsed = JSON.parse(raw);
|
|
9
|
+
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.skills)) {
|
|
10
|
+
throw new Error("Invalid manifest: expected object with 'skills' array");
|
|
11
|
+
}
|
|
12
|
+
const obj = parsed;
|
|
13
|
+
const skills = obj.skills.map((entry, i) => {
|
|
14
|
+
if (typeof entry !== "object" || entry === null) {
|
|
15
|
+
throw new Error(`Invalid manifest entry at index ${i}`);
|
|
16
|
+
}
|
|
17
|
+
const e = entry;
|
|
18
|
+
if (typeof e.name !== "string" || e.name.length === 0) {
|
|
19
|
+
throw new Error(`Invalid manifest entry at index ${i}: missing name`);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
name: e.name,
|
|
23
|
+
version: typeof e.version === "string" ? e.version : undefined,
|
|
24
|
+
source: typeof e.source === "string" ? e.source : undefined,
|
|
25
|
+
description: typeof e.description === "string" ? e.description : undefined,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
return { skills };
|
|
29
|
+
}
|
|
30
|
+
export async function importCommand(file, opts) {
|
|
31
|
+
if (!existsSync(file)) {
|
|
32
|
+
if (opts.json) {
|
|
33
|
+
console.log(JSON.stringify({ error: `File not found: ${file}` }));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.error(`Error: File not found: ${file}`);
|
|
37
|
+
}
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
let manifest;
|
|
41
|
+
try {
|
|
42
|
+
const raw = readFileSync(file, "utf-8");
|
|
43
|
+
manifest = parseManifest(raw);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const msg = err instanceof Error ? err.message : "Failed to parse manifest";
|
|
47
|
+
if (opts.json) {
|
|
48
|
+
console.log(JSON.stringify({ error: msg }));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.error(`Error: ${msg}`);
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
if (manifest.skills.length === 0) {
|
|
56
|
+
if (opts.json) {
|
|
57
|
+
console.log(JSON.stringify({ installed: [], skipped: [], failed: [], message: "No skills in manifest" }));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log("No skills found in manifest.");
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const config = loadConfig();
|
|
65
|
+
const installed = [];
|
|
66
|
+
const skipped = [];
|
|
67
|
+
const failed = [];
|
|
68
|
+
const errors = {};
|
|
69
|
+
for (const entry of manifest.skills) {
|
|
70
|
+
// Validate skill name
|
|
71
|
+
try {
|
|
72
|
+
validateSlug(entry.name, "skill name");
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const msg = err instanceof Error ? err.message : "Invalid skill name";
|
|
76
|
+
failed.push(entry.name);
|
|
77
|
+
errors[entry.name] = msg;
|
|
78
|
+
if (!opts.json) {
|
|
79
|
+
console.error(`Skipping ${entry.name}: ${msg}`);
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Check if already installed (skip unless --force)
|
|
84
|
+
const { isSkillInstalled } = await import("../utils/fs.js");
|
|
85
|
+
if (isSkillInstalled(entry.name) && !opts.force) {
|
|
86
|
+
skipped.push(entry.name);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Fetch and install
|
|
90
|
+
try {
|
|
91
|
+
const providerName = entry.source ?? config.defaultProvider;
|
|
92
|
+
const provider = getProvider(providerName);
|
|
93
|
+
const files = await provider.fetch(entry.name);
|
|
94
|
+
installSkill(entry.name, files);
|
|
95
|
+
const remote = await provider.info(entry.name);
|
|
96
|
+
const version = remote?.version ?? entry.version ?? "0.0.0";
|
|
97
|
+
writeSkillMeta(entry.name, {
|
|
98
|
+
version,
|
|
99
|
+
installedAt: new Date().toISOString(),
|
|
100
|
+
source: provider.name,
|
|
101
|
+
description: remote?.description ?? entry.description,
|
|
102
|
+
fileCount: files.length,
|
|
103
|
+
sizeBytes: files.reduce((s, f) => s + f.content.length, 0),
|
|
104
|
+
});
|
|
105
|
+
updateLockEntry(entry.name, version, provider.name, files);
|
|
106
|
+
installed.push(entry.name);
|
|
107
|
+
if (!opts.json) {
|
|
108
|
+
console.log(`Installed ${entry.name}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const msg = err instanceof Error ? err.message : "unknown error";
|
|
113
|
+
failed.push(entry.name);
|
|
114
|
+
errors[entry.name] = msg;
|
|
115
|
+
if (!opts.json) {
|
|
116
|
+
console.error(`Failed to install ${entry.name}: ${msg}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (opts.json) {
|
|
121
|
+
const result = { installed, skipped, failed };
|
|
122
|
+
if (Object.keys(errors).length > 0)
|
|
123
|
+
result.errors = errors;
|
|
124
|
+
console.log(JSON.stringify(result));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
console.log(`Import complete: ${installed.length} installed, ${skipped.length} skipped, ${failed.length} failed`);
|
|
128
|
+
}
|
|
129
|
+
if (failed.length > 0)
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
package/dist/commands/info.d.ts
CHANGED
package/dist/commands/info.js
CHANGED
|
@@ -39,7 +39,12 @@ export async function infoCommand(skillName, opts) {
|
|
|
39
39
|
installedVersion: meta?.version,
|
|
40
40
|
source: skill.source,
|
|
41
41
|
repo: skill.repo,
|
|
42
|
-
|
|
42
|
+
tags: skill.tags,
|
|
43
|
+
verified: skill.verified,
|
|
44
|
+
author: skill.author,
|
|
45
|
+
companions: skill.companions,
|
|
46
|
+
conflicts: skill.conflicts,
|
|
47
|
+
},
|
|
43
48
|
}));
|
|
44
49
|
return;
|
|
45
50
|
}
|
|
@@ -47,11 +52,32 @@ export async function infoCommand(skillName, opts) {
|
|
|
47
52
|
if (installed) {
|
|
48
53
|
const meta = readSkillMeta(skillName);
|
|
49
54
|
const localVersion = meta?.version ?? "unknown";
|
|
50
|
-
console.log(" " +
|
|
55
|
+
console.log(" " +
|
|
56
|
+
ui.success("Installed") +
|
|
57
|
+
(localVersion !== skill.version ? ui.warn(` (local: v${localVersion})`) : ""));
|
|
51
58
|
}
|
|
52
59
|
console.log();
|
|
53
60
|
console.log(" " + skill.description);
|
|
54
61
|
console.log();
|
|
62
|
+
if (skill.verified) {
|
|
63
|
+
console.log(" " + ui.success("[Verified]") + " Official skill");
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log(ui.dim(" Community skill"));
|
|
67
|
+
}
|
|
68
|
+
if (skill.author) {
|
|
69
|
+
console.log(ui.dim(` Author: ${skill.author}`));
|
|
70
|
+
}
|
|
71
|
+
if (skill.tags && skill.tags.length > 0) {
|
|
72
|
+
console.log(ui.dim(` Tags: ${skill.tags.join(", ")}`));
|
|
73
|
+
}
|
|
74
|
+
if (skill.companions && skill.companions.length > 0) {
|
|
75
|
+
console.log(ui.dim(` Works with: ${skill.companions.join(", ")}`));
|
|
76
|
+
}
|
|
77
|
+
if (skill.conflicts && skill.conflicts.length > 0) {
|
|
78
|
+
console.log(ui.warn(` Conflicts: ${skill.conflicts.join(", ")}`));
|
|
79
|
+
}
|
|
80
|
+
console.log();
|
|
55
81
|
console.log(ui.dim(` Source: ${skill.source}`));
|
|
56
82
|
if (skill.repo) {
|
|
57
83
|
console.log(ui.dim(` Repo: ${skill.repo}`));
|
|
@@ -77,7 +103,7 @@ export async function infoCommand(skillName, opts) {
|
|
|
77
103
|
installed: true,
|
|
78
104
|
source: meta?.source ?? "local",
|
|
79
105
|
offline: true,
|
|
80
|
-
}
|
|
106
|
+
},
|
|
81
107
|
}));
|
|
82
108
|
return;
|
|
83
109
|
}
|
|
@@ -111,4 +137,3 @@ export async function infoCommand(skillName, opts) {
|
|
|
111
137
|
}
|
|
112
138
|
process.exit(1);
|
|
113
139
|
}
|
|
114
|
-
//# sourceMappingURL=info.js.map
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -1,29 +1,12 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { join,
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { renderBanner } from "../utils/help.js";
|
|
6
|
+
import { detectProjectContext } from "../utils/project-context.js";
|
|
6
7
|
export function detectProject(cwd) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
return { name, type: "Go", lang: "go" };
|
|
10
|
-
if (existsSync(join(cwd, "Cargo.toml")))
|
|
11
|
-
return { name, type: "Rust", lang: "rust" };
|
|
12
|
-
if (existsSync(join(cwd, "requirements.txt")) || existsSync(join(cwd, "pyproject.toml")))
|
|
13
|
-
return { name, type: "Python", lang: "python" };
|
|
14
|
-
if (existsSync(join(cwd, "package.json"))) {
|
|
15
|
-
try {
|
|
16
|
-
const raw = readFileSync(join(cwd, "package.json"), "utf-8");
|
|
17
|
-
const pkg = JSON.parse(raw);
|
|
18
|
-
if (pkg.dependencies?.next || pkg.devDependencies?.next)
|
|
19
|
-
return { name, type: "Next.js", lang: "typescript" };
|
|
20
|
-
if (pkg.dependencies?.react || pkg.devDependencies?.react)
|
|
21
|
-
return { name, type: "React", lang: "typescript" };
|
|
22
|
-
}
|
|
23
|
-
catch { /* ignore */ }
|
|
24
|
-
return { name, type: "Node.js", lang: "typescript" };
|
|
25
|
-
}
|
|
26
|
-
return { name, type: "Unknown", lang: "general" };
|
|
8
|
+
const ctx = detectProjectContext(cwd);
|
|
9
|
+
return { name: ctx.name, type: ctx.type, lang: ctx.lang };
|
|
27
10
|
}
|
|
28
11
|
function claudeTemplate(proj) {
|
|
29
12
|
return `# CLAUDE.md - ${proj.name}
|
|
@@ -150,14 +133,19 @@ const TOOL_FILES = {
|
|
|
150
133
|
aider: { path: ".aider.conf.yml", template: aiderTemplate, label: "Aider" },
|
|
151
134
|
};
|
|
152
135
|
export const SKILL_SUGGESTIONS = {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
136
|
+
Go: ["golang-pro", "go-linter-configuration", "testing-strategy", "security-review"],
|
|
137
|
+
Rust: ["rust-best-practices", "testing-strategy", "security-review"],
|
|
138
|
+
Python: ["python-best-practices", "testing-strategy", "security-review"],
|
|
156
139
|
"Next.js": ["typescript", "typescript-advanced", "frontend-design", "performance-optimization", "security-review"],
|
|
157
|
-
|
|
140
|
+
React: ["typescript", "frontend-design", "frontend-code-review", "testing-strategy"],
|
|
158
141
|
"Node.js": ["typescript", "npm-package", "testing-strategy", "security-review"],
|
|
159
142
|
};
|
|
160
|
-
export const SKILL_SUGGESTIONS_DEFAULT = [
|
|
143
|
+
export const SKILL_SUGGESTIONS_DEFAULT = [
|
|
144
|
+
"code-reviewer",
|
|
145
|
+
"security-review",
|
|
146
|
+
"codebase-dissection",
|
|
147
|
+
"testing-strategy",
|
|
148
|
+
];
|
|
161
149
|
export async function initCommand(opts) {
|
|
162
150
|
console.log(renderBanner());
|
|
163
151
|
console.log();
|
|
@@ -216,7 +204,9 @@ export async function initCommand(opts) {
|
|
|
216
204
|
const settings = JSON.parse(readFileSync(globalSettings, "utf-8"));
|
|
217
205
|
hasPreCompactHook = Array.isArray(settings?.hooks?.PreCompact) && settings.hooks.PreCompact.length > 0;
|
|
218
206
|
}
|
|
219
|
-
catch {
|
|
207
|
+
catch {
|
|
208
|
+
/* ignore */
|
|
209
|
+
}
|
|
220
210
|
}
|
|
221
211
|
if (!hasPreCompactHook) {
|
|
222
212
|
const installHook = await p.confirm({
|
|
@@ -234,14 +224,18 @@ export async function initCommand(opts) {
|
|
|
234
224
|
settings = JSON.parse(readFileSync(globalSettings, "utf-8"));
|
|
235
225
|
}
|
|
236
226
|
const hooks = (settings.hooks ?? {});
|
|
237
|
-
hooks.PreCompact = [
|
|
227
|
+
hooks.PreCompact = [
|
|
228
|
+
{
|
|
238
229
|
matcher: "",
|
|
239
|
-
hooks: [
|
|
230
|
+
hooks: [
|
|
231
|
+
{
|
|
240
232
|
type: "command",
|
|
241
|
-
command:
|
|
233
|
+
command: 'bash -c \'PROJ_DIR="$HOME/.claude/projects"; for d in "$PROJ_DIR"/*/memory; do if [ -d "$d" ]; then echo "## Handover $(date +%Y-%m-%d_%H%M)" >> "$d/HANDOVER.md"; echo "Auto-compaction triggered. Review MEMORY.md for preserved context." >> "$d/HANDOVER.md"; echo "" >> "$d/HANDOVER.md"; fi; done\'',
|
|
242
234
|
timeout: 10,
|
|
243
|
-
}
|
|
244
|
-
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
];
|
|
245
239
|
settings.hooks = hooks;
|
|
246
240
|
writeFileSync(globalSettings, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
247
241
|
p.log.success("Installed PreCompact hook in ~/.claude/settings.json");
|
|
@@ -259,4 +253,3 @@ export async function initCommand(opts) {
|
|
|
259
253
|
p.note(skillList, "Recommended skills");
|
|
260
254
|
p.outro(`Next: ${chalk.cyan("arcana install <skill>")} or ${chalk.cyan("arcana install --all")}`);
|
|
261
255
|
}
|
|
262
|
-
//# sourceMappingURL=init.js.map
|