@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
@@ -0,0 +1,171 @@
1
+ import { readdirSync, readFileSync, existsSync, lstatSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as p from "@clack/prompts";
4
+ import chalk from "chalk";
5
+ import { getInstallDir, readSkillMeta } from "../utils/fs.js";
6
+ import { readLockfile, writeLockfile, computeHash } from "../utils/integrity.js";
7
+ import { renderBanner } from "../utils/help.js";
8
+ import { printErrorWithHint } from "../utils/ui.js";
9
+ function readSkillFiles(skillDir) {
10
+ const files = [];
11
+ const queue = [skillDir];
12
+ while (queue.length > 0) {
13
+ const dir = queue.pop();
14
+ for (const entry of readdirSync(dir)) {
15
+ const full = join(dir, entry);
16
+ const stat = lstatSync(full);
17
+ if (stat.isDirectory())
18
+ queue.push(full);
19
+ else if (stat.isFile()) {
20
+ const relPath = full.slice(skillDir.length + 1).replace(/\\/g, "/");
21
+ files.push({ path: relPath, content: readFileSync(full, "utf-8") });
22
+ }
23
+ }
24
+ }
25
+ return files;
26
+ }
27
+ export async function lockCommand(opts) {
28
+ if (opts.ci) {
29
+ return ciMode(opts.json);
30
+ }
31
+ return generateMode(opts.json);
32
+ }
33
+ async function generateMode(json) {
34
+ if (!json) {
35
+ console.log(renderBanner());
36
+ console.log();
37
+ p.intro(chalk.bold("Generate lockfile"));
38
+ }
39
+ const installDir = getInstallDir();
40
+ if (!existsSync(installDir)) {
41
+ if (json) {
42
+ console.log(JSON.stringify({ action: "generate", entries: 0, path: "~/.arcana/arcana-lock.json" }));
43
+ }
44
+ else {
45
+ p.log.info("No skills installed. Lockfile written with 0 entries.");
46
+ }
47
+ writeLockfile([]);
48
+ return;
49
+ }
50
+ const dirs = readdirSync(installDir).filter((d) => {
51
+ try {
52
+ return lstatSync(join(installDir, d)).isDirectory();
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ });
58
+ const entries = [];
59
+ for (const name of dirs) {
60
+ const skillDir = join(installDir, name);
61
+ const files = readSkillFiles(skillDir);
62
+ const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
63
+ const concatenated = sorted.map((f) => f.content).join("");
64
+ const hash = computeHash(concatenated);
65
+ const meta = readSkillMeta(name);
66
+ entries.push({
67
+ skill: name,
68
+ version: meta?.version ?? "0.0.0",
69
+ hash,
70
+ source: meta?.source ?? "unknown",
71
+ installedAt: meta?.installedAt ?? new Date().toISOString(),
72
+ });
73
+ }
74
+ writeLockfile(entries);
75
+ if (json) {
76
+ console.log(JSON.stringify({ action: "generate", entries: entries.length, path: "~/.arcana/arcana-lock.json" }));
77
+ }
78
+ else {
79
+ p.log.success(`Lockfile written with ${entries.length} entries.`);
80
+ p.outro(chalk.dim("~/.arcana/arcana-lock.json"));
81
+ }
82
+ }
83
+ async function ciMode(json) {
84
+ if (!json) {
85
+ console.log(renderBanner());
86
+ console.log();
87
+ p.intro(chalk.bold("Validate lockfile"));
88
+ }
89
+ const existing = readLockfile();
90
+ if (existing.length === 0) {
91
+ const lockPath = join(getInstallDir(), "..", "arcana-lock.json");
92
+ if (!existsSync(lockPath)) {
93
+ if (json) {
94
+ console.log(JSON.stringify({
95
+ action: "ci",
96
+ valid: false,
97
+ mismatches: [],
98
+ missing: [],
99
+ extra: [],
100
+ error: "No lockfile found",
101
+ }));
102
+ }
103
+ else {
104
+ printErrorWithHint(new Error("No lockfile found. Run `arcana lock` first to generate one."), true);
105
+ }
106
+ process.exit(1);
107
+ }
108
+ }
109
+ const installDir = getInstallDir();
110
+ const installedDirs = existsSync(installDir)
111
+ ? readdirSync(installDir).filter((d) => {
112
+ try {
113
+ return lstatSync(join(installDir, d)).isDirectory();
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ })
119
+ : [];
120
+ const lockedNames = new Set(existing.map((e) => e.skill));
121
+ const installedNames = new Set(installedDirs);
122
+ const mismatches = [];
123
+ const missing = [];
124
+ const extra = [];
125
+ // Check each lockfile entry against installed state
126
+ for (const entry of existing) {
127
+ if (!installedNames.has(entry.skill)) {
128
+ missing.push(entry.skill);
129
+ continue;
130
+ }
131
+ const skillDir = join(installDir, entry.skill);
132
+ const files = readSkillFiles(skillDir);
133
+ const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
134
+ const concatenated = sorted.map((f) => f.content).join("");
135
+ const hash = computeHash(concatenated);
136
+ if (hash !== entry.hash) {
137
+ mismatches.push(entry.skill);
138
+ }
139
+ }
140
+ // Check for extra skills not in lockfile
141
+ for (const name of installedDirs) {
142
+ if (!lockedNames.has(name)) {
143
+ extra.push(name);
144
+ }
145
+ }
146
+ const valid = mismatches.length === 0 && missing.length === 0 && extra.length === 0;
147
+ if (json) {
148
+ console.log(JSON.stringify({ action: "ci", valid, mismatches, missing, extra }));
149
+ }
150
+ else {
151
+ if (valid) {
152
+ p.log.success("Lockfile matches installed state.");
153
+ p.outro(chalk.dim("All entries verified."));
154
+ }
155
+ else {
156
+ if (mismatches.length > 0) {
157
+ p.log.error(`Hash mismatch: ${mismatches.join(", ")}`);
158
+ }
159
+ if (missing.length > 0) {
160
+ p.log.error(`Missing from disk: ${missing.join(", ")}`);
161
+ }
162
+ if (extra.length > 0) {
163
+ p.log.warn(`Extra (not in lockfile): ${extra.join(", ")}`);
164
+ }
165
+ p.outro(chalk.dim("Lockfile validation failed."));
166
+ }
167
+ }
168
+ if (!valid) {
169
+ process.exit(1);
170
+ }
171
+ }
@@ -1,4 +1,3 @@
1
1
  export declare function optimizeCommand(opts: {
2
2
  json?: boolean;
3
3
  }): Promise<void>;
4
- //# sourceMappingURL=optimize.d.ts.map
@@ -17,19 +17,39 @@ function readSettings() {
17
17
  function checkAutocompact() {
18
18
  const settings = readSettings();
19
19
  if (!settings) {
20
- return { area: "Autocompact", status: "suggest", message: "No settings.json found", action: "Set CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=80 in ~/.claude/settings.json env block" };
20
+ return {
21
+ area: "Autocompact",
22
+ status: "suggest",
23
+ message: "No settings.json found",
24
+ action: "Set CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=80 in ~/.claude/settings.json env block",
25
+ };
21
26
  }
22
27
  const env = settings.env;
23
28
  const val = env?.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE;
24
29
  if (!val) {
25
- return { area: "Autocompact", status: "suggest", message: "Not configured (defaults to high threshold)", action: "Set to 80 to compact earlier and save tokens" };
30
+ return {
31
+ area: "Autocompact",
32
+ status: "suggest",
33
+ message: "Not configured (defaults to high threshold)",
34
+ action: "Set to 80 to compact earlier and save tokens",
35
+ };
26
36
  }
27
37
  const pct = parseInt(val);
28
38
  if (pct <= 70) {
29
- return { area: "Autocompact", status: "warn", message: `Set to ${pct}%. Too aggressive, may lose context.`, action: "Raise to 75-80% for better balance" };
39
+ return {
40
+ area: "Autocompact",
41
+ status: "warn",
42
+ message: `Set to ${pct}%. Too aggressive, may lose context.`,
43
+ action: "Raise to 75-80% for better balance",
44
+ };
30
45
  }
31
46
  if (pct > 85) {
32
- return { area: "Autocompact", status: "suggest", message: `Set to ${pct}%. Compaction happens late, less room for reasoning.`, action: "Lower to 80% for better quality (research-backed)" };
47
+ return {
48
+ area: "Autocompact",
49
+ status: "suggest",
50
+ message: `Set to ${pct}%. Compaction happens late, less room for reasoning.`,
51
+ action: "Lower to 80% for better quality (research-backed)",
52
+ };
33
53
  }
34
54
  return { area: "Autocompact", status: "good", message: `Set to ${pct}% (optimal range)` };
35
55
  }
@@ -41,7 +61,12 @@ function checkEffortLevel() {
41
61
  return { area: "Effort level", status: "good", message: "Using default (high)" };
42
62
  }
43
63
  if (val === "low") {
44
- return { area: "Effort level", status: "suggest", message: "Set to 'low'. Faster but may miss details.", action: "Use 'medium' for daily work, 'high' for complex tasks" };
64
+ return {
65
+ area: "Effort level",
66
+ status: "suggest",
67
+ message: "Set to 'low'. Faster but may miss details.",
68
+ action: "Use 'medium' for daily work, 'high' for complex tasks",
69
+ };
45
70
  }
46
71
  if (val === "medium") {
47
72
  return { area: "Effort level", status: "good", message: "Set to 'medium'. Good balance of speed and quality." };
@@ -55,7 +80,12 @@ function checkNonEssentialCalls() {
55
80
  if (val === "1" || val === "true") {
56
81
  return { area: "Non-essential calls", status: "good", message: "Disabled (saves tokens)" };
57
82
  }
58
- return { area: "Non-essential calls", status: "suggest", message: "Not disabled", action: "Set DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 in settings.json env to save tokens" };
83
+ return {
84
+ area: "Non-essential calls",
85
+ status: "suggest",
86
+ message: "Not disabled",
87
+ action: "Set DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 in settings.json env to save tokens",
88
+ };
59
89
  }
60
90
  function checkSkillTokenBudget() {
61
91
  const dir = getInstallDir();
@@ -78,7 +108,10 @@ function checkSkillTokenBudget() {
78
108
  const estTokens = Math.round(totalKB * 256);
79
109
  if (totalKB > 500) {
80
110
  large.sort((a, b) => b.kb - a.kb);
81
- const topNames = large.slice(0, 3).map(s => s.name).join(", ");
111
+ const topNames = large
112
+ .slice(0, 3)
113
+ .map((s) => s.name)
114
+ .join(", ");
82
115
  return {
83
116
  area: "Skill token budget",
84
117
  status: "warn",
@@ -94,7 +127,11 @@ function checkSkillTokenBudget() {
94
127
  action: "Review installed skills with 'arcana list --installed' and remove unused ones",
95
128
  };
96
129
  }
97
- return { area: "Skill token budget", status: "good", message: `${skillCount} skills, ${totalKB.toFixed(0)} KB (~${(estTokens / 1000).toFixed(0)}K tokens)` };
130
+ return {
131
+ area: "Skill token budget",
132
+ status: "good",
133
+ message: `${skillCount} skills, ${totalKB.toFixed(0)} KB (~${(estTokens / 1000).toFixed(0)}K tokens)`,
134
+ };
98
135
  }
99
136
  function checkDiskHealth() {
100
137
  const claudeDir = join(homedir(), ".claude");
@@ -103,19 +140,26 @@ function checkDiskHealth() {
103
140
  }
104
141
  const totalMB = getDirSize(claudeDir) / (1024 * 1024);
105
142
  if (totalMB > 1000) {
106
- return { area: "Disk health", status: "warn", message: `${totalMB.toFixed(0)} MB total Claude data`, action: "Run: arcana compact (removes agent logs, keeps sessions)" };
143
+ return {
144
+ area: "Disk health",
145
+ status: "warn",
146
+ message: `${totalMB.toFixed(0)} MB total Claude data`,
147
+ action: "Run: arcana compact (removes agent logs, keeps sessions)",
148
+ };
107
149
  }
108
150
  if (totalMB > 500) {
109
- return { area: "Disk health", status: "suggest", message: `${totalMB.toFixed(0)} MB total Claude data`, action: "Run: arcana compact" };
151
+ return {
152
+ area: "Disk health",
153
+ status: "suggest",
154
+ message: `${totalMB.toFixed(0)} MB total Claude data`,
155
+ action: "Run: arcana compact",
156
+ };
110
157
  }
111
158
  return { area: "Disk health", status: "good", message: `${totalMB.toFixed(0)} MB total Claude data` };
112
159
  }
113
160
  function checkPreCompactHook() {
114
161
  // Check both global and local settings for PreCompact hooks
115
- const paths = [
116
- join(homedir(), ".claude", "settings.json"),
117
- join(homedir(), ".claude", "settings.local.json"),
118
- ];
162
+ const paths = [join(homedir(), ".claude", "settings.json"), join(homedir(), ".claude", "settings.local.json")];
119
163
  for (const settingsPath of paths) {
120
164
  if (!existsSync(settingsPath))
121
165
  continue;
@@ -222,7 +266,56 @@ function checkAgentBloat() {
222
266
  action: "Run: arcana compact",
223
267
  };
224
268
  }
225
- return { area: "Agent log bloat", status: "good", message: `${agentCount} agent logs (${agentMB.toFixed(0)} MB), ${mainCount} main sessions (${(mainBytes / (1024 * 1024)).toFixed(0)} MB)` };
269
+ return {
270
+ area: "Agent log bloat",
271
+ status: "good",
272
+ message: `${agentCount} agent logs (${agentMB.toFixed(0)} MB), ${mainCount} main sessions (${(mainBytes / (1024 * 1024)).toFixed(0)} MB)`,
273
+ };
274
+ }
275
+ function checkLargestSkills() {
276
+ const dir = getInstallDir();
277
+ if (!existsSync(dir)) {
278
+ return { area: "Top skills by size", status: "good", message: "No skills installed" };
279
+ }
280
+ const skills = [];
281
+ for (const entry of readdirSync(dir)) {
282
+ const skillDir = join(dir, entry);
283
+ if (!statSync(skillDir).isDirectory())
284
+ continue;
285
+ const kb = getDirSize(skillDir) / 1024;
286
+ skills.push({ name: entry, kb });
287
+ }
288
+ if (skills.length === 0) {
289
+ return { area: "Top skills by size", status: "good", message: "No skills installed" };
290
+ }
291
+ skills.sort((a, b) => b.kb - a.kb);
292
+ const totalKB = skills.reduce((s, sk) => s + sk.kb, 0);
293
+ const totalMB = totalKB / 1024;
294
+ const top5 = skills
295
+ .slice(0, 5)
296
+ .map((s) => `${s.name} (${s.kb.toFixed(0)} KB)`)
297
+ .join(", ");
298
+ if (totalMB > 3) {
299
+ return {
300
+ area: "Top skills by size",
301
+ status: "warn",
302
+ message: `${skills.length} skills, ${totalMB.toFixed(1)} MB total. Top 5: ${top5}`,
303
+ action: "Review large skills with 'arcana list --installed'. Uninstall unused ones.",
304
+ };
305
+ }
306
+ if (totalMB > 1.5) {
307
+ return {
308
+ area: "Top skills by size",
309
+ status: "suggest",
310
+ message: `${skills.length} skills, ${totalMB.toFixed(1)} MB total. Top 5: ${top5}`,
311
+ action: "Consider removing rarely used skills to save context tokens",
312
+ };
313
+ }
314
+ return {
315
+ area: "Top skills by size",
316
+ status: "good",
317
+ message: `${skills.length} skills, ${totalMB.toFixed(1)} MB total. Top 5: ${top5}`,
318
+ };
226
319
  }
227
320
  export async function optimizeCommand(opts) {
228
321
  if (!opts.json) {
@@ -238,21 +331,20 @@ export async function optimizeCommand(opts) {
238
331
  checkMemorySize(),
239
332
  checkAgentBloat(),
240
333
  checkDiskHealth(),
334
+ checkLargestSkills(),
241
335
  ];
242
336
  if (opts.json) {
243
337
  console.log(JSON.stringify({ recommendations }, null, 2));
244
338
  return;
245
339
  }
246
340
  for (const rec of recommendations) {
247
- const icon = rec.status === "good" ? ui.success("[OK]")
248
- : rec.status === "suggest" ? ui.cyan("[>>]")
249
- : ui.warn("[!!]");
341
+ const icon = rec.status === "good" ? ui.success("[OK]") : rec.status === "suggest" ? ui.cyan("[>>]") : ui.warn("[!!]");
250
342
  console.log(` ${icon} ${ui.bold(rec.area)}: ${rec.message}`);
251
343
  if (rec.action) {
252
344
  console.log(ui.dim(` ${rec.action}`));
253
345
  }
254
346
  }
255
- const actionable = recommendations.filter(r => r.status !== "good");
347
+ const actionable = recommendations.filter((r) => r.status !== "good");
256
348
  console.log();
257
349
  if (actionable.length === 0) {
258
350
  console.log(ui.success(" Your setup is well optimized."));
@@ -262,4 +354,3 @@ export async function optimizeCommand(opts) {
262
354
  }
263
355
  console.log();
264
356
  }
265
- //# sourceMappingURL=optimize.js.map
@@ -0,0 +1,4 @@
1
+ export declare function outdatedCommand(opts: {
2
+ provider?: string;
3
+ json?: boolean;
4
+ }): Promise<void>;
@@ -0,0 +1,159 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import semver from "semver";
4
+ import { getInstallDir, readSkillMeta } from "../utils/fs.js";
5
+ import { getProvider, getProviders } from "../registry.js";
6
+ import { loadConfig } from "../utils/config.js";
7
+ function listInstalledSkills(installDir) {
8
+ if (!existsSync(installDir))
9
+ return [];
10
+ let entries;
11
+ try {
12
+ entries = readdirSync(installDir);
13
+ }
14
+ catch {
15
+ return [];
16
+ }
17
+ const results = [];
18
+ for (const entry of entries) {
19
+ const fullPath = join(installDir, entry);
20
+ try {
21
+ const stat = statSync(fullPath);
22
+ if (stat.isDirectory() && existsSync(join(fullPath, "SKILL.md"))) {
23
+ results.push(entry);
24
+ }
25
+ }
26
+ catch {
27
+ // skip unreadable
28
+ }
29
+ }
30
+ return results;
31
+ }
32
+ export async function outdatedCommand(opts) {
33
+ const installDir = getInstallDir();
34
+ const skills = listInstalledSkills(installDir);
35
+ if (skills.length === 0) {
36
+ if (opts.json) {
37
+ console.log(JSON.stringify({ outdated: [], upToDate: 0, total: 0 }));
38
+ }
39
+ else {
40
+ console.log("No skills installed.");
41
+ }
42
+ process.exit(0);
43
+ }
44
+ const providerName = opts.provider ?? loadConfig().defaultProvider;
45
+ const providers = opts.provider ? [getProvider(providerName)] : getProviders();
46
+ if (providers.length === 0) {
47
+ if (opts.json) {
48
+ console.log(JSON.stringify({
49
+ error: "No providers configured",
50
+ outdated: [],
51
+ upToDate: 0,
52
+ total: 0,
53
+ }));
54
+ }
55
+ else {
56
+ console.error("No providers configured. Run: arcana providers --add owner/repo");
57
+ }
58
+ process.exit(0);
59
+ }
60
+ const outdated = [];
61
+ let upToDate = 0;
62
+ let checked = 0;
63
+ for (const skillName of skills) {
64
+ const meta = readSkillMeta(skillName);
65
+ const localVersion = meta?.version ?? "0.0.0";
66
+ const preferredSource = meta?.source;
67
+ // Try the provider that installed this skill first, then fall back to others
68
+ const orderedProviders = [...providers];
69
+ if (preferredSource) {
70
+ const idx = orderedProviders.findIndex((p) => p.name === preferredSource);
71
+ if (idx > 0) {
72
+ const [pref] = orderedProviders.splice(idx, 1);
73
+ orderedProviders.unshift(pref);
74
+ }
75
+ }
76
+ let found = false;
77
+ for (const provider of orderedProviders) {
78
+ try {
79
+ const remoteInfo = await provider.info(skillName);
80
+ if (!remoteInfo)
81
+ continue;
82
+ const remoteVersion = remoteInfo.version;
83
+ const coercedRemote = semver.valid(semver.coerce(remoteVersion)) ?? "0.0.0";
84
+ const coercedLocal = semver.valid(semver.coerce(localVersion)) ?? "0.0.0";
85
+ if (semver.gt(coercedRemote, coercedLocal)) {
86
+ outdated.push({
87
+ name: skillName,
88
+ current: localVersion,
89
+ available: remoteVersion,
90
+ source: provider.name,
91
+ });
92
+ }
93
+ else {
94
+ upToDate++;
95
+ }
96
+ found = true;
97
+ checked++;
98
+ break;
99
+ }
100
+ catch {
101
+ // try next provider
102
+ }
103
+ }
104
+ if (!found) {
105
+ // Could not check this skill, count it as up-to-date for totals
106
+ upToDate++;
107
+ checked++;
108
+ }
109
+ }
110
+ const result = {
111
+ outdated,
112
+ upToDate,
113
+ total: checked,
114
+ };
115
+ if (opts.json) {
116
+ console.log(JSON.stringify(result, null, 2));
117
+ process.exit(0);
118
+ }
119
+ // Console output: aligned table
120
+ console.log(`Checked ${result.total} installed skills.`);
121
+ console.log();
122
+ if (outdated.length === 0) {
123
+ console.log("All skills are up to date.");
124
+ process.exit(0);
125
+ }
126
+ // Calculate column widths
127
+ const nameWidth = Math.max(4, ...outdated.map((e) => e.name.length));
128
+ const currentWidth = Math.max(7, ...outdated.map((e) => e.current.length));
129
+ const availableWidth = Math.max(9, ...outdated.map((e) => e.available.length));
130
+ const sourceWidth = Math.max(6, ...outdated.map((e) => e.source.length));
131
+ const header = "Name".padEnd(nameWidth) +
132
+ " " +
133
+ "Current".padEnd(currentWidth) +
134
+ " " +
135
+ "Available".padEnd(availableWidth) +
136
+ " " +
137
+ "Source".padEnd(sourceWidth);
138
+ const separator = "-".repeat(nameWidth) +
139
+ " " +
140
+ "-".repeat(currentWidth) +
141
+ " " +
142
+ "-".repeat(availableWidth) +
143
+ " " +
144
+ "-".repeat(sourceWidth);
145
+ console.log(header);
146
+ console.log(separator);
147
+ for (const entry of outdated) {
148
+ console.log(entry.name.padEnd(nameWidth) +
149
+ " " +
150
+ entry.current.padEnd(currentWidth) +
151
+ " " +
152
+ entry.available.padEnd(availableWidth) +
153
+ " " +
154
+ entry.source.padEnd(sourceWidth));
155
+ }
156
+ console.log();
157
+ console.log(`${outdated.length} outdated, ${upToDate} up to date, ${result.total} total`);
158
+ process.exit(0);
159
+ }
@@ -0,0 +1,3 @@
1
+ export declare function profileCommand(action: string | undefined, name: string | undefined, skills: string[], opts: {
2
+ json?: boolean;
3
+ }): Promise<void>;