@sporesec/arcana 2.4.0 → 3.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.
Files changed (245) hide show
  1. package/dist/cli.d.ts +0 -1
  2. package/dist/cli.js +120 -9
  3. package/dist/command-registry.d.ts +10 -0
  4. package/dist/command-registry.js +65 -0
  5. package/dist/commands/audit.d.ts +0 -1
  6. package/dist/commands/audit.js +16 -6
  7. package/dist/commands/benchmark.d.ts +4 -0
  8. package/dist/commands/benchmark.js +178 -0
  9. package/dist/commands/clean.d.ts +0 -1
  10. package/dist/commands/clean.js +19 -8
  11. package/dist/commands/compact.d.ts +2 -1
  12. package/dist/commands/compact.js +74 -14
  13. package/dist/commands/completions.d.ts +3 -0
  14. package/dist/commands/completions.js +104 -0
  15. package/dist/commands/config.d.ts +0 -1
  16. package/dist/commands/config.js +15 -6
  17. package/dist/commands/create.d.ts +0 -1
  18. package/dist/commands/create.js +1 -1
  19. package/dist/commands/diff.d.ts +4 -0
  20. package/dist/commands/diff.js +166 -0
  21. package/dist/commands/doctor.d.ts +0 -1
  22. package/dist/commands/doctor.js +64 -23
  23. package/dist/commands/export-cmd.d.ts +4 -0
  24. package/dist/commands/export-cmd.js +66 -0
  25. package/dist/commands/import-cmd.d.ts +4 -0
  26. package/dist/commands/import-cmd.js +131 -0
  27. package/dist/commands/info.d.ts +0 -1
  28. package/dist/commands/info.js +29 -4
  29. package/dist/commands/init.d.ts +0 -1
  30. package/dist/commands/init.js +26 -33
  31. package/dist/commands/install.d.ts +1 -1
  32. package/dist/commands/install.js +118 -205
  33. package/dist/commands/list.d.ts +0 -1
  34. package/dist/commands/list.js +12 -4
  35. package/dist/commands/lock.d.ts +4 -0
  36. package/dist/commands/lock.js +171 -0
  37. package/dist/commands/optimize.d.ts +0 -1
  38. package/dist/commands/optimize.js +111 -20
  39. package/dist/commands/outdated.d.ts +4 -0
  40. package/dist/commands/outdated.js +159 -0
  41. package/dist/commands/profile.d.ts +3 -0
  42. package/dist/commands/profile.js +274 -0
  43. package/dist/commands/providers.d.ts +0 -1
  44. package/dist/commands/providers.js +1 -4
  45. package/dist/commands/recommend.d.ts +5 -0
  46. package/dist/commands/recommend.js +96 -0
  47. package/dist/commands/scan.d.ts +0 -1
  48. package/dist/commands/scan.js +13 -7
  49. package/dist/commands/search.d.ts +2 -1
  50. package/dist/commands/search.js +32 -9
  51. package/dist/commands/stats.d.ts +0 -1
  52. package/dist/commands/stats.js +24 -20
  53. package/dist/commands/team.d.ts +3 -0
  54. package/dist/commands/team.js +291 -0
  55. package/dist/commands/uninstall.d.ts +0 -1
  56. package/dist/commands/uninstall.js +18 -4
  57. package/dist/commands/update.d.ts +0 -1
  58. package/dist/commands/update.js +155 -155
  59. package/dist/commands/validate.d.ts +0 -1
  60. package/dist/commands/validate.js +14 -6
  61. package/dist/commands/verify.d.ts +4 -0
  62. package/dist/commands/verify.js +116 -0
  63. package/dist/constants.d.ts +10 -0
  64. package/dist/constants.js +13 -0
  65. package/dist/index.d.ts +0 -1
  66. package/dist/index.js +0 -1
  67. package/dist/interactive/browse.d.ts +4 -0
  68. package/dist/interactive/browse.js +103 -0
  69. package/dist/interactive/categories.d.ts +4 -0
  70. package/dist/interactive/categories.js +87 -0
  71. package/dist/interactive/health.d.ts +1 -0
  72. package/dist/interactive/health.js +57 -0
  73. package/dist/interactive/helpers.d.ts +11 -0
  74. package/dist/interactive/helpers.js +66 -0
  75. package/dist/interactive/index.d.ts +1 -0
  76. package/dist/interactive/index.js +1 -0
  77. package/dist/interactive/manage.d.ts +2 -0
  78. package/dist/interactive/manage.js +187 -0
  79. package/dist/interactive/menu.d.ts +1 -0
  80. package/dist/interactive/menu.js +107 -0
  81. package/dist/interactive/search.d.ts +2 -0
  82. package/dist/interactive/search.js +66 -0
  83. package/dist/interactive/setup.d.ts +2 -0
  84. package/dist/interactive/setup.js +48 -0
  85. package/dist/interactive/skill-detail.d.ts +5 -0
  86. package/dist/interactive/skill-detail.js +126 -0
  87. package/dist/interactive.d.ts +0 -1
  88. package/dist/interactive.js +89 -66
  89. package/dist/providers/arcana.d.ts +0 -1
  90. package/dist/providers/arcana.js +0 -1
  91. package/dist/providers/base.d.ts +0 -1
  92. package/dist/providers/base.js +0 -1
  93. package/dist/providers/github.d.ts +0 -1
  94. package/dist/providers/github.js +8 -3
  95. package/dist/registry.d.ts +0 -1
  96. package/dist/registry.js +1 -4
  97. package/dist/types.d.ts +10 -1
  98. package/dist/types.js +0 -1
  99. package/dist/utils/atomic.d.ts +0 -1
  100. package/dist/utils/atomic.js +3 -2
  101. package/dist/utils/cache.d.ts +0 -1
  102. package/dist/utils/cache.js +3 -2
  103. package/dist/utils/config.d.ts +2 -1
  104. package/dist/utils/config.js +30 -5
  105. package/dist/utils/conflict-check.d.ts +8 -0
  106. package/dist/utils/conflict-check.js +72 -0
  107. package/dist/utils/errors.d.ts +0 -1
  108. package/dist/utils/errors.js +0 -1
  109. package/dist/utils/frontmatter.d.ts +0 -1
  110. package/dist/utils/frontmatter.js +37 -10
  111. package/dist/utils/fs.d.ts +0 -1
  112. package/dist/utils/fs.js +30 -11
  113. package/dist/utils/help.d.ts +0 -1
  114. package/dist/utils/help.js +15 -28
  115. package/dist/utils/history.d.ts +0 -1
  116. package/dist/utils/history.js +0 -1
  117. package/dist/utils/http.d.ts +0 -1
  118. package/dist/utils/http.js +14 -5
  119. package/dist/utils/install-core.d.ts +48 -0
  120. package/dist/utils/install-core.js +108 -0
  121. package/dist/utils/integrity.d.ts +17 -0
  122. package/dist/utils/integrity.js +84 -0
  123. package/dist/utils/parallel.d.ts +0 -1
  124. package/dist/utils/parallel.js +0 -1
  125. package/dist/utils/project-context.d.ts +19 -0
  126. package/dist/utils/project-context.js +283 -0
  127. package/dist/utils/scanner.d.ts +0 -1
  128. package/dist/utils/scanner.js +138 -10
  129. package/dist/utils/scoring.d.ts +10 -0
  130. package/dist/utils/scoring.js +84 -0
  131. package/dist/utils/ui.d.ts +0 -1
  132. package/dist/utils/ui.js +11 -4
  133. package/dist/utils/validate.d.ts +0 -1
  134. package/dist/utils/validate.js +4 -1
  135. package/package.json +74 -62
  136. package/dist/cli.d.ts.map +0 -1
  137. package/dist/cli.js.map +0 -1
  138. package/dist/commands/audit.d.ts.map +0 -1
  139. package/dist/commands/audit.js.map +0 -1
  140. package/dist/commands/audit.test.d.ts +0 -2
  141. package/dist/commands/audit.test.d.ts.map +0 -1
  142. package/dist/commands/audit.test.js +0 -217
  143. package/dist/commands/audit.test.js.map +0 -1
  144. package/dist/commands/clean.d.ts.map +0 -1
  145. package/dist/commands/clean.js.map +0 -1
  146. package/dist/commands/compact.d.ts.map +0 -1
  147. package/dist/commands/compact.js.map +0 -1
  148. package/dist/commands/config.d.ts.map +0 -1
  149. package/dist/commands/config.js.map +0 -1
  150. package/dist/commands/create.d.ts.map +0 -1
  151. package/dist/commands/create.js.map +0 -1
  152. package/dist/commands/doctor.d.ts.map +0 -1
  153. package/dist/commands/doctor.js.map +0 -1
  154. package/dist/commands/info.d.ts.map +0 -1
  155. package/dist/commands/info.js.map +0 -1
  156. package/dist/commands/init.d.ts.map +0 -1
  157. package/dist/commands/init.js.map +0 -1
  158. package/dist/commands/install.d.ts.map +0 -1
  159. package/dist/commands/install.js.map +0 -1
  160. package/dist/commands/list.d.ts.map +0 -1
  161. package/dist/commands/list.js.map +0 -1
  162. package/dist/commands/optimize.d.ts.map +0 -1
  163. package/dist/commands/optimize.js.map +0 -1
  164. package/dist/commands/providers.d.ts.map +0 -1
  165. package/dist/commands/providers.js.map +0 -1
  166. package/dist/commands/scan.d.ts.map +0 -1
  167. package/dist/commands/scan.js.map +0 -1
  168. package/dist/commands/search.d.ts.map +0 -1
  169. package/dist/commands/search.js.map +0 -1
  170. package/dist/commands/stats.d.ts.map +0 -1
  171. package/dist/commands/stats.js.map +0 -1
  172. package/dist/commands/uninstall.d.ts.map +0 -1
  173. package/dist/commands/uninstall.js.map +0 -1
  174. package/dist/commands/update.d.ts.map +0 -1
  175. package/dist/commands/update.js.map +0 -1
  176. package/dist/commands/validate.d.ts.map +0 -1
  177. package/dist/commands/validate.js.map +0 -1
  178. package/dist/index.d.ts.map +0 -1
  179. package/dist/index.js.map +0 -1
  180. package/dist/interactive.d.ts.map +0 -1
  181. package/dist/interactive.js.map +0 -1
  182. package/dist/providers/arcana.d.ts.map +0 -1
  183. package/dist/providers/arcana.js.map +0 -1
  184. package/dist/providers/base.d.ts.map +0 -1
  185. package/dist/providers/base.js.map +0 -1
  186. package/dist/providers/github.d.ts.map +0 -1
  187. package/dist/providers/github.js.map +0 -1
  188. package/dist/registry.d.ts.map +0 -1
  189. package/dist/registry.js.map +0 -1
  190. package/dist/types.d.ts.map +0 -1
  191. package/dist/types.js.map +0 -1
  192. package/dist/utils/atomic.d.ts.map +0 -1
  193. package/dist/utils/atomic.js.map +0 -1
  194. package/dist/utils/atomic.test.d.ts +0 -2
  195. package/dist/utils/atomic.test.d.ts.map +0 -1
  196. package/dist/utils/atomic.test.js +0 -31
  197. package/dist/utils/atomic.test.js.map +0 -1
  198. package/dist/utils/cache.d.ts.map +0 -1
  199. package/dist/utils/cache.js.map +0 -1
  200. package/dist/utils/config.d.ts.map +0 -1
  201. package/dist/utils/config.js.map +0 -1
  202. package/dist/utils/config.test.d.ts +0 -2
  203. package/dist/utils/config.test.d.ts.map +0 -1
  204. package/dist/utils/config.test.js +0 -38
  205. package/dist/utils/config.test.js.map +0 -1
  206. package/dist/utils/errors.d.ts.map +0 -1
  207. package/dist/utils/errors.js.map +0 -1
  208. package/dist/utils/frontmatter.d.ts.map +0 -1
  209. package/dist/utils/frontmatter.js.map +0 -1
  210. package/dist/utils/frontmatter.test.d.ts +0 -2
  211. package/dist/utils/frontmatter.test.d.ts.map +0 -1
  212. package/dist/utils/frontmatter.test.js +0 -152
  213. package/dist/utils/frontmatter.test.js.map +0 -1
  214. package/dist/utils/fs.d.ts.map +0 -1
  215. package/dist/utils/fs.js.map +0 -1
  216. package/dist/utils/fs.test.d.ts +0 -2
  217. package/dist/utils/fs.test.d.ts.map +0 -1
  218. package/dist/utils/fs.test.js +0 -145
  219. package/dist/utils/fs.test.js.map +0 -1
  220. package/dist/utils/help.d.ts.map +0 -1
  221. package/dist/utils/help.js.map +0 -1
  222. package/dist/utils/help.test.d.ts +0 -2
  223. package/dist/utils/help.test.d.ts.map +0 -1
  224. package/dist/utils/help.test.js +0 -66
  225. package/dist/utils/help.test.js.map +0 -1
  226. package/dist/utils/history.d.ts.map +0 -1
  227. package/dist/utils/history.js.map +0 -1
  228. package/dist/utils/http.d.ts.map +0 -1
  229. package/dist/utils/http.js.map +0 -1
  230. package/dist/utils/http.test.d.ts +0 -2
  231. package/dist/utils/http.test.d.ts.map +0 -1
  232. package/dist/utils/http.test.js +0 -55
  233. package/dist/utils/http.test.js.map +0 -1
  234. package/dist/utils/parallel.d.ts.map +0 -1
  235. package/dist/utils/parallel.js.map +0 -1
  236. package/dist/utils/scanner.d.ts.map +0 -1
  237. package/dist/utils/scanner.js.map +0 -1
  238. package/dist/utils/ui.d.ts.map +0 -1
  239. package/dist/utils/ui.js.map +0 -1
  240. package/dist/utils/ui.test.d.ts +0 -2
  241. package/dist/utils/ui.test.d.ts.map +0 -1
  242. package/dist/utils/ui.test.js +0 -31
  243. package/dist/utils/ui.test.js.map +0 -1
  244. package/dist/utils/validate.d.ts.map +0 -1
  245. package/dist/utils/validate.js.map +0 -1
