@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.
Files changed (247) hide show
  1. package/dist/cli.d.ts +0 -1
  2. package/dist/cli.js +124 -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 +2 -3
  6. package/dist/commands/audit.js +47 -14
  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 +3 -1
  60. package/dist/commands/validate.js +90 -15
  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 +44 -14
  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/quality.d.ts +27 -0
  128. package/dist/utils/quality.js +174 -0
  129. package/dist/utils/scanner.d.ts +0 -1
  130. package/dist/utils/scanner.js +138 -10
  131. package/dist/utils/scoring.d.ts +10 -0
  132. package/dist/utils/scoring.js +84 -0
  133. package/dist/utils/ui.d.ts +0 -1
  134. package/dist/utils/ui.js +11 -4
  135. package/dist/utils/validate.d.ts +0 -1
  136. package/dist/utils/validate.js +4 -1
  137. package/package.json +74 -62
  138. package/dist/cli.d.ts.map +0 -1
  139. package/dist/cli.js.map +0 -1
  140. package/dist/commands/audit.d.ts.map +0 -1
  141. package/dist/commands/audit.js.map +0 -1
  142. package/dist/commands/audit.test.d.ts +0 -2
  143. package/dist/commands/audit.test.d.ts.map +0 -1
  144. package/dist/commands/audit.test.js +0 -217
  145. package/dist/commands/audit.test.js.map +0 -1
  146. package/dist/commands/clean.d.ts.map +0 -1
  147. package/dist/commands/clean.js.map +0 -1
  148. package/dist/commands/compact.d.ts.map +0 -1
  149. package/dist/commands/compact.js.map +0 -1
  150. package/dist/commands/config.d.ts.map +0 -1
  151. package/dist/commands/config.js.map +0 -1
  152. package/dist/commands/create.d.ts.map +0 -1
  153. package/dist/commands/create.js.map +0 -1
  154. package/dist/commands/doctor.d.ts.map +0 -1
  155. package/dist/commands/doctor.js.map +0 -1
  156. package/dist/commands/info.d.ts.map +0 -1
  157. package/dist/commands/info.js.map +0 -1
  158. package/dist/commands/init.d.ts.map +0 -1
  159. package/dist/commands/init.js.map +0 -1
  160. package/dist/commands/install.d.ts.map +0 -1
  161. package/dist/commands/install.js.map +0 -1
  162. package/dist/commands/list.d.ts.map +0 -1
  163. package/dist/commands/list.js.map +0 -1
  164. package/dist/commands/optimize.d.ts.map +0 -1
  165. package/dist/commands/optimize.js.map +0 -1
  166. package/dist/commands/providers.d.ts.map +0 -1
  167. package/dist/commands/providers.js.map +0 -1
  168. package/dist/commands/scan.d.ts.map +0 -1
  169. package/dist/commands/scan.js.map +0 -1
  170. package/dist/commands/search.d.ts.map +0 -1
  171. package/dist/commands/search.js.map +0 -1
  172. package/dist/commands/stats.d.ts.map +0 -1
  173. package/dist/commands/stats.js.map +0 -1
  174. package/dist/commands/uninstall.d.ts.map +0 -1
  175. package/dist/commands/uninstall.js.map +0 -1
  176. package/dist/commands/update.d.ts.map +0 -1
  177. package/dist/commands/update.js.map +0 -1
  178. package/dist/commands/validate.d.ts.map +0 -1
  179. package/dist/commands/validate.js.map +0 -1
  180. package/dist/index.d.ts.map +0 -1
  181. package/dist/index.js.map +0 -1
  182. package/dist/interactive.d.ts.map +0 -1
  183. package/dist/interactive.js.map +0 -1
  184. package/dist/providers/arcana.d.ts.map +0 -1
  185. package/dist/providers/arcana.js.map +0 -1
  186. package/dist/providers/base.d.ts.map +0 -1
  187. package/dist/providers/base.js.map +0 -1
  188. package/dist/providers/github.d.ts.map +0 -1
  189. package/dist/providers/github.js.map +0 -1
  190. package/dist/registry.d.ts.map +0 -1
  191. package/dist/registry.js.map +0 -1
  192. package/dist/types.d.ts.map +0 -1
  193. package/dist/types.js.map +0 -1
  194. package/dist/utils/atomic.d.ts.map +0 -1
  195. package/dist/utils/atomic.js.map +0 -1
  196. package/dist/utils/atomic.test.d.ts +0 -2
  197. package/dist/utils/atomic.test.d.ts.map +0 -1
  198. package/dist/utils/atomic.test.js +0 -31
  199. package/dist/utils/atomic.test.js.map +0 -1
  200. package/dist/utils/cache.d.ts.map +0 -1
  201. package/dist/utils/cache.js.map +0 -1
  202. package/dist/utils/config.d.ts.map +0 -1
  203. package/dist/utils/config.js.map +0 -1
  204. package/dist/utils/config.test.d.ts +0 -2
  205. package/dist/utils/config.test.d.ts.map +0 -1
  206. package/dist/utils/config.test.js +0 -38
  207. package/dist/utils/config.test.js.map +0 -1
  208. package/dist/utils/errors.d.ts.map +0 -1
  209. package/dist/utils/errors.js.map +0 -1
  210. package/dist/utils/frontmatter.d.ts.map +0 -1
  211. package/dist/utils/frontmatter.js.map +0 -1
  212. package/dist/utils/frontmatter.test.d.ts +0 -2
  213. package/dist/utils/frontmatter.test.d.ts.map +0 -1
  214. package/dist/utils/frontmatter.test.js +0 -152
  215. package/dist/utils/frontmatter.test.js.map +0 -1
  216. package/dist/utils/fs.d.ts.map +0 -1
  217. package/dist/utils/fs.js.map +0 -1
  218. package/dist/utils/fs.test.d.ts +0 -2
  219. package/dist/utils/fs.test.d.ts.map +0 -1
  220. package/dist/utils/fs.test.js +0 -145
  221. package/dist/utils/fs.test.js.map +0 -1
  222. package/dist/utils/help.d.ts.map +0 -1
  223. package/dist/utils/help.js.map +0 -1
  224. package/dist/utils/help.test.d.ts +0 -2
  225. package/dist/utils/help.test.d.ts.map +0 -1
  226. package/dist/utils/help.test.js +0 -66
  227. package/dist/utils/help.test.js.map +0 -1
  228. package/dist/utils/history.d.ts.map +0 -1
  229. package/dist/utils/history.js.map +0 -1
  230. package/dist/utils/http.d.ts.map +0 -1
  231. package/dist/utils/http.js.map +0 -1
  232. package/dist/utils/http.test.d.ts +0 -2
  233. package/dist/utils/http.test.d.ts.map +0 -1
  234. package/dist/utils/http.test.js +0 -55
  235. package/dist/utils/http.test.js.map +0 -1
  236. package/dist/utils/parallel.d.ts.map +0 -1
  237. package/dist/utils/parallel.js.map +0 -1
  238. package/dist/utils/scanner.d.ts.map +0 -1
  239. package/dist/utils/scanner.js.map +0 -1
  240. package/dist/utils/ui.d.ts.map +0 -1
  241. package/dist/utils/ui.js.map +0 -1
  242. package/dist/utils/ui.test.d.ts +0 -2
  243. package/dist/utils/ui.test.d.ts.map +0 -1
  244. package/dist/utils/ui.test.js +0 -31
  245. package/dist/utils/ui.test.js.map +0 -1
  246. package/dist/utils/validate.d.ts.map +0 -1
  247. 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);
