@sporesec/arcana 2.3.1 → 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 (241) hide show
  1. package/dist/cli.d.ts +0 -1
  2. package/dist/cli.js +140 -10
  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 +2 -1
  10. package/dist/commands/clean.js +198 -47
  11. package/dist/commands/compact.d.ts +6 -0
  12. package/dist/commands/compact.js +239 -0
  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 +153 -24
  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 +156 -117
  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 +3 -0
  38. package/dist/commands/optimize.js +356 -0
  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 +83 -16
  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 +19 -1
  112. package/dist/utils/fs.js +105 -8
  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 +19 -7
  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/config.d.ts.map +0 -1
  147. package/dist/commands/config.js.map +0 -1
  148. package/dist/commands/create.d.ts.map +0 -1
  149. package/dist/commands/create.js.map +0 -1
  150. package/dist/commands/doctor.d.ts.map +0 -1
  151. package/dist/commands/doctor.js.map +0 -1
  152. package/dist/commands/info.d.ts.map +0 -1
  153. package/dist/commands/info.js.map +0 -1
  154. package/dist/commands/init.d.ts.map +0 -1
  155. package/dist/commands/init.js.map +0 -1
  156. package/dist/commands/install.d.ts.map +0 -1
  157. package/dist/commands/install.js.map +0 -1
  158. package/dist/commands/list.d.ts.map +0 -1
  159. package/dist/commands/list.js.map +0 -1
  160. package/dist/commands/providers.d.ts.map +0 -1
  161. package/dist/commands/providers.js.map +0 -1
  162. package/dist/commands/scan.d.ts.map +0 -1
  163. package/dist/commands/scan.js.map +0 -1
  164. package/dist/commands/search.d.ts.map +0 -1
  165. package/dist/commands/search.js.map +0 -1
  166. package/dist/commands/stats.d.ts.map +0 -1
  167. package/dist/commands/stats.js.map +0 -1
  168. package/dist/commands/uninstall.d.ts.map +0 -1
  169. package/dist/commands/uninstall.js.map +0 -1
  170. package/dist/commands/update.d.ts.map +0 -1
  171. package/dist/commands/update.js.map +0 -1
  172. package/dist/commands/validate.d.ts.map +0 -1
  173. package/dist/commands/validate.js.map +0 -1
  174. package/dist/index.d.ts.map +0 -1
  175. package/dist/index.js.map +0 -1
  176. package/dist/interactive.d.ts.map +0 -1
  177. package/dist/interactive.js.map +0 -1
  178. package/dist/providers/arcana.d.ts.map +0 -1
  179. package/dist/providers/arcana.js.map +0 -1
  180. package/dist/providers/base.d.ts.map +0 -1
  181. package/dist/providers/base.js.map +0 -1
  182. package/dist/providers/github.d.ts.map +0 -1
  183. package/dist/providers/github.js.map +0 -1
  184. package/dist/registry.d.ts.map +0 -1
  185. package/dist/registry.js.map +0 -1
  186. package/dist/types.d.ts.map +0 -1
  187. package/dist/types.js.map +0 -1
  188. package/dist/utils/atomic.d.ts.map +0 -1
  189. package/dist/utils/atomic.js.map +0 -1
  190. package/dist/utils/atomic.test.d.ts +0 -2
  191. package/dist/utils/atomic.test.d.ts.map +0 -1
  192. package/dist/utils/atomic.test.js +0 -31
  193. package/dist/utils/atomic.test.js.map +0 -1
  194. package/dist/utils/cache.d.ts.map +0 -1
  195. package/dist/utils/cache.js.map +0 -1
  196. package/dist/utils/config.d.ts.map +0 -1
  197. package/dist/utils/config.js.map +0 -1
  198. package/dist/utils/config.test.d.ts +0 -2
  199. package/dist/utils/config.test.d.ts.map +0 -1
  200. package/dist/utils/config.test.js +0 -38
  201. package/dist/utils/config.test.js.map +0 -1
  202. package/dist/utils/errors.d.ts.map +0 -1
  203. package/dist/utils/errors.js.map +0 -1
  204. package/dist/utils/frontmatter.d.ts.map +0 -1
  205. package/dist/utils/frontmatter.js.map +0 -1
  206. package/dist/utils/frontmatter.test.d.ts +0 -2
  207. package/dist/utils/frontmatter.test.d.ts.map +0 -1
  208. package/dist/utils/frontmatter.test.js +0 -152
  209. package/dist/utils/frontmatter.test.js.map +0 -1
  210. package/dist/utils/fs.d.ts.map +0 -1
  211. package/dist/utils/fs.js.map +0 -1
  212. package/dist/utils/fs.test.d.ts +0 -2
  213. package/dist/utils/fs.test.d.ts.map +0 -1
  214. package/dist/utils/fs.test.js +0 -145
  215. package/dist/utils/fs.test.js.map +0 -1
  216. package/dist/utils/help.d.ts.map +0 -1
  217. package/dist/utils/help.js.map +0 -1
  218. package/dist/utils/help.test.d.ts +0 -2
  219. package/dist/utils/help.test.d.ts.map +0 -1
  220. package/dist/utils/help.test.js +0 -66
  221. package/dist/utils/help.test.js.map +0 -1
  222. package/dist/utils/history.d.ts.map +0 -1
  223. package/dist/utils/history.js.map +0 -1
  224. package/dist/utils/http.d.ts.map +0 -1
  225. package/dist/utils/http.js.map +0 -1
  226. package/dist/utils/http.test.d.ts +0 -2
  227. package/dist/utils/http.test.d.ts.map +0 -1
  228. package/dist/utils/http.test.js +0 -55
  229. package/dist/utils/http.test.js.map +0 -1
  230. package/dist/utils/parallel.d.ts.map +0 -1
  231. package/dist/utils/parallel.js.map +0 -1
  232. package/dist/utils/scanner.d.ts.map +0 -1
  233. package/dist/utils/scanner.js.map +0 -1
  234. package/dist/utils/ui.d.ts.map +0 -1
  235. package/dist/utils/ui.js.map +0 -1
  236. package/dist/utils/ui.test.d.ts +0 -2
  237. package/dist/utils/ui.test.d.ts.map +0 -1
  238. package/dist/utils/ui.test.js +0 -31
  239. package/dist/utils/ui.test.js.map +0 -1
  240. package/dist/utils/validate.d.ts.map +0 -1
  241. package/dist/utils/validate.js.map +0 -1