@@ -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 { name: "Node.js", status: "fail", message: `${process.version} (need 18+)`, fix: "Install Node.js 18 or later" };
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 { name: "Skills directory", status: "warn", message: "~/.agents/skills/ not found", fix: "Run: arcana install --all" };
18
- }
19
- const skills = readdirSync(dir).filter(d => statSync(join(dir, d)).isDirectory());
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 { name: "Symlinks", status: "warn", message: `${broken}/${symlinks.length} broken symlinks`, fix: "Run: arcana clean" };
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 { name: "Git config", status: "warn", message: "Missing user.name or user.email", fix: "git config --global user.name \"Your Name\"" };
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 { name: "Git config", status: "warn", message: "Git not found", fix: "Install Git from https://git-scm.com" };
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 { /* skip */ }
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 { name: "Disk usage", status: "warn", message: `${mb} MB across ${dirCount} projects (threshold: ${DISK_USAGE_THRESHOLD_MB} MB). Try: arcana clean` };
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 = missingMd.length === 1
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 { name: "Skill sizes", status: "pass", message: `All skills under 50 KB (total: ${totalKB.toFixed(0)} KB, ~${Math.round(totalKB * 256 / 1000)}K tokens)` };
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.slice(0, 3).map(s => `${s.name} (${s.kb.toFixed(0)} KB)`).join(", ");
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({ checks: checks.map((c) => ({ name: c.name, status: c.status, message: c.message, ...(c.fix ? { fix: c.fix } : {}) })) }, null, 2));
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,4 @@
1
+ export declare function exportCommand(opts: {
2
+ json?: boolean;
3
+ sbom?: boolean;
4
+ }): Promise<void>;
@@ -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,4 @@
1
+ export declare function importCommand(file: string, opts: {
2
+ json?: boolean;
3
+ force?: boolean;
4
+ }): Promise<void>;
@@ -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
+ }
@@ -2,4 +2,3 @@ export declare function infoCommand(skillName: string, opts: {
2
2
  provider?: string;
3
3
  json?: boolean;
4
4
  }): Promise<void>;