@@ -127,6 +127,9 @@ export function createCli() {
127
127
  .option("-a, --all", "Validate all installed skills")
128
128
  .option("-f, --fix", "Auto-fix common issues")
129
129
  .option("-j, --json", "Output as JSON")
130
+ .option("--source <dir>", "Validate from source directory instead of install dir")
131
+ .option("--cross", "Run cross-validation (marketplace sync, companions, orphans)")
132
+ .option("--min-score <n>", "Minimum quality score (0-100), fail if any skill scores below", parseInt)
130
133
  .action(async (skill, opts) => {
131
134
  const { validateCommand } = await import("./commands/validate.js");
132
135
  return validateCommand(skill, opts);
@@ -184,6 +187,8 @@ export function createCli() {
184
187
  .command("compact")
185
188
  .description("Remove agent logs while preserving main session history")
186
189
  .option("-n, --dry-run", "Show what would be removed without deleting")
190
+ .option("--prune", "Also prune oversized main sessions (>14d old AND >10 MB)")
191
+ .option("--prune-days <days>", "Override prune age threshold (default: 14)", parseInt)
187
192
  .option("-j, --json", "Output as JSON")
188
193
  .action(async (opts) => {
189
194
  const { compactCommand } = await import("./commands/compact.js");
@@ -211,6 +216,7 @@ export function createCli() {
211
216
  .description("Audit skill quality (code examples, BAD/GOOD pairs, structure)")
212
217
  .option("-a, --all", "Audit all installed skills")
213
218
  .option("-j, --json", "Output as JSON")
219
+ .option("--source <dir>", "Audit from source directory instead of install dir")
214
220
  .action(async (skill, opts) => {
215
221
  const { auditCommand } = await import("./commands/audit.js");
216
222
  return auditCommand(skill, opts);
@@ -233,6 +239,115 @@ export function createCli() {
233
239
  const { optimizeCommand } = await import("./commands/optimize.js");
234
240
  return optimizeCommand(opts);
235
241
  });
242
+ // === Security ===
243
+ program
244
+ .command("verify [skill]")
245
+ .description("Verify installed skill integrity against lockfile")
246
+ .option("-a, --all", "Verify all installed skills")
247
+ .option("-j, --json", "Output as JSON")
248
+ .addHelpText("after", "\nExamples:\n arcana verify code-reviewer\n arcana verify --all\n arcana verify --all --json")
249
+ .action(async (skill, opts) => {
250
+ const { verifyCommand } = await import("./commands/verify.js");
251
+ return verifyCommand(skill, opts);
252
+ });
253
+ program
254
+ .command("lock")
255
+ .description("Generate or validate lockfile from installed skills")
256
+ .option("--ci", "Validate lockfile matches installed skills (CI mode)")
257
+ .option("-j, --json", "Output as JSON")
258
+ .addHelpText("after", "\nExamples:\n arcana lock\n arcana lock --ci\n arcana lock --json")
259
+ .action(async (opts) => {
260
+ const { lockCommand } = await import("./commands/lock.js");
261
+ return lockCommand(opts);
262
+ });
263
+ // === Performance & Inspection ===
264
+ program
265
+ .command("benchmark [skill]")
266
+ .description("Measure token cost of installed skills")
267
+ .option("-a, --all", "Benchmark all installed skills")
268
+ .option("-j, --json", "Output as JSON")
269
+ .addHelpText("after", "\nExamples:\n arcana benchmark code-reviewer\n arcana benchmark --all")
270
+ .action(async (skill, opts) => {
271
+ const { benchmarkCommand } = await import("./commands/benchmark.js");
272
+ return benchmarkCommand(skill, opts);
273
+ });
274
+ program
275
+ .command("diff <skill>")
276
+ .description("Show changes between installed and remote skill version")
277
+ .option("-p, --provider <name>", "Provider to compare against")
278
+ .option("-j, --json", "Output as JSON")
279
+ .addHelpText("after", "\nExamples:\n arcana diff code-reviewer\n arcana diff code-reviewer --json")
280
+ .action(async (skill, opts) => {
281
+ const { diffCommand } = await import("./commands/diff.js");
282
+ return diffCommand(skill, opts);
283
+ });
284
+ program
285
+ .command("outdated")
286
+ .description("List skills with newer versions available")
287
+ .option("-p, --provider <name>", "Check against specific provider")
288
+ .option("-j, --json", "Output as JSON")
289
+ .action(async (opts) => {
290
+ const { outdatedCommand } = await import("./commands/outdated.js");
291
+ return outdatedCommand(opts);
292
+ });
293
+ // === Workflow & Team ===
294
+ program
295
+ .command("profile [action] [name] [skills...]")
296
+ .description("Manage skill profiles (named sets of skills)")
297
+ .option("-j, --json", "Output as JSON")
298
+ .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")
299
+ .action(async (action, name, skills, opts) => {
300
+ const { profileCommand } = await import("./commands/profile.js");
301
+ return profileCommand(action, name, skills, opts);
302
+ });
303
+ program
304
+ .command("team [action] [skill]")
305
+ .description("Manage shared team skill configuration")
306
+ .option("-j, --json", "Output as JSON")
307
+ .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")
308
+ .action(async (action, skill, opts) => {
309
+ const { teamCommand } = await import("./commands/team.js");
310
+ return teamCommand(action, skill, opts);
311
+ });
312
+ program
313
+ .command("export")
314
+ .description("Export installed skills as a manifest")
315
+ .option("--sbom", "Export as SPDX-lite software bill of materials")
316
+ .option("-j, --json", "Output as JSON (default)")
317
+ .action(async (opts) => {
318
+ const { exportCommand } = await import("./commands/export-cmd.js");
319
+ return exportCommand(opts);
320
+ });
321
+ program
322
+ .command("import <file>")
323
+ .description("Import and install skills from a manifest file")
324
+ .option("-f, --force", "Reinstall even if already installed")
325
+ .option("-j, --json", "Output as JSON")
326
+ .addHelpText("after", "\nExamples:\n arcana import manifest.json\n arcana import manifest.json --force")
327
+ .action(async (file, opts) => {
328
+ const { importCommand } = await import("./commands/import-cmd.js");
329
+ return importCommand(file, opts);
330
+ });
331
+ program
332
+ .command("completions <shell>")
333
+ .description("Generate shell completion scripts")
334
+ .option("-j, --json", "Output as JSON")
335
+ .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")
336
+ .action(async (shell, opts) => {
337
+ const { completionsCommand } = await import("./commands/completions.js");
338
+ return completionsCommand(shell, opts);
339
+ });
340
+ // ── Smart Recommendations ──────────────────────────────────────
341
+ program
342
+ .command("recommend")
343
+ .description("Get smart skill recommendations for current project")
344
+ .option("-j, --json", "Output as JSON")
345
+ .option("-l, --limit <n>", "Max results per category", parseInt)
346
+ .option("-p, --provider <name>", "Limit to provider")
347
+ .addHelpText("after", "\nExamples:\n arcana recommend\n arcana recommend --json\n arcana recommend --limit 5")
348
+ .action(async (opts) => {
349
+ const { recommendCommand } = await import("./commands/recommend.js");
350
+ return recommendCommand(opts);
351
+ });
236
352
  return program;
237
353
  }
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
+ }
@@ -1,4 +1,4 @@
1
- interface AuditResult {
1
+ export interface AuditResult {
2
2
  skill: string;
3
3
  rating: "PERFECT" | "STRONG" | "ADEQUATE" | "WEAK";
4
4
  score: number;
@@ -12,6 +12,5 @@ export declare function auditSkill(skillDir: string, skillName: string): AuditRe
12
12
  export declare function auditCommand(skill: string | undefined, opts: {
13
13
  all?: boolean;
14
14
  json?: boolean;
15
+ source?: string;
15
16
  }): Promise<void>;
16
- export {};
17
- //# sourceMappingURL=audit.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
- import { join } from "node:path";
2
+ import { join, resolve } from "node:path";
3
3
  import { getInstallDir } from "../utils/fs.js";
4
4
  import { extractFrontmatter, parseFrontmatter } from "../utils/frontmatter.js";
5
5
  import { ui, banner } from "../utils/ui.js";
@@ -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,12 +69,32 @@ 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
- // Rating
79
+ // 9. Section diversity (3+ unique ## headings)
80
+ const uniqueHeadings = new Set((body.match(/^## .+$/gm) || []).map((h) => h.toLowerCase()));
81
+ const goodDiversity = uniqueHeadings.size >= 3;
82
+ checks.push({
83
+ name: "Section diversity (3+ unique headings)",
84
+ passed: goodDiversity,
85
+ detail: `${uniqueHeadings.size} unique sections`,
86
+ });
87
+ if (goodDiversity)
88
+ score += 5;
89
+ // 10. Numbered steps (task decomposition signal)
90
+ const numberedSteps = (body.match(/^\d+\.\s/gm) || []).length;
91
+ const hasSteps = numberedSteps >= 3;
92
+ checks.push({ name: "Has numbered steps (3+)", passed: hasSteps, detail: `${numberedSteps} steps` });
93
+ if (hasSteps)
94
+ score += 5;
95
+ // Rating (max possible: 110)
72
96
  let rating;
73
- if (score >= 85)
97
+ if (score >= 90)
74
98
  rating = "PERFECT";
75
99
  else if (score >= 65)
76
100
  rating = "STRONG";
@@ -83,8 +107,8 @@ export function auditSkill(skillDir, skillName) {
83
107
  export async function auditCommand(skill, opts) {
84
108
  if (!opts.json)
85
109
  banner();
86
- const installDir = getInstallDir();
87
- if (!existsSync(installDir)) {
110
+ const baseDir = opts.source ? resolve(opts.source) : getInstallDir();
111
+ if (!existsSync(baseDir)) {
88
112
  if (opts.json) {
89
113
  console.log(JSON.stringify({ results: [] }));
90
114
  }
@@ -96,7 +120,14 @@ export async function auditCommand(skill, opts) {
96
120
  }
97
121
  let skills;
98
122
  if (opts.all) {
99
- skills = readdirSync(installDir).filter((d) => statSync(join(installDir, d)).isDirectory());
123
+ skills = readdirSync(baseDir).filter((d) => {
124
+ try {
125
+ return statSync(join(baseDir, d)).isDirectory();
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ });
100
131
  }
101
132
  else if (skill) {
102
133
  skills = [skill];
@@ -115,7 +146,7 @@ export async function auditCommand(skill, opts) {
115
146
  }
116
147
  const results = [];
117
148
  for (const name of skills.sort()) {
118
- const skillDir = join(installDir, name);
149
+ const skillDir = join(baseDir, name);
119
150
  if (!existsSync(skillDir)) {
120
151
  results.push({ skill: name, rating: "WEAK", score: 0, checks: [{ name: "Exists", passed: false }] });
121
152
  continue;
@@ -129,11 +160,14 @@ export async function auditCommand(skill, opts) {
129
160
  const counts = { PERFECT: 0, STRONG: 0, ADEQUATE: 0, WEAK: 0 };
130
161
  for (const r of results) {
131
162
  counts[r.rating]++;
132
- const ratingColor = r.rating === "PERFECT" ? ui.success
133
- : r.rating === "STRONG" ? ui.cyan
134
- : r.rating === "ADEQUATE" ? ui.warn
163
+ const ratingColor = r.rating === "PERFECT"
164
+ ? ui.success
165
+ : r.rating === "STRONG"
166
+ ? ui.cyan
167
+ : r.rating === "ADEQUATE"
168
+ ? ui.warn
135
169
  : ui.error;
136
- console.log(` ${ratingColor(`[${r.rating}]`)} ${ui.bold(r.skill)} ${ui.dim(`(${r.score}/100)`)}`);
170
+ console.log(` ${ratingColor(`[${r.rating}]`)} ${ui.bold(r.skill)} ${ui.dim(`(${r.score}/110)`)}`);
137
171
  for (const check of r.checks) {
138
172
  if (!check.passed) {
139
173
  const detail = check.detail ? ` ${ui.dim(`(${check.detail})`)}` : "";
@@ -154,4 +188,3 @@ export async function auditCommand(skill, opts) {
154
188
  console.log(` ${parts.join(ui.dim(" | "))}`);
155
189
  console.log();
156
190
  }
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