@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
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  import { Command } from "commander";
2
2
  export declare function createCli(): Command;
3
- //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js CHANGED
@@ -5,10 +5,7 @@ const require = createRequire(import.meta.url);
5
5
  const pkg = require("../package.json");
6
6
  export function createCli() {
7
7
  const program = new Command();
8
- program
9
- .name("arcana")
10
- .description("Universal AI development CLI")
11
- .version(pkg.version);
8
+ program.name("arcana").description("Universal AI development CLI").version(pkg.version);
12
9
  // Store default help formatter before overriding
13
10
  const defaultFormatHelp = program.createHelp().formatHelp.bind(program.createHelp());
14
11
  program.configureHelp({
@@ -36,8 +33,8 @@ export function createCli() {
36
33
  if (cmd) {
37
34
  console.error();
38
35
  console.error(ui.error(" Error: ") + `unknown command '${cmd}'`);
39
- const commands = ["list", "install", "info", "search", "providers", "create", "validate", "update", "uninstall", "init", "doctor", "clean", "compact", "stats", "config", "audit", "scan", "optimize"];
40
- const match = commands.find(c => c.startsWith(cmd.slice(0, 3)));
36
+ const { findClosestCommand } = await import("./command-registry.js");
37
+ const match = findClosestCommand(cmd);
41
38
  if (match) {
42
39
  console.error(ui.dim(` Did you mean '${match}'?`));
43
40
  }
@@ -55,7 +52,7 @@ export function createCli() {
55
52
  showWelcome(pkg.version);
56
53
  markInitialized();
57
54
  }
58
- const { showInteractiveMenu } = await import("./interactive.js");
55
+ const { showInteractiveMenu } = await import("./interactive/index.js");
59
56
  await showInteractiveMenu(pkg.version);
60
57
  });
61
58
  program
@@ -79,6 +76,7 @@ export function createCli() {
79
76
  .option("-f, --force", "Reinstall even if already installed")
80
77
  .option("--dry-run", "Show what would be installed without installing")
81
78
  .option("-j, --json", "Output as JSON")
79
+ .option("--no-check", "Skip conflict detection")
82
80
  .addHelpText("after", "\nExamples:\n arcana install code-reviewer\n arcana install skill1 skill2 skill3\n arcana install --all --force")
83
81
  .action(async (skills, opts) => {
84
82
  const { installCommand } = await import("./commands/install.js");
@@ -98,8 +96,10 @@ export function createCli() {
98
96
  .description("Search for skills across providers")
99
97
  .option("-p, --provider <name>", "Limit search to provider")
100
98
  .option("--no-cache", "Bypass skill cache")
99
+ .option("-t, --tag <tag>", "Filter by tech stack tag")
100
+ .option("-s, --smart", "Context-aware ranking (uses project detection)")
101
101
  .option("-j, --json", "Output as JSON")
102
- .addHelpText("after", "\nExamples:\n arcana search testing\n arcana search \"code review\"")
102
+ .addHelpText("after", '\nExamples:\n arcana search testing\n arcana search "code review"\n arcana search react --tag typescript\n arcana search api --smart')
103
103
  .action(async (query, opts) => {
104
104
  const { searchCommand } = await import("./commands/search.js");
105
105
  return searchCommand(query, opts);
@@ -184,6 +184,8 @@ export function createCli() {
184
184
  .command("compact")
185
185
  .description("Remove agent logs while preserving main session history")
186
186
  .option("-n, --dry-run", "Show what would be removed without deleting")
187
+ .option("--prune", "Also prune oversized main sessions (>14d old AND >10 MB)")
188
+ .option("--prune-days <days>", "Override prune age threshold (default: 14)", parseInt)
187
189
  .option("-j, --json", "Output as JSON")
188
190
  .action(async (opts) => {
189
191
  const { compactCommand } = await import("./commands/compact.js");
@@ -233,6 +235,115 @@ export function createCli() {
233
235
  const { optimizeCommand } = await import("./commands/optimize.js");
234
236
  return optimizeCommand(opts);
235
237
  });
238
+ // === Security ===
239
+ program
240
+ .command("verify [skill]")
241
+ .description("Verify installed skill integrity against lockfile")
242
+ .option("-a, --all", "Verify all installed skills")
243
+ .option("-j, --json", "Output as JSON")
244
+ .addHelpText("after", "\nExamples:\n arcana verify code-reviewer\n arcana verify --all\n arcana verify --all --json")
245
+ .action(async (skill, opts) => {
246
+ const { verifyCommand } = await import("./commands/verify.js");
247
+ return verifyCommand(skill, opts);
248
+ });
249
+ program
250
+ .command("lock")
251
+ .description("Generate or validate lockfile from installed skills")
252
+ .option("--ci", "Validate lockfile matches installed skills (CI mode)")
253
+ .option("-j, --json", "Output as JSON")
254
+ .addHelpText("after", "\nExamples:\n arcana lock\n arcana lock --ci\n arcana lock --json")
255
+ .action(async (opts) => {
256
+ const { lockCommand } = await import("./commands/lock.js");
257
+ return lockCommand(opts);
258
+ });
259
+ // === Performance & Inspection ===
260
+ program
261
+ .command("benchmark [skill]")
262
+ .description("Measure token cost of installed skills")
263
+ .option("-a, --all", "Benchmark all installed skills")
264
+ .option("-j, --json", "Output as JSON")
265
+ .addHelpText("after", "\nExamples:\n arcana benchmark code-reviewer\n arcana benchmark --all")
266
+ .action(async (skill, opts) => {
267
+ const { benchmarkCommand } = await import("./commands/benchmark.js");
268
+ return benchmarkCommand(skill, opts);
269
+ });
270
+ program
271
+ .command("diff <skill>")
272
+ .description("Show changes between installed and remote skill version")
273
+ .option("-p, --provider <name>", "Provider to compare against")
274
+ .option("-j, --json", "Output as JSON")
275
+ .addHelpText("after", "\nExamples:\n arcana diff code-reviewer\n arcana diff code-reviewer --json")
276
+ .action(async (skill, opts) => {
277
+ const { diffCommand } = await import("./commands/diff.js");
278
+ return diffCommand(skill, opts);
279
+ });
280
+ program
281
+ .command("outdated")
282
+ .description("List skills with newer versions available")
283
+ .option("-p, --provider <name>", "Check against specific provider")
284
+ .option("-j, --json", "Output as JSON")
285
+ .action(async (opts) => {
286
+ const { outdatedCommand } = await import("./commands/outdated.js");
287
+ return outdatedCommand(opts);
288
+ });
289
+ // === Workflow & Team ===
290
+ program
291
+ .command("profile [action] [name] [skills...]")
292
+ .description("Manage skill profiles (named sets of skills)")
293
+ .option("-j, --json", "Output as JSON")
294
+ .addHelpText("after", "\nActions:\n list List all profiles (default)\n create <name> Create a profile\n delete <name> Delete a profile\n show <name> Show skills in a profile\n apply <name> Install all skills from a profile\n\nExamples:\n arcana profile create security scan verify audit\n arcana profile apply security")
295
+ .action(async (action, name, skills, opts) => {
296
+ const { profileCommand } = await import("./commands/profile.js");
297
+ return profileCommand(action, name, skills, opts);
298
+ });
299
+ program
300
+ .command("team [action] [skill]")
301
+ .description("Manage shared team skill configuration")
302
+ .option("-j, --json", "Output as JSON")
303
+ .addHelpText("after", "\nActions:\n init Create .arcana/team.json\n sync Install skills from team config\n add <skill> Add a skill to team config\n remove <skill> Remove a skill from team config\n\nExamples:\n arcana team init\n arcana team add code-reviewer\n arcana team sync")
304
+ .action(async (action, skill, opts) => {
305
+ const { teamCommand } = await import("./commands/team.js");
306
+ return teamCommand(action, skill, opts);
307
+ });
308
+ program
309
+ .command("export")
310
+ .description("Export installed skills as a manifest")
311
+ .option("--sbom", "Export as SPDX-lite software bill of materials")
312
+ .option("-j, --json", "Output as JSON (default)")
313
+ .action(async (opts) => {
314
+ const { exportCommand } = await import("./commands/export-cmd.js");
315
+ return exportCommand(opts);
316
+ });
317
+ program
318
+ .command("import <file>")
319
+ .description("Import and install skills from a manifest file")
320
+ .option("-f, --force", "Reinstall even if already installed")
321
+ .option("-j, --json", "Output as JSON")
322
+ .addHelpText("after", "\nExamples:\n arcana import manifest.json\n arcana import manifest.json --force")
323
+ .action(async (file, opts) => {
324
+ const { importCommand } = await import("./commands/import-cmd.js");
325
+ return importCommand(file, opts);
326
+ });
327
+ program
328
+ .command("completions <shell>")
329
+ .description("Generate shell completion scripts")
330
+ .option("-j, --json", "Output as JSON")
331
+ .addHelpText("after", "\nSupported shells: bash, zsh, fish\n\nExamples:\n arcana completions bash >> ~/.bashrc\n arcana completions zsh >> ~/.zshrc\n arcana completions fish > ~/.config/fish/completions/arcana.fish")
332
+ .action(async (shell, opts) => {
333
+ const { completionsCommand } = await import("./commands/completions.js");
334
+ return completionsCommand(shell, opts);
335
+ });
336
+ // ── Smart Recommendations ──────────────────────────────────────
337
+ program
338
+ .command("recommend")
339
+ .description("Get smart skill recommendations for current project")
340
+ .option("-j, --json", "Output as JSON")
341
+ .option("-l, --limit <n>", "Max results per category", parseInt)
342
+ .option("-p, --provider <name>", "Limit to provider")
343
+ .addHelpText("after", "\nExamples:\n arcana recommend\n arcana recommend --json\n arcana recommend --limit 5")
344
+ .action(async (opts) => {
345
+ const { recommendCommand } = await import("./commands/recommend.js");
346
+ return recommendCommand(opts);
347
+ });
236
348
  return program;
237
349
  }
238
- //# sourceMappingURL=cli.js.map
@@ -0,0 +1,10 @@
1
+ export interface CommandEntry {
2
+ name: string;
3
+ usage: string;
4
+ description: string;
5
+ group: string;
6
+ }
7
+ export declare function getCommandNames(): string[];
8
+ export declare function getGroupedCommands(): Record<string, CommandEntry[]>;
9
+ export declare function findClosestCommand(input: string): string | undefined;
10
+ export declare function getCliReference(): string;
@@ -0,0 +1,65 @@
1
+ const COMMANDS = [
2
+ // Getting Started
3
+ { name: "init", usage: "init", description: "Initialize arcana in current project", group: "GETTING STARTED" },
4
+ { name: "doctor", usage: "doctor", description: "Check environment and diagnose issues", group: "GETTING STARTED" },
5
+ // Skills
6
+ { name: "list", usage: "list", description: "List available skills", group: "SKILLS" },
7
+ { name: "search", usage: "search <query>", description: "Search across providers", group: "SKILLS" },
8
+ { name: "info", usage: "info <skill>", description: "Show skill details", group: "SKILLS" },
9
+ { name: "install", usage: "install [skills...]", description: "Install one or more skills", group: "SKILLS" },
10
+ { name: "update", usage: "update [skills...]", description: "Update installed skills", group: "SKILLS" },
11
+ { name: "uninstall", usage: "uninstall [skills...]", description: "Remove one or more skills", group: "SKILLS" },
12
+ { name: "recommend", usage: "recommend", description: "Smart skill recommendations", group: "SKILLS" },
13
+ // Development
14
+ { name: "create", usage: "create <name>", description: "Create a new skill from template", group: "DEVELOPMENT" },
15
+ { name: "validate", usage: "validate [skill]", description: "Validate skill structure", group: "DEVELOPMENT" },
16
+ { name: "audit", usage: "audit [skill]", description: "Audit skill quality", group: "DEVELOPMENT" },
17
+ // Security
18
+ { name: "scan", usage: "scan [skill]", description: "Scan skills for security threats", group: "SECURITY" },
19
+ { name: "verify", usage: "verify [skill]", description: "Verify skill integrity", group: "SECURITY" },
20
+ { name: "lock", usage: "lock", description: "Generate or validate lockfile", group: "SECURITY" },
21
+ // Inspection
22
+ { name: "benchmark", usage: "benchmark [skill]", description: "Measure token cost", group: "INSPECTION" },
23
+ { name: "diff", usage: "diff <skill>", description: "Show installed vs remote changes", group: "INSPECTION" },
24
+ { name: "outdated", usage: "outdated", description: "List skills with newer versions", group: "INSPECTION" },
25
+ // Configuration
26
+ {
27
+ name: "config",
28
+ usage: "config [key] [val]",
29
+ description: "View or modify configuration",
30
+ group: "CONFIGURATION",
31
+ },
32
+ { name: "providers", usage: "providers", description: "Manage skill providers", group: "CONFIGURATION" },
33
+ { name: "clean", usage: "clean", description: "Remove orphaned data", group: "CONFIGURATION" },
34
+ { name: "compact", usage: "compact", description: "Remove agent logs", group: "CONFIGURATION" },
35
+ { name: "stats", usage: "stats", description: "Show session analytics", group: "CONFIGURATION" },
36
+ {
37
+ name: "optimize",
38
+ usage: "optimize",
39
+ description: "Suggest token/performance improvements",
40
+ group: "CONFIGURATION",
41
+ },
42
+ // Team & Workflow
43
+ { name: "profile", usage: "profile [action]", description: "Manage skill profiles", group: "WORKFLOW" },
44
+ { name: "team", usage: "team [action]", description: "Shared team skill config", group: "WORKFLOW" },
45
+ { name: "export", usage: "export", description: "Export installed skills manifest", group: "WORKFLOW" },
46
+ { name: "import", usage: "import <file>", description: "Import skills from manifest", group: "WORKFLOW" },
47
+ { name: "completions", usage: "completions <shell>", description: "Generate shell completions", group: "WORKFLOW" },
48
+ ];
49
+ export function getCommandNames() {
50
+ return COMMANDS.map((c) => c.name);
51
+ }
52
+ export function getGroupedCommands() {
53
+ const groups = {};
54
+ for (const cmd of COMMANDS) {
55
+ (groups[cmd.group] ??= []).push(cmd);
56
+ }
57
+ return groups;
58
+ }
59
+ export function findClosestCommand(input) {
60
+ const prefix = input.slice(0, 3).toLowerCase();
61
+ return COMMANDS.find((c) => c.name.startsWith(prefix))?.name;
62
+ }
63
+ export function getCliReference() {
64
+ return COMMANDS.map((c) => `arcana ${c.usage}`).join("\n");
65
+ }
@@ -14,4 +14,3 @@ export declare function auditCommand(skill: string | undefined, opts: {
14
14
  json?: boolean;
15
15
  }): Promise<void>;
16
16
  export {};
17
- //# sourceMappingURL=audit.d.ts.map
@@ -53,7 +53,11 @@ export function auditSkill(skillDir, skillName) {
53
53
  // 6. Not a capabilities list (detects pattern of many "- " lines with no code between them)
54
54
  const bulletLines = (body.match(/^- /gm) || []).length;
55
55
  const isCapabilityList = bulletLines > 20 && codeBlockCount < 3;
56
- checks.push({ name: "Not a capabilities list", passed: !isCapabilityList, detail: isCapabilityList ? `${bulletLines} bullets, ${Math.floor(codeBlockCount)} code blocks` : undefined });
56
+ checks.push({
57
+ name: "Not a capabilities list",
58
+ passed: !isCapabilityList,
59
+ detail: isCapabilityList ? `${bulletLines} bullets, ${Math.floor(codeBlockCount)} code blocks` : undefined,
60
+ });
57
61
  if (!isCapabilityList)
58
62
  score += 15;
59
63
  // 7. Reasonable length (50-500 lines)
@@ -65,7 +69,11 @@ export function auditSkill(skillDir, skillName) {
65
69
  const hasScripts = existsSync(join(skillDir, "scripts"));
66
70
  const hasRefs = existsSync(join(skillDir, "references")) || existsSync(join(skillDir, "rules"));
67
71
  const hasExtras = hasScripts || hasRefs;
68
- checks.push({ name: "Has scripts/ or references/", passed: hasExtras, detail: hasExtras ? [hasScripts && "scripts", hasRefs && "references"].filter(Boolean).join(", ") : "none" });
72
+ checks.push({
73
+ name: "Has scripts/ or references/",
74
+ passed: hasExtras,
75
+ detail: hasExtras ? [hasScripts && "scripts", hasRefs && "references"].filter(Boolean).join(", ") : "none",
76
+ });
69
77
  if (hasExtras)
70
78
  score += 10;
71
79
  // Rating
@@ -129,9 +137,12 @@ export async function auditCommand(skill, opts) {
129
137
  const counts = { PERFECT: 0, STRONG: 0, ADEQUATE: 0, WEAK: 0 };
130
138
  for (const r of results) {
131
139
  counts[r.rating]++;
132
- const ratingColor = r.rating === "PERFECT" ? ui.success
133
- : r.rating === "STRONG" ? ui.cyan
134
- : r.rating === "ADEQUATE" ? ui.warn
140
+ const ratingColor = r.rating === "PERFECT"
141
+ ? ui.success
142
+ : r.rating === "STRONG"
143
+ ? ui.cyan
144
+ : r.rating === "ADEQUATE"
145
+ ? ui.warn
135
146
  : ui.error;
136
147
  console.log(` ${ratingColor(`[${r.rating}]`)} ${ui.bold(r.skill)} ${ui.dim(`(${r.score}/100)`)}`);
137
148
  for (const check of r.checks) {
@@ -154,4 +165,3 @@ export async function auditCommand(skill, opts) {
154
165
  console.log(` ${parts.join(ui.dim(" | "))}`);
155
166
  console.log();
156
167
  }
157
- //# sourceMappingURL=audit.js.map
@@ -0,0 +1,4 @@
1
+ export declare function benchmarkCommand(skill: string | undefined, opts: {
2
+ all?: boolean;
3
+ json?: boolean;
4
+ }): Promise<void>;
@@ -0,0 +1,178 @@
1
+ import { readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getInstallDir, readSkillMeta, getDirSize } from "../utils/fs.js";
4
+ import { CONTEXT_WINDOW_TOKENS } from "../constants.js";
5
+ function collectFiles(dir, prefix) {
6
+ const entries = [];
7
+ for (const entry of readdirSync(dir)) {
8
+ const fullPath = join(dir, entry);
9
+ const stat = statSync(fullPath);
10
+ if (stat.isDirectory()) {
11
+ entries.push(...collectFiles(fullPath, prefix ? `${prefix}/${entry}` : entry));
12
+ }
13
+ else {
14
+ entries.push({ path: prefix ? `${prefix}/${entry}` : entry, sizeBytes: stat.size });
15
+ }
16
+ }
17
+ return entries;
18
+ }
19
+ function benchmarkSkill(skillName) {
20
+ const installDir = getInstallDir();
21
+ const skillDir = join(installDir, skillName);
22
+ try {
23
+ statSync(skillDir);
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ const files = collectFiles(skillDir, "");
29
+ const totalBytes = getDirSize(skillDir);
30
+ const estimatedTokens = Math.round(totalBytes / 4);
31
+ const contextPercent = (estimatedTokens / CONTEXT_WINDOW_TOKENS) * 100;
32
+ return {
33
+ name: skillName,
34
+ fileCount: files.length,
35
+ totalBytes,
36
+ estimatedTokens,
37
+ contextPercent,
38
+ files,
39
+ };
40
+ }
41
+ function formatKB(bytes) {
42
+ return (bytes / 1024).toFixed(1) + " KB";
43
+ }
44
+ function formatTokens(tokens) {
45
+ if (tokens >= 1_000_000)
46
+ return (tokens / 1_000_000).toFixed(1) + "M";
47
+ if (tokens >= 1_000)
48
+ return (tokens / 1_000).toFixed(1) + "k";
49
+ return String(tokens);
50
+ }
51
+ export async function benchmarkCommand(skill, opts) {
52
+ const installDir = getInstallDir();
53
+ if (skill) {
54
+ return benchmarkSingle(skill, opts.json);
55
+ }
56
+ if (opts.all) {
57
+ return benchmarkAll(installDir, opts.json);
58
+ }
59
+ console.error("Specify a skill name or use --all to benchmark all installed skills.");
60
+ console.error("Usage: arcana benchmark <skill-name>");
61
+ console.error(" arcana benchmark --all");
62
+ process.exit(1);
63
+ }
64
+ function benchmarkSingle(skillName, json) {
65
+ const result = benchmarkSkill(skillName);
66
+ if (!result) {
67
+ if (json) {
68
+ console.log(JSON.stringify({ error: `Skill "${skillName}" not found` }));
69
+ }
70
+ else {
71
+ console.error(`Skill "${skillName}" is not installed.`);
72
+ }
73
+ process.exit(1);
74
+ }
75
+ const meta = readSkillMeta(skillName);
76
+ if (json) {
77
+ console.log(JSON.stringify({
78
+ name: result.name,
79
+ version: meta?.version ?? "unknown",
80
+ fileCount: result.fileCount,
81
+ totalBytes: result.totalBytes,
82
+ estimatedTokens: result.estimatedTokens,
83
+ contextPercent: Math.round(result.contextPercent * 100) / 100,
84
+ files: result.files.map((f) => ({
85
+ path: f.path,
86
+ sizeBytes: f.sizeBytes,
87
+ estimatedTokens: Math.round(f.sizeBytes / 4),
88
+ })),
89
+ }));
90
+ return;
91
+ }
92
+ console.log();
93
+ console.log(` Benchmark: ${skillName}${meta?.version ? ` v${meta.version}` : ""}`);
94
+ console.log();
95
+ console.log(` Files: ${result.fileCount}`);
96
+ console.log(` Total size: ${formatKB(result.totalBytes)}`);
97
+ console.log(` Est. tokens: ${formatTokens(result.estimatedTokens)}`);
98
+ console.log(` Context usage: ${result.contextPercent.toFixed(2)}% of ${(CONTEXT_WINDOW_TOKENS / 1000).toFixed(0)}k window`);
99
+ console.log();
100
+ console.log(" File breakdown:");
101
+ console.log();
102
+ const sorted = [...result.files].sort((a, b) => b.sizeBytes - a.sizeBytes);
103
+ const maxPathLen = Math.min(Math.max(...sorted.map((f) => f.path.length)), 50);
104
+ for (const file of sorted) {
105
+ const displayPath = file.path.length > 50 ? file.path.slice(0, 47) + "..." : file.path;
106
+ const tokens = Math.round(file.sizeBytes / 4);
107
+ console.log(` ${displayPath.padEnd(maxPathLen + 2)} ${formatKB(file.sizeBytes).padStart(10)} ~${formatTokens(tokens).padStart(6)} tokens`);
108
+ }
109
+ console.log();
110
+ }
111
+ function benchmarkAll(installDir, json) {
112
+ let dirs;
113
+ try {
114
+ dirs = readdirSync(installDir).filter((d) => {
115
+ try {
116
+ return statSync(join(installDir, d)).isDirectory();
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ });
122
+ }
123
+ catch {
124
+ dirs = [];
125
+ }
126
+ if (dirs.length === 0) {
127
+ if (json) {
128
+ console.log(JSON.stringify({ skills: [], totalTokens: 0, totalBytes: 0 }));
129
+ }
130
+ else {
131
+ console.log("No skills installed.");
132
+ }
133
+ return;
134
+ }
135
+ const results = [];
136
+ for (const dir of dirs) {
137
+ const result = benchmarkSkill(dir);
138
+ if (result)
139
+ results.push(result);
140
+ }
141
+ results.sort((a, b) => b.estimatedTokens - a.estimatedTokens);
142
+ const totalBytes = results.reduce((sum, r) => sum + r.totalBytes, 0);
143
+ const totalTokens = results.reduce((sum, r) => sum + r.estimatedTokens, 0);
144
+ const totalContextPercent = (totalTokens / CONTEXT_WINDOW_TOKENS) * 100;
145
+ if (json) {
146
+ console.log(JSON.stringify({
147
+ skills: results.map((r) => ({
148
+ name: r.name,
149
+ fileCount: r.fileCount,
150
+ totalBytes: r.totalBytes,
151
+ estimatedTokens: r.estimatedTokens,
152
+ contextPercent: Math.round(r.contextPercent * 100) / 100,
153
+ })),
154
+ totalBytes,
155
+ totalTokens,
156
+ totalContextPercent: Math.round(totalContextPercent * 100) / 100,
157
+ }));
158
+ return;
159
+ }
160
+ console.log();
161
+ console.log(` Benchmark: ${results.length} installed skill(s)`);
162
+ console.log();
163
+ const maxNameLen = Math.min(Math.max(...results.map((r) => r.name.length)), 30);
164
+ console.log(` ${"Skill".padEnd(maxNameLen + 2)} ${"Files".padStart(5)} ${"Size".padStart(10)} ${"Tokens".padStart(8)} ${"Context %".padStart(9)}`);
165
+ console.log(` ${"-".repeat(maxNameLen + 2)} ${"-".repeat(5)} ${"-".repeat(10)} ${"-".repeat(8)} ${"-".repeat(9)}`);
166
+ for (const r of results) {
167
+ const displayName = r.name.length > 30 ? r.name.slice(0, 27) + "..." : r.name;
168
+ console.log(` ${displayName.padEnd(maxNameLen + 2)} ${String(r.fileCount).padStart(5)} ${formatKB(r.totalBytes).padStart(10)} ${formatTokens(r.estimatedTokens).padStart(8)} ${r.contextPercent.toFixed(2).padStart(8)}%`);
169
+ }
170
+ console.log(` ${"-".repeat(maxNameLen + 2)} ${"-".repeat(5)} ${"-".repeat(10)} ${"-".repeat(8)} ${"-".repeat(9)}`);
171
+ console.log(` ${"TOTAL".padEnd(maxNameLen + 2)} ${String(results.reduce((s, r) => s + r.fileCount, 0)).padStart(5)} ${formatKB(totalBytes).padStart(10)} ${formatTokens(totalTokens).padStart(8)} ${totalContextPercent.toFixed(2).padStart(8)}%`);
172
+ console.log();
173
+ if (totalContextPercent > 50) {
174
+ console.log(` Warning: installed skills consume ${totalContextPercent.toFixed(1)}% of context window.`);
175
+ console.log(" Consider removing unused skills with: arcana uninstall <skill>");
176
+ console.log();
177
+ }
178
+ }
@@ -4,4 +4,3 @@ export declare function cleanCommand(opts: {
4
4
  keepDays?: number;
5
5
  json?: boolean;
6
6
  }): Promise<void>;
7
- //# sourceMappingURL=clean.d.ts.map
@@ -4,10 +4,8 @@ import { homedir } from "node:os";
4
4
  import { ui, banner } from "../utils/ui.js";
5
5
  import { getDirSize, listSymlinks, isOrphanedProject } from "../utils/fs.js";
6
6
  import { clearHistory } from "../utils/history.js";
7
+ import { STALE_PROJECT_DAYS, AGENT_LOG_MAX_AGE_DAYS, MAIN_LOG_MAX_AGE_DAYS } from "../constants.js";
7
8
  const AUXILIARY_DIRS = ["file-history", "debug", "shell-snapshots", "todos", "plans"];
8
- const STALE_PROJECT_DAYS = 90;
9
- const AGENT_LOG_MAX_AGE_DAYS = 7;
10
- const MAIN_LOG_MAX_AGE_DAYS = 30;
11
9
  function purgeDir(dir, dryRun) {
12
10
  if (!existsSync(dir))
13
11
  return 0;
@@ -20,7 +18,9 @@ function purgeDir(dir, dryRun) {
20
18
  if (!dryRun)
21
19
  rmSync(full, { recursive: true, force: true });
22
20
  }
23
- catch { /* skip locked files */ }
21
+ catch {
22
+ /* skip locked files */
23
+ }
24
24
  }
25
25
  return reclaimed;
26
26
  }
@@ -53,7 +53,7 @@ export async function cleanCommand(opts) {
53
53
  const failedSymlinks = [];
54
54
  if (!opts.json)
55
55
  console.log(ui.bold(" Symlinks"));
56
- for (const link of listSymlinks().filter(s => s.broken)) {
56
+ for (const link of listSymlinks().filter((s) => s.broken)) {
57
57
  if (!dryRun) {
58
58
  try {
59
59
  rmSync(link.fullPath);
@@ -161,7 +161,13 @@ export async function cleanCommand(opts) {
161
161
  const reason = isAgent ? "agent log" : "main session";
162
162
  if (!opts.json)
163
163
  console.log(` ${ui.dim("Remove:")} ${entry}/${file} ${ui.dim(`(${sizeMB.toFixed(1)} MB, ${Math.floor(daysOld)}d, ${reason})`)}`);
164
- result.removedSessionLogs.push({ project: entry, file, sizeMB: sizeMB.toFixed(1), daysOld: Math.floor(daysOld), reason });
164
+ result.removedSessionLogs.push({
165
+ project: entry,
166
+ file,
167
+ sizeMB: sizeMB.toFixed(1),
168
+ daysOld: Math.floor(daysOld),
169
+ reason,
170
+ });
165
171
  logCount++;
166
172
  result.actions++;
167
173
  }
@@ -187,7 +193,13 @@ export async function cleanCommand(opts) {
187
193
  const mb = (size / (1024 * 1024)).toFixed(1);
188
194
  if (!opts.json)
189
195
  console.log(` ${ui.dim("Remove:")} ${entry}/${sub}/ ${ui.dim(`(${mb} MB, ${Math.floor(daysOld)}d, session dir)`)}`);
190
- result.removedSessionLogs.push({ project: entry, file: sub + "/", sizeMB: mb, daysOld: Math.floor(daysOld), reason: "session dir" });
196
+ result.removedSessionLogs.push({
197
+ project: entry,
198
+ file: sub + "/",
199
+ sizeMB: mb,
200
+ daysOld: Math.floor(daysOld),
201
+ reason: "session dir",
202
+ });
191
203
  logCount++;
192
204
  result.actions++;
193
205
  }
@@ -262,4 +274,3 @@ export async function cleanCommand(opts) {
262
274
  }
263
275
  console.log();
264
276
  }
265
- //# sourceMappingURL=clean.js.map
@@ -1,5 +1,6 @@
1
1
  export declare function compactCommand(opts: {
2
2
  dryRun?: boolean;
3
3
  json?: boolean;
4
+ prune?: boolean;
5
+ pruneDays?: number;
4
6
  }): Promise<void>;
5
- //# sourceMappingURL=compact.d.ts.map