@@ -2,49 +2,88 @@ import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { ui, banner } from "../utils/ui.js";
5
- import { getDirSize, listSymlinks } from "../utils/fs.js";
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";
8
+ const AUXILIARY_DIRS = ["file-history", "debug", "shell-snapshots", "todos", "plans"];
9
+ function purgeDir(dir, dryRun) {
10
+ if (!existsSync(dir))
11
+ return 0;
12
+ let reclaimed = 0;
13
+ for (const entry of readdirSync(dir)) {
14
+ const full = join(dir, entry);
15
+ try {
16
+ const stat = statSync(full);
17
+ reclaimed += stat.isDirectory() ? getDirSize(full) : stat.size;
18
+ if (!dryRun)
19
+ rmSync(full, { recursive: true, force: true });
20
+ }
21
+ catch {
22
+ /* skip locked files */
23
+ }
24
+ }
25
+ return reclaimed;
26
+ }
27
+ function isAgentLog(filename) {
28
+ return filename.startsWith("agent-") && filename.endsWith(".jsonl");
29
+ }
7
30
  export async function cleanCommand(opts) {
8
31
  if (!opts.json)
9
32
  banner();
10
33
  const dryRun = opts.dryRun ?? false;
34
+ const aggressive = opts.aggressive ?? false;
35
+ const keepDays = opts.keepDays ?? MAIN_LOG_MAX_AGE_DAYS;
36
+ const agentKeepDays = aggressive ? 0 : AGENT_LOG_MAX_AGE_DAYS;
37
+ const mainKeepDays = aggressive ? 0 : keepDays;
11
38
  if (dryRun && !opts.json)
12
39
  console.log(ui.warn(" DRY RUN - no files will be deleted\n"));
13
- let totalReclaimed = 0;
14
- let actions = 0;
15
- const removedSymlinks = [];
16
- const removedProjects = [];
17
- const removedCacheFiles = [];
40
+ if (aggressive && !opts.json)
41
+ console.log(ui.warn(" AGGRESSIVE MODE - all session logs will be targeted\n"));
42
+ const result = {
43
+ dryRun,
44
+ actions: 0,
45
+ reclaimedBytes: 0,
46
+ removedSymlinks: [],
47
+ removedProjects: [],
48
+ removedCacheFiles: [],
49
+ removedSessionLogs: [],
50
+ purgedDirs: [],
51
+ };
18
52
  // 1. Clean broken symlinks
19
53
  const failedSymlinks = [];
20
- for (const link of listSymlinks().filter(s => s.broken)) {
54
+ if (!opts.json)
55
+ console.log(ui.bold(" Symlinks"));
56
+ for (const link of listSymlinks().filter((s) => s.broken)) {
21
57
  if (!dryRun) {
22
58
  try {
23
59
  rmSync(link.fullPath);
24
60
  }
25
61
  catch (err) {
26
62
  if (!opts.json)
27
- console.log(` ${ui.warn("Could not remove symlink:")} ${link.name} ${ui.dim(`(${err instanceof Error ? err.message : "unknown error"})`)}`);
63
+ console.log(` ${ui.warn(" Could not remove:")} ${link.name} ${ui.dim(`(${err instanceof Error ? err.message : "unknown"})`)}`);
28
64
  failedSymlinks.push(link.name);
29
65
  continue;
30
66
  }
31
67
  }
32
68
  if (!opts.json)
33
- console.log(` ${ui.dim("Remove broken symlink:")} ${link.name}`);
34
- removedSymlinks.push(link.name);
35
- actions++;
69
+ console.log(` ${ui.dim(" Remove broken:")} ${link.name}`);
70
+ result.removedSymlinks.push(link.name);
71
+ result.actions++;
36
72
  }
37
- // 2. Clean orphaned Claude project dirs
73
+ if (result.removedSymlinks.length === 0 && !opts.json)
74
+ console.log(ui.dim(" No broken symlinks"));
75
+ // 2. Clean orphaned + stale project dirs
76
+ if (!opts.json)
77
+ console.log(ui.bold("\n Project Data"));
38
78
  const projectsDir = join(homedir(), ".claude", "projects");
39
79
  if (existsSync(projectsDir)) {
40
80
  for (const entry of readdirSync(projectsDir)) {
41
81
  const projDir = join(projectsDir, entry);
42
82
  if (!statSync(projDir).isDirectory())
43
83
  continue;
44
- // Skip memory/ and other special dirs
45
84
  if (entry === "memory" || entry.startsWith("."))
46
85
  continue;
47
- // Check for session data staleness (no files modified in 90+ days)
86
+ const orphaned = isOrphanedProject(entry);
48
87
  let newest = 0;
49
88
  try {
50
89
  for (const file of readdirSync(projDir)) {
@@ -57,26 +96,148 @@ export async function cleanCommand(opts) {
57
96
  continue;
58
97
  }
59
98
  const daysOld = (Date.now() - newest) / (1000 * 60 * 60 * 24);
60
- if (daysOld > 90) {
99
+ const shouldRemove = orphaned || daysOld > STALE_PROJECT_DAYS;
100
+ if (shouldRemove) {
61
101
  const size = getDirSize(projDir);
62
- totalReclaimed += size;
102
+ result.reclaimedBytes += size;
63
103
  const mb = (size / (1024 * 1024)).toFixed(1);
104
+ const reason = orphaned ? "orphaned (source deleted)" : `stale (${Math.floor(daysOld)}d old)`;
64
105
  if (!dryRun)
65
106
  rmSync(projDir, { recursive: true, force: true });
66
107
  if (!opts.json)
67
- console.log(` ${ui.dim("Remove stale project data:")} ${entry} ${ui.dim(`(${mb} MB, ${Math.floor(daysOld)}d old)`)}`);
68
- removedProjects.push({ name: entry, sizeMB: mb, daysOld: Math.floor(daysOld) });
69
- actions++;
108
+ console.log(` ${ui.dim("Remove:")} ${entry} ${ui.dim(`(${mb} MB, ${reason})`)}`);
109
+ result.removedProjects.push({ name: entry, sizeMB: mb, reason });
110
+ result.actions++;
111
+ }
112
+ }
113
+ }
114
+ if (result.removedProjects.length === 0 && !opts.json)
115
+ console.log(ui.dim(" No orphaned or stale projects"));
116
+ // 3. Tiered session log cleanup
117
+ // - Agent logs (agent-*.jsonl): delete after 7 days (never resumed, bulk of bloat)
118
+ // - Main sessions (UUID.jsonl): delete after 30 days (configurable with --keep-days)
119
+ // - Aggressive mode: delete everything regardless of age
120
+ if (!opts.json) {
121
+ console.log(ui.bold("\n Session Logs"));
122
+ if (!aggressive) {
123
+ console.log(ui.dim(` Agent logs: remove >${agentKeepDays}d | Main sessions: remove >${mainKeepDays}d`));
124
+ }
125
+ }
126
+ let logCount = 0;
127
+ if (existsSync(projectsDir)) {
128
+ const now = Date.now();
129
+ for (const entry of readdirSync(projectsDir)) {
130
+ const projDir = join(projectsDir, entry);
131
+ if (!statSync(projDir).isDirectory())
132
+ continue;
133
+ if (entry === "memory" || entry.startsWith("."))
134
+ continue;
135
+ // Scan all files in project dir (not recursive, JSONL files are at top level)
136
+ for (const file of readdirSync(projDir)) {
137
+ if (!file.endsWith(".jsonl"))
138
+ continue;
139
+ const fullPath = join(projDir, file);
140
+ let stat;
141
+ try {
142
+ stat = statSync(fullPath);
143
+ }
144
+ catch {
145
+ continue;
146
+ }
147
+ const daysOld = (now - stat.mtimeMs) / (1000 * 60 * 60 * 24);
148
+ const isAgent = isAgentLog(file);
149
+ const maxAge = isAgent ? agentKeepDays : mainKeepDays;
150
+ if (daysOld > maxAge) {
151
+ const sizeMB = stat.size / (1024 * 1024);
152
+ result.reclaimedBytes += stat.size;
153
+ if (!dryRun) {
154
+ try {
155
+ rmSync(fullPath, { force: true });
156
+ }
157
+ catch {
158
+ continue;
159
+ }
160
+ }
161
+ const reason = isAgent ? "agent log" : "main session";
162
+ if (!opts.json)
163
+ console.log(` ${ui.dim("Remove:")} ${entry}/${file} ${ui.dim(`(${sizeMB.toFixed(1)} MB, ${Math.floor(daysOld)}d, ${reason})`)}`);
164
+ result.removedSessionLogs.push({
165
+ project: entry,
166
+ file,
167
+ sizeMB: sizeMB.toFixed(1),
168
+ daysOld: Math.floor(daysOld),
169
+ reason,
170
+ });
171
+ logCount++;
172
+ result.actions++;
173
+ }
174
+ }
175
+ // Also clean agent subdirectories (UUID dirs containing agent subfiles)
176
+ for (const sub of readdirSync(projDir)) {
177
+ const subPath = join(projDir, sub);
178
+ try {
179
+ if (!statSync(subPath).isDirectory())
180
+ continue;
181
+ if (sub === "memory")
182
+ continue;
183
+ // Check if directory is old enough
184
+ const stat = statSync(subPath);
185
+ const daysOld = (now - stat.mtimeMs) / (1000 * 60 * 60 * 24);
186
+ if (daysOld > agentKeepDays) {
187
+ const size = getDirSize(subPath);
188
+ if (size === 0)
189
+ continue;
190
+ result.reclaimedBytes += size;
191
+ if (!dryRun)
192
+ rmSync(subPath, { recursive: true, force: true });
193
+ const mb = (size / (1024 * 1024)).toFixed(1);
194
+ if (!opts.json)
195
+ console.log(` ${ui.dim("Remove:")} ${entry}/${sub}/ ${ui.dim(`(${mb} MB, ${Math.floor(daysOld)}d, 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
+ });
203
+ logCount++;
204
+ result.actions++;
205
+ }
206
+ }
207
+ catch {
208
+ continue;
209
+ }
70
210
  }
71
211
  }
72
212
  }
73
- // 3. Clear action history
213
+ if (logCount === 0 && !opts.json)
214
+ console.log(ui.dim(" No session logs to trim"));
215
+ // 4. Purge auxiliary directories
216
+ if (!opts.json)
217
+ console.log(ui.bold("\n Auxiliary Data"));
218
+ const claudeDir = join(homedir(), ".claude");
219
+ for (const dirName of AUXILIARY_DIRS) {
220
+ const dir = join(claudeDir, dirName);
221
+ if (!existsSync(dir))
222
+ continue;
223
+ const size = getDirSize(dir);
224
+ if (size === 0)
225
+ continue;
226
+ const mb = (size / (1024 * 1024)).toFixed(1);
227
+ const reclaimed = dryRun ? size : purgeDir(dir, false);
228
+ result.reclaimedBytes += reclaimed;
229
+ if (!opts.json)
230
+ console.log(` ${ui.dim("Purge:")} ${dirName}/ ${ui.dim(`(${mb} MB)`)}`);
231
+ result.purgedDirs.push({ name: dirName, sizeMB: mb });
232
+ result.actions++;
233
+ }
234
+ if (result.purgedDirs.length === 0 && !opts.json)
235
+ console.log(ui.dim(" All clean"));
236
+ // 5. Clear action history
74
237
  if (!dryRun)
75
238
  clearHistory();
76
- if (!opts.json)
77
- console.log(` ${ui.dim("Clear action history")}`);
78
- actions++;
79
- // 4. Clean disk cache
239
+ result.actions++;
240
+ // 6. Clean disk cache
80
241
  const cacheDir = join(homedir(), ".arcana", "cache");
81
242
  if (existsSync(cacheDir)) {
82
243
  for (const file of readdirSync(cacheDir)) {
@@ -88,38 +249,28 @@ export async function cleanCommand(opts) {
88
249
  continue;
89
250
  }
90
251
  }
91
- if (!opts.json)
92
- console.log(` ${ui.dim("Remove cached data:")} ${file}`);
93
- removedCacheFiles.push(file);
94
- actions++;
252
+ result.removedCacheFiles.push(file);
253
+ result.actions++;
95
254
  }
96
255
  }
256
+ if (failedSymlinks.length > 0)
257
+ result.failedSymlinks = failedSymlinks;
258
+ // Output
97
259
  if (opts.json) {
98
- const result = {
99
- dryRun,
100
- actions,
101
- reclaimedMB: Number((totalReclaimed / (1024 * 1024)).toFixed(1)),
102
- removedSymlinks,
103
- removedProjects,
104
- removedCacheFiles,
105
- };
106
- if (failedSymlinks.length > 0)
107
- result.failedSymlinks = failedSymlinks;
108
- console.log(JSON.stringify(result));
260
+ console.log(JSON.stringify({
261
+ ...result,
262
+ reclaimedMB: Number((result.reclaimedBytes / (1024 * 1024)).toFixed(1)),
263
+ }, null, 2));
109
264
  return;
110
265
  }
111
266
  console.log();
112
- if (actions > 0) {
113
- const mb = (totalReclaimed / (1024 * 1024)).toFixed(1);
114
- const verb = dryRun ? "Would reclaim" : "Reclaimed";
115
- console.log(ui.success(` ${actions} items cleaned. ${verb} ${mb} MB.`));
267
+ const mb = (result.reclaimedBytes / (1024 * 1024)).toFixed(1);
268
+ const verb = dryRun ? "Would reclaim" : "Reclaimed";
269
+ if (result.actions > 1) {
270
+ console.log(ui.success(` ${result.actions} items cleaned. ${verb} ${mb} MB.`));
116
271
  }
117
272
  else {
118
- console.log(ui.dim(" Checked:"));
119
- console.log(ui.dim(` - Broken symlinks in ~/.claude/skills/: ${removedSymlinks.length} found`));
120
- console.log(ui.dim(` - Stale project data in ~/.claude/projects/: ${removedProjects.length} found`));
121
273
  console.log(ui.success(" Nothing to clean."));
122
274
  }
123
275
  console.log();
124
276
  }
125
- //# sourceMappingURL=clean.js.map
@@ -0,0 +1,6 @@
1
+ export declare function compactCommand(opts: {
2
+ dryRun?: boolean;
3
+ json?: boolean;
4
+ prune?: boolean;
5
+ pruneDays?: number;
6
+ }): Promise<void>;
@@ -0,0 +1,239 @@
1
+ import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { ui, banner } from "../utils/ui.js";
5
+ import { getDirSize } from "../utils/fs.js";
6
+ import { PRUNE_DEFAULT_DAYS, PRUNE_SIZE_THRESHOLD_BYTES, PRUNE_KEEP_NEWEST } from "../constants.js";
7
+ function analyzeProject(projDir, projName) {
8
+ const now = Date.now();
9
+ const mainFiles = [];
10
+ const agentFiles = [];
11
+ const sessionDirs = [];
12
+ for (const entry of readdirSync(projDir)) {
13
+ const full = join(projDir, entry);
14
+ try {
15
+ const stat = statSync(full);
16
+ if (stat.isDirectory()) {
17
+ if (entry === "memory")
18
+ continue;
19
+ sessionDirs.push({
20
+ name: entry,
21
+ sizeBytes: getDirSize(full),
22
+ daysOld: Math.floor((now - stat.mtimeMs) / (1000 * 60 * 60 * 24)),
23
+ });
24
+ continue;
25
+ }
26
+ if (!entry.endsWith(".jsonl"))
27
+ continue;
28
+ const info = {
29
+ name: entry,
30
+ sizeBytes: stat.size,
31
+ daysOld: Math.floor((now - stat.mtimeMs) / (1000 * 60 * 60 * 24)),
32
+ };
33
+ if (entry.startsWith("agent-")) {
34
+ agentFiles.push(info);
35
+ }
36
+ else {
37
+ mainFiles.push(info);
38
+ }
39
+ }
40
+ catch {
41
+ continue;
42
+ }
43
+ }
44
+ const totalBytes = mainFiles.reduce((s, f) => s + f.sizeBytes, 0) +
45
+ agentFiles.reduce((s, f) => s + f.sizeBytes, 0) +
46
+ sessionDirs.reduce((s, d) => s + d.sizeBytes, 0);
47
+ // Reclaimable: all agent files + all session dirs
48
+ const reclaimableBytes = agentFiles.reduce((s, f) => s + f.sizeBytes, 0) + sessionDirs.reduce((s, d) => s + d.sizeBytes, 0);
49
+ return { name: projName, mainFiles, agentFiles, sessionDirs, totalBytes, reclaimableBytes };
50
+ }
51
+ function formatBytes(bytes) {
52
+ if (bytes < 1024)
53
+ return `${bytes} B`;
54
+ if (bytes < 1024 * 1024)
55
+ return `${(bytes / 1024).toFixed(1)} KB`;
56
+ if (bytes < 1024 * 1024 * 1024)
57
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
58
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
59
+ }
60
+ /**
61
+ * Prune oversized main session files.
62
+ * Targets files older than pruneDays AND larger than 10 MB.
63
+ * Always keeps the 3 newest sessions per project regardless.
64
+ */
65
+ function pruneMainSessions(analyses, projectsDir, pruneDays, dryRun, json) {
66
+ const pruned = [];
67
+ let reclaimedBytes = 0;
68
+ for (const a of analyses) {
69
+ // Sort main files by age (newest first) to protect the 3 newest
70
+ const sorted = [...a.mainFiles].sort((x, y) => x.daysOld - y.daysOld);
71
+ const candidates = sorted.slice(PRUNE_KEEP_NEWEST); // Skip the 3 newest
72
+ for (const f of candidates) {
73
+ if (f.daysOld > pruneDays && f.sizeBytes > PRUNE_SIZE_THRESHOLD_BYTES) {
74
+ const sizeMB = (f.sizeBytes / (1024 * 1024)).toFixed(1);
75
+ if (!json) {
76
+ console.log(` ${ui.warn("Prune:")} ${f.name} ${ui.dim(`(${sizeMB} MB, ${f.daysOld}d old)`)}`);
77
+ }
78
+ if (!dryRun) {
79
+ try {
80
+ rmSync(join(projectsDir, a.name, f.name), { force: true });
81
+ }
82
+ catch {
83
+ /* skip */
84
+ }
85
+ }
86
+ pruned.push({ project: a.name, file: f.name, sizeMB, daysOld: f.daysOld });
87
+ reclaimedBytes += f.sizeBytes;
88
+ }
89
+ }
90
+ }
91
+ return { pruned, reclaimedBytes };
92
+ }
93
+ export async function compactCommand(opts) {
94
+ if (!opts.json)
95
+ banner();
96
+ const dryRun = opts.dryRun ?? false;
97
+ if (dryRun && !opts.json)
98
+ console.log(ui.warn(" DRY RUN - no files will be deleted\n"));
99
+ const projectsDir = join(homedir(), ".claude", "projects");
100
+ if (!existsSync(projectsDir)) {
101
+ if (opts.json) {
102
+ console.log(JSON.stringify({ projects: [], totalReclaimed: 0 }));
103
+ }
104
+ else {
105
+ console.log(ui.dim(" No session data found."));
106
+ console.log();
107
+ }
108
+ return;
109
+ }
110
+ const analyses = [];
111
+ for (const entry of readdirSync(projectsDir)) {
112
+ const projDir = join(projectsDir, entry);
113
+ if (!statSync(projDir).isDirectory())
114
+ continue;
115
+ if (entry === "memory" || entry.startsWith("."))
116
+ continue;
117
+ const analysis = analyzeProject(projDir, entry);
118
+ if (analysis.totalBytes > 0) {
119
+ analyses.push(analysis);
120
+ }
121
+ }
122
+ analyses.sort((a, b) => b.totalBytes - a.totalBytes);
123
+ if (opts.json) {
124
+ const jsonResult = {
125
+ projects: analyses.map((a) => ({
126
+ name: a.name,
127
+ totalBytes: a.totalBytes,
128
+ reclaimableBytes: a.reclaimableBytes,
129
+ mainSessions: a.mainFiles.length,
130
+ agentLogs: a.agentFiles.length,
131
+ sessionDirs: a.sessionDirs.length,
132
+ })),
133
+ totalReclaimed: dryRun ? 0 : analyses.reduce((s, a) => s + a.reclaimableBytes, 0),
134
+ };
135
+ if (!dryRun) {
136
+ for (const a of analyses) {
137
+ const projDir = join(projectsDir, a.name);
138
+ for (const f of a.agentFiles) {
139
+ try {
140
+ rmSync(join(projDir, f.name), { force: true });
141
+ }
142
+ catch {
143
+ /* skip */
144
+ }
145
+ }
146
+ for (const d of a.sessionDirs) {
147
+ try {
148
+ rmSync(join(projDir, d.name), { recursive: true, force: true });
149
+ }
150
+ catch {
151
+ /* skip */
152
+ }
153
+ }
154
+ }
155
+ }
156
+ if (opts.prune) {
157
+ const pruneDays = opts.pruneDays ?? PRUNE_DEFAULT_DAYS;
158
+ const pruneResult = pruneMainSessions(analyses, projectsDir, pruneDays, dryRun, true);
159
+ jsonResult.pruned = pruneResult.pruned;
160
+ jsonResult.prunedBytes = pruneResult.reclaimedBytes;
161
+ if (!dryRun) {
162
+ jsonResult.totalReclaimed = jsonResult.totalReclaimed + pruneResult.reclaimedBytes;
163
+ }
164
+ }
165
+ console.log(JSON.stringify(jsonResult, null, 2));
166
+ return;
167
+ }
168
+ if (!opts.json)
169
+ console.log(ui.bold(" Session Compaction Report\n"));
170
+ let totalReclaimed = 0;
171
+ const hasWork = analyses.some((a) => a.reclaimableBytes > 0);
172
+ for (const a of analyses) {
173
+ if (a.totalBytes < 1024)
174
+ continue; // Skip tiny projects
175
+ const truncName = a.name.length > 50 ? a.name.slice(0, 47) + "..." : a.name;
176
+ console.log(` ${ui.bold(truncName)} ${ui.dim(formatBytes(a.totalBytes))}`);
177
+ // Main sessions
178
+ const mainSize = a.mainFiles.reduce((s, f) => s + f.sizeBytes, 0);
179
+ console.log(` ${ui.success("Keep:")} ${a.mainFiles.length} main session${a.mainFiles.length !== 1 ? "s" : ""} ${ui.dim(`(${formatBytes(mainSize)})`)}`);
180
+ // Agent logs
181
+ if (a.agentFiles.length > 0) {
182
+ const agentSize = a.agentFiles.reduce((s, f) => s + f.sizeBytes, 0);
183
+ console.log(` ${ui.warn("Remove:")} ${a.agentFiles.length} agent log${a.agentFiles.length !== 1 ? "s" : ""} ${ui.dim(`(${formatBytes(agentSize)})`)}`);
184
+ if (!dryRun) {
185
+ const projDir = join(projectsDir, a.name);
186
+ for (const f of a.agentFiles) {
187
+ try {
188
+ rmSync(join(projDir, f.name), { force: true });
189
+ }
190
+ catch {
191
+ /* skip */
192
+ }
193
+ }
194
+ }
195
+ totalReclaimed += agentSize;
196
+ }
197
+ // Session dirs
198
+ if (a.sessionDirs.length > 0) {
199
+ const dirSize = a.sessionDirs.reduce((s, d) => s + d.sizeBytes, 0);
200
+ if (dirSize > 0) {
201
+ console.log(` ${ui.warn("Remove:")} ${a.sessionDirs.length} session dir${a.sessionDirs.length !== 1 ? "s" : ""} ${ui.dim(`(${formatBytes(dirSize)})`)}`);
202
+ if (!dryRun) {
203
+ const projDir = join(projectsDir, a.name);
204
+ for (const d of a.sessionDirs) {
205
+ try {
206
+ rmSync(join(projDir, d.name), { recursive: true, force: true });
207
+ }
208
+ catch {
209
+ /* skip */
210
+ }
211
+ }
212
+ }
213
+ totalReclaimed += dirSize;
214
+ }
215
+ }
216
+ console.log();
217
+ }
218
+ // Prune oversized main sessions if requested
219
+ if (opts.prune) {
220
+ const pruneDays = opts.pruneDays ?? PRUNE_DEFAULT_DAYS;
221
+ console.log(ui.bold(` Pruning main sessions (>${pruneDays}d old AND >10 MB, keeping 3 newest per project)\n`));
222
+ const pruneResult = pruneMainSessions(analyses, projectsDir, pruneDays, dryRun, false);
223
+ totalReclaimed += pruneResult.reclaimedBytes;
224
+ if (pruneResult.pruned.length === 0) {
225
+ console.log(ui.dim(" No oversized sessions to prune."));
226
+ }
227
+ console.log();
228
+ }
229
+ // Summary
230
+ if (!hasWork && !opts.prune) {
231
+ console.log(ui.success(" Already compact. No agent logs to remove."));
232
+ }
233
+ else {
234
+ const verb = dryRun ? "Would reclaim" : "Reclaimed";
235
+ console.log(ui.success(` ${verb} ${formatBytes(totalReclaimed)} by removing agent logs and session dirs.`));
236
+ console.log(ui.dim(" Main session files preserved for history."));
237
+ }
238
+ console.log();
239
+ }
@@ -0,0 +1,3 @@
1
+ export declare function completionsCommand(shell: string, opts: {
2
+ json?: boolean;
3
+ }): void;
@@ -0,0 +1,104 @@
1
+ const BASH_COMPLETIONS = `# arcana bash completion
2
+ _arcana_completions() {
3
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
4
+ local commands="list install info search providers create validate update uninstall init doctor clean compact stats config audit scan optimize verify lock profile benchmark diff outdated team export import completions"
5
+
6
+ if [ "\${COMP_CWORD}" -eq 1 ]; then
7
+ COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
8
+ fi
9
+ }
10
+ complete -F _arcana_completions arcana
11
+ `;
12
+ const ZSH_COMPLETIONS = `#compdef arcana
13
+ _arcana() {
14
+ local -a commands
15
+ commands=(
16
+ 'list:List available skills'
17
+ 'install:Install one or more skills'
18
+ 'info:Show skill details'
19
+ 'search:Search for skills'
20
+ 'providers:Manage skill providers'
21
+ 'create:Create a new skill'
22
+ 'validate:Validate skill structure'
23
+ 'update:Update installed skills'
24
+ 'uninstall:Uninstall skills'
25
+ 'init:Initialize arcana in project'
26
+ 'doctor:Check environment'
27
+ 'clean:Remove orphaned data'
28
+ 'compact:Remove agent logs'
29
+ 'stats:Show session analytics'
30
+ 'config:View or modify config'
31
+ 'audit:Audit skill quality'
32
+ 'scan:Scan for security threats'
33
+ 'optimize:Suggest improvements'
34
+ 'verify:Verify skill integrity'
35
+ 'lock:Manage lockfile'
36
+ 'profile:Manage skill profiles'
37
+ 'benchmark:Measure token cost'
38
+ 'diff:Show skill changes'
39
+ 'outdated:List outdated skills'
40
+ 'team:Team skill management'
41
+ 'export:Export skill manifest'
42
+ 'import:Import skills'
43
+ 'completions:Generate shell completions'
44
+ )
45
+ _describe 'command' commands
46
+ }
47
+ _arcana
48
+ `;
49
+ const FISH_COMPLETIONS = `# arcana fish completion
50
+ set -l commands list install info search providers create validate update uninstall init doctor clean compact stats config audit scan optimize verify lock profile benchmark diff outdated team export import completions
51
+ complete -c arcana -f
52
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a list -d "List available skills"
53
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a install -d "Install skills"
54
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a info -d "Show skill details"
55
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a search -d "Search for skills"
56
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a providers -d "Manage providers"
57
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a create -d "Create a skill"
58
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a validate -d "Validate skills"
59
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a update -d "Update skills"
60
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a uninstall -d "Uninstall skills"
61
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize arcana"
62
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a doctor -d "Check environment"
63
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a clean -d "Remove orphaned data"
64
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a compact -d "Remove agent logs"
65
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a stats -d "Session analytics"
66
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a config -d "View/modify config"
67
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a audit -d "Audit skill quality"
68
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a scan -d "Security scan"
69
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a optimize -d "Suggest improvements"
70
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a verify -d "Verify integrity"
71
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a lock -d "Manage lockfile"
72
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a profile -d "Skill profiles"
73
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a benchmark -d "Measure token cost"
74
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a diff -d "Show skill changes"
75
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a outdated -d "List outdated skills"
76
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a team -d "Team management"
77
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a export -d "Export manifest"
78
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a import -d "Import skills"
79
+ complete -c arcana -n "not __fish_seen_subcommand_from $commands" -a completions -d "Shell completions"
80
+ `;
81
+ const SHELLS = {
82
+ bash: BASH_COMPLETIONS,
83
+ zsh: ZSH_COMPLETIONS,
84
+ fish: FISH_COMPLETIONS,
85
+ };
86
+ export function completionsCommand(shell, opts) {
87
+ const supported = Object.keys(SHELLS);
88
+ if (!supported.includes(shell)) {
89
+ if (opts.json) {
90
+ console.log(JSON.stringify({ error: `Unsupported shell: ${shell}`, supported }));
91
+ }
92
+ else {
93
+ console.error(`Unsupported shell: ${shell}`);
94
+ console.error(`Supported: ${supported.join(", ")}`);
95
+ }
96
+ process.exit(1);
97
+ }
98
+ if (opts.json) {
99
+ console.log(JSON.stringify({ shell, script: SHELLS[shell] }));
100
+ }
101
+ else {
102
+ console.log(SHELLS[shell]);
103
+ }
104
+ }
@@ -1,4 +1,3 @@
1
1
  export declare function configCommand(action: string | undefined, value: string | undefined, opts?: {
2
2
  json?: boolean;
3
3
  }): Promise<void>;
4
- //# sourceMappingURL=config.d.ts.map