@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.
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +140 -10
- package/dist/command-registry.d.ts +10 -0
- package/dist/command-registry.js +65 -0
- package/dist/commands/audit.d.ts +0 -1
- package/dist/commands/audit.js +16 -6
- package/dist/commands/benchmark.d.ts +4 -0
- package/dist/commands/benchmark.js +178 -0
- package/dist/commands/clean.d.ts +2 -1
- package/dist/commands/clean.js +198 -47
- package/dist/commands/compact.d.ts +6 -0
- package/dist/commands/compact.js +239 -0
- package/dist/commands/completions.d.ts +3 -0
- package/dist/commands/completions.js +104 -0
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +15 -6
- package/dist/commands/create.d.ts +0 -1
- package/dist/commands/create.js +1 -1
- package/dist/commands/diff.d.ts +4 -0
- package/dist/commands/diff.js +166 -0
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/doctor.js +153 -24
- package/dist/commands/export-cmd.d.ts +4 -0
- package/dist/commands/export-cmd.js +66 -0
- package/dist/commands/import-cmd.d.ts +4 -0
- package/dist/commands/import-cmd.js +131 -0
- package/dist/commands/info.d.ts +0 -1
- package/dist/commands/info.js +29 -4
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.js +156 -117
- package/dist/commands/install.d.ts +1 -1
- package/dist/commands/install.js +118 -205
- package/dist/commands/list.d.ts +0 -1
- package/dist/commands/list.js +12 -4
- package/dist/commands/lock.d.ts +4 -0
- package/dist/commands/lock.js +171 -0
- package/dist/commands/optimize.d.ts +3 -0
- package/dist/commands/optimize.js +356 -0
- package/dist/commands/outdated.d.ts +4 -0
- package/dist/commands/outdated.js +159 -0
- package/dist/commands/profile.d.ts +3 -0
- package/dist/commands/profile.js +274 -0
- package/dist/commands/providers.d.ts +0 -1
- package/dist/commands/providers.js +1 -4
- package/dist/commands/recommend.d.ts +5 -0
- package/dist/commands/recommend.js +96 -0
- package/dist/commands/scan.d.ts +0 -1
- package/dist/commands/scan.js +13 -7
- package/dist/commands/search.d.ts +2 -1
- package/dist/commands/search.js +32 -9
- package/dist/commands/stats.d.ts +0 -1
- package/dist/commands/stats.js +83 -16
- package/dist/commands/team.d.ts +3 -0
- package/dist/commands/team.js +291 -0
- package/dist/commands/uninstall.d.ts +0 -1
- package/dist/commands/uninstall.js +18 -4
- package/dist/commands/update.d.ts +0 -1
- package/dist/commands/update.js +155 -155
- package/dist/commands/validate.d.ts +0 -1
- package/dist/commands/validate.js +14 -6
- package/dist/commands/verify.d.ts +4 -0
- package/dist/commands/verify.js +116 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/interactive/browse.d.ts +4 -0
- package/dist/interactive/browse.js +103 -0
- package/dist/interactive/categories.d.ts +4 -0
- package/dist/interactive/categories.js +87 -0
- package/dist/interactive/health.d.ts +1 -0
- package/dist/interactive/health.js +57 -0
- package/dist/interactive/helpers.d.ts +11 -0
- package/dist/interactive/helpers.js +66 -0
- package/dist/interactive/index.d.ts +1 -0
- package/dist/interactive/index.js +1 -0
- package/dist/interactive/manage.d.ts +2 -0
- package/dist/interactive/manage.js +187 -0
- package/dist/interactive/menu.d.ts +1 -0
- package/dist/interactive/menu.js +107 -0
- package/dist/interactive/search.d.ts +2 -0
- package/dist/interactive/search.js +66 -0
- package/dist/interactive/setup.d.ts +2 -0
- package/dist/interactive/setup.js +48 -0
- package/dist/interactive/skill-detail.d.ts +5 -0
- package/dist/interactive/skill-detail.js +126 -0
- package/dist/interactive.d.ts +0 -1
- package/dist/interactive.js +89 -66
- package/dist/providers/arcana.d.ts +0 -1
- package/dist/providers/arcana.js +0 -1
- package/dist/providers/base.d.ts +0 -1
- package/dist/providers/base.js +0 -1
- package/dist/providers/github.d.ts +0 -1
- package/dist/providers/github.js +8 -3
- package/dist/registry.d.ts +0 -1
- package/dist/registry.js +1 -4
- package/dist/types.d.ts +10 -1
- package/dist/types.js +0 -1
- package/dist/utils/atomic.d.ts +0 -1
- package/dist/utils/atomic.js +3 -2
- package/dist/utils/cache.d.ts +0 -1
- package/dist/utils/cache.js +3 -2
- package/dist/utils/config.d.ts +2 -1
- package/dist/utils/config.js +30 -5
- package/dist/utils/conflict-check.d.ts +8 -0
- package/dist/utils/conflict-check.js +72 -0
- package/dist/utils/errors.d.ts +0 -1
- package/dist/utils/errors.js +0 -1
- package/dist/utils/frontmatter.d.ts +0 -1
- package/dist/utils/frontmatter.js +37 -10
- package/dist/utils/fs.d.ts +19 -1
- package/dist/utils/fs.js +105 -8
- package/dist/utils/help.d.ts +0 -1
- package/dist/utils/help.js +15 -28
- package/dist/utils/history.d.ts +0 -1
- package/dist/utils/history.js +0 -1
- package/dist/utils/http.d.ts +0 -1
- package/dist/utils/http.js +14 -5
- package/dist/utils/install-core.d.ts +48 -0
- package/dist/utils/install-core.js +108 -0
- package/dist/utils/integrity.d.ts +17 -0
- package/dist/utils/integrity.js +84 -0
- package/dist/utils/parallel.d.ts +0 -1
- package/dist/utils/parallel.js +0 -1
- package/dist/utils/project-context.d.ts +19 -0
- package/dist/utils/project-context.js +283 -0
- package/dist/utils/scanner.d.ts +0 -1
- package/dist/utils/scanner.js +138 -10
- package/dist/utils/scoring.d.ts +10 -0
- package/dist/utils/scoring.js +84 -0
- package/dist/utils/ui.d.ts +0 -1
- package/dist/utils/ui.js +11 -4
- package/dist/utils/validate.d.ts +0 -1
- package/dist/utils/validate.js +4 -1
- package/package.json +19 -7
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/audit.d.ts.map +0 -1
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/audit.test.d.ts +0 -2
- package/dist/commands/audit.test.d.ts.map +0 -1
- package/dist/commands/audit.test.js +0 -217
- package/dist/commands/audit.test.js.map +0 -1
- package/dist/commands/clean.d.ts.map +0 -1
- package/dist/commands/clean.js.map +0 -1
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/info.d.ts.map +0 -1
- package/dist/commands/info.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/install.d.ts.map +0 -1
- package/dist/commands/install.js.map +0 -1
- package/dist/commands/list.d.ts.map +0 -1
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/providers.d.ts.map +0 -1
- package/dist/commands/providers.js.map +0 -1
- package/dist/commands/scan.d.ts.map +0 -1
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/search.d.ts.map +0 -1
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/stats.d.ts.map +0 -1
- package/dist/commands/stats.js.map +0 -1
- package/dist/commands/uninstall.d.ts.map +0 -1
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/validate.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/interactive.d.ts.map +0 -1
- package/dist/interactive.js.map +0 -1
- package/dist/providers/arcana.d.ts.map +0 -1
- package/dist/providers/arcana.js.map +0 -1
- package/dist/providers/base.d.ts.map +0 -1
- package/dist/providers/base.js.map +0 -1
- package/dist/providers/github.d.ts.map +0 -1
- package/dist/providers/github.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/atomic.d.ts.map +0 -1
- package/dist/utils/atomic.js.map +0 -1
- package/dist/utils/atomic.test.d.ts +0 -2
- package/dist/utils/atomic.test.d.ts.map +0 -1
- package/dist/utils/atomic.test.js +0 -31
- package/dist/utils/atomic.test.js.map +0 -1
- package/dist/utils/cache.d.ts.map +0 -1
- package/dist/utils/cache.js.map +0 -1
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/config.test.d.ts +0 -2
- package/dist/utils/config.test.d.ts.map +0 -1
- package/dist/utils/config.test.js +0 -38
- package/dist/utils/config.test.js.map +0 -1
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/frontmatter.d.ts.map +0 -1
- package/dist/utils/frontmatter.js.map +0 -1
- package/dist/utils/frontmatter.test.d.ts +0 -2
- package/dist/utils/frontmatter.test.d.ts.map +0 -1
- package/dist/utils/frontmatter.test.js +0 -152
- package/dist/utils/frontmatter.test.js.map +0 -1
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/fs.test.d.ts +0 -2
- package/dist/utils/fs.test.d.ts.map +0 -1
- package/dist/utils/fs.test.js +0 -145
- package/dist/utils/fs.test.js.map +0 -1
- package/dist/utils/help.d.ts.map +0 -1
- package/dist/utils/help.js.map +0 -1
- package/dist/utils/help.test.d.ts +0 -2
- package/dist/utils/help.test.d.ts.map +0 -1
- package/dist/utils/help.test.js +0 -66
- package/dist/utils/help.test.js.map +0 -1
- package/dist/utils/history.d.ts.map +0 -1
- package/dist/utils/history.js.map +0 -1
- package/dist/utils/http.d.ts.map +0 -1
- package/dist/utils/http.js.map +0 -1
- package/dist/utils/http.test.d.ts +0 -2
- package/dist/utils/http.test.d.ts.map +0 -1
- package/dist/utils/http.test.js +0 -55
- package/dist/utils/http.test.js.map +0 -1
- package/dist/utils/parallel.d.ts.map +0 -1
- package/dist/utils/parallel.js.map +0 -1
- package/dist/utils/scanner.d.ts.map +0 -1
- package/dist/utils/scanner.js.map +0 -1
- package/dist/utils/ui.d.ts.map +0 -1
- package/dist/utils/ui.js.map +0 -1
- package/dist/utils/ui.test.d.ts +0 -2
- package/dist/utils/ui.test.d.ts.map +0 -1
- package/dist/utils/ui.test.js +0 -31
- package/dist/utils/ui.test.js.map +0 -1
- package/dist/utils/validate.d.ts.map +0 -1
- package/dist/utils/validate.js.map +0 -1
package/dist/commands/clean.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
+
const shouldRemove = orphaned || daysOld > STALE_PROJECT_DAYS;
|
|
100
|
+
if (shouldRemove) {
|
|
61
101
|
const size = getDirSize(projDir);
|
|
62
|
-
|
|
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(`
|
|
68
|
-
removedProjects.push({ name: entry, sizeMB: mb,
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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,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,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
|
+
}
|