5
- //# sourceMappingURL=info.d.ts.map
@@ -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(" " + ui.success("Installed") + (localVersion !== skill.version ? ui.warn(` (local: v${localVersion})`) : ""));
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
@@ -10,4 +10,3 @@ export declare function initCommand(opts: {
10
10
  tool?: string;
11
11
  }): Promise<void>;
12
12
  export {};
13
- //# sourceMappingURL=init.d.ts.map
@@ -1,29 +1,12 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
- import { join, basename, dirname } from "node:path";
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 name = basename(cwd);
8
- if (existsSync(join(cwd, "go.mod")))
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
- "Go": ["golang-pro", "go-linter-configuration", "testing-strategy", "security-review"],
154
- "Rust": ["rust-best-practices", "testing-strategy", "security-review"],
155
- "Python": ["python-best-practices", "testing-strategy", "security-review"],
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
- "React": ["typescript", "frontend-design", "frontend-code-review", "testing-strategy"],
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 = ["code-reviewer", "security-review", "codebase-dissection", "testing-strategy"];
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 { /* ignore */ }
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: "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'",
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
@@ -4,5 +4,5 @@ export declare function installCommand(skillNames: string[], opts: {
4
4
  force?: boolean;
5
5
  dryRun?: boolean;
6
6
  json?: boolean;
7
+ noCheck?: boolean;
7
8
  }): Promise<void>;
8
- //# sourceMappingURL=install.d.ts.map