@asifkibria/claude-code-toolkit 1.0.2 → 1.2.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/README.md +165 -214
- package/dist/CLAUDE.md +7 -0
- package/dist/__tests__/dashboard.test.d.ts +2 -0
- package/dist/__tests__/dashboard.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard.test.js +606 -0
- package/dist/__tests__/dashboard.test.js.map +1 -0
- package/dist/__tests__/mcp-validator.test.d.ts +2 -0
- package/dist/__tests__/mcp-validator.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-validator.test.js +217 -0
- package/dist/__tests__/mcp-validator.test.js.map +1 -0
- package/dist/__tests__/scanner.test.js +350 -1
- package/dist/__tests__/scanner.test.js.map +1 -1
- package/dist/__tests__/security.test.d.ts +2 -0
- package/dist/__tests__/security.test.d.ts.map +1 -0
- package/dist/__tests__/security.test.js +375 -0
- package/dist/__tests__/security.test.js.map +1 -0
- package/dist/__tests__/session-recovery.test.d.ts +2 -0
- package/dist/__tests__/session-recovery.test.d.ts.map +1 -0
- package/dist/__tests__/session-recovery.test.js +230 -0
- package/dist/__tests__/session-recovery.test.js.map +1 -0
- package/dist/__tests__/storage.test.d.ts +2 -0
- package/dist/__tests__/storage.test.d.ts.map +1 -0
- package/dist/__tests__/storage.test.js +241 -0
- package/dist/__tests__/storage.test.js.map +1 -0
- package/dist/__tests__/trace.test.d.ts +2 -0
- package/dist/__tests__/trace.test.d.ts.map +1 -0
- package/dist/__tests__/trace.test.js +376 -0
- package/dist/__tests__/trace.test.js.map +1 -0
- package/dist/cli.js +501 -20
- package/dist/cli.js.map +1 -1
- package/dist/index.js +950 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/dashboard-ui.d.ts +2 -0
- package/dist/lib/dashboard-ui.d.ts.map +1 -0
- package/dist/lib/dashboard-ui.js +2075 -0
- package/dist/lib/dashboard-ui.js.map +1 -0
- package/dist/lib/dashboard.d.ts +15 -0
- package/dist/lib/dashboard.d.ts.map +1 -0
- package/dist/lib/dashboard.js +1422 -0
- package/dist/lib/dashboard.js.map +1 -0
- package/dist/lib/logs.d.ts +42 -0
- package/dist/lib/logs.d.ts.map +1 -0
- package/dist/lib/logs.js +166 -0
- package/dist/lib/logs.js.map +1 -0
- package/dist/lib/mcp-validator.d.ts +86 -0
- package/dist/lib/mcp-validator.d.ts.map +1 -0
- package/dist/lib/mcp-validator.js +463 -0
- package/dist/lib/mcp-validator.js.map +1 -0
- package/dist/lib/scanner.d.ts +187 -2
- package/dist/lib/scanner.d.ts.map +1 -1
- package/dist/lib/scanner.js +1224 -14
- package/dist/lib/scanner.js.map +1 -1
- package/dist/lib/security.d.ts +57 -0
- package/dist/lib/security.d.ts.map +1 -0
- package/dist/lib/security.js +423 -0
- package/dist/lib/security.js.map +1 -0
- package/dist/lib/session-recovery.d.ts +60 -0
- package/dist/lib/session-recovery.d.ts.map +1 -0
- package/dist/lib/session-recovery.js +433 -0
- package/dist/lib/session-recovery.js.map +1 -0
- package/dist/lib/storage.d.ts +68 -0
- package/dist/lib/storage.d.ts.map +1 -0
- package/dist/lib/storage.js +500 -0
- package/dist/lib/storage.js.map +1 -0
- package/dist/lib/trace.d.ts +119 -0
- package/dist/lib/trace.d.ts.map +1 -0
- package/dist/lib/trace.js +649 -0
- package/dist/lib/trace.js.map +1 -0
- package/package.json +11 -3
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
5
|
+
const TRACE_CATEGORIES = [
|
|
6
|
+
{
|
|
7
|
+
name: "conversations",
|
|
8
|
+
description: "Full conversation transcripts with all code and prompts",
|
|
9
|
+
sensitivity: "critical",
|
|
10
|
+
paths: ["projects"],
|
|
11
|
+
pattern: /\.jsonl$/,
|
|
12
|
+
impactWarning: "Your prompts, code snippets, and complete conversation history will be permanently deleted. You will lose the ability to review past sessions or resume conversations.",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "subagents",
|
|
16
|
+
description: "Sub-agent conversation transcripts",
|
|
17
|
+
sensitivity: "critical",
|
|
18
|
+
paths: ["projects"],
|
|
19
|
+
pattern: /subagents\/agent-.*\.jsonl$/,
|
|
20
|
+
impactWarning: "All sub-agent conversation transcripts will be deleted. Background task history and multi-step operation logs will be lost.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "debug-logs",
|
|
24
|
+
description: "Session debug information",
|
|
25
|
+
sensitivity: "high",
|
|
26
|
+
paths: ["debug"],
|
|
27
|
+
impactWarning: "Session debug logs will be deleted. These contain troubleshooting information useful for diagnosing issues.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "file-history",
|
|
31
|
+
description: "Full snapshots of every file Claude edited",
|
|
32
|
+
sensitivity: "critical",
|
|
33
|
+
paths: ["file-history"],
|
|
34
|
+
impactWarning: "Complete file edit history will be deleted. You will LOSE THE ABILITY TO REVERT changes Claude made to your files.",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "shell-snapshots",
|
|
38
|
+
description: "Shell environment variables, PATH, and exports",
|
|
39
|
+
sensitivity: "high",
|
|
40
|
+
paths: ["shell-snapshots"],
|
|
41
|
+
impactWarning: "Shell environment snapshots will be deleted. These may contain PATH info, environment variables, and shell configuration.",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "session-env",
|
|
45
|
+
description: "Per-session environment data",
|
|
46
|
+
sensitivity: "medium",
|
|
47
|
+
paths: ["session-env"],
|
|
48
|
+
impactWarning: "Per-session environment data will be removed. This includes session-specific configuration and state.",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "memory",
|
|
52
|
+
description: "Auto-generated notes about your codebase",
|
|
53
|
+
sensitivity: "high",
|
|
54
|
+
paths: ["projects"],
|
|
55
|
+
pattern: /memory\//,
|
|
56
|
+
impactWarning: "Auto-generated codebase notes and learned patterns will be deleted. Claude will need to re-learn your project structure.",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "history",
|
|
60
|
+
description: "Index of all sessions with timestamps and project paths",
|
|
61
|
+
sensitivity: "medium",
|
|
62
|
+
paths: ["."],
|
|
63
|
+
pattern: /^history\.jsonl$/,
|
|
64
|
+
impactWarning: "Session history index will be deleted. The --resume feature will not be able to find previous sessions.",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "stats",
|
|
68
|
+
description: "Daily activity, token counts, model usage",
|
|
69
|
+
sensitivity: "medium",
|
|
70
|
+
paths: ["."],
|
|
71
|
+
pattern: /^stats-cache\.json$/,
|
|
72
|
+
impactWarning: "Usage statistics will be cleared. Daily activity tracking and token usage history will be lost.",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "todos",
|
|
76
|
+
description: "Task lists from sessions",
|
|
77
|
+
sensitivity: "low",
|
|
78
|
+
paths: ["todos"],
|
|
79
|
+
impactWarning: "Task lists from previous sessions will be deleted.",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "plans",
|
|
83
|
+
description: "Implementation plan files",
|
|
84
|
+
sensitivity: "medium",
|
|
85
|
+
paths: ["plans"],
|
|
86
|
+
impactWarning: "Implementation plans created during planning sessions will be deleted.",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "telemetry",
|
|
90
|
+
description: "Feature flags, stable user IDs, experiment data",
|
|
91
|
+
sensitivity: "medium",
|
|
92
|
+
paths: ["statsig"],
|
|
93
|
+
impactWarning: "Telemetry data including feature flags and experiment information will be removed.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "security-state",
|
|
97
|
+
description: "Trust verification state per project",
|
|
98
|
+
sensitivity: "low",
|
|
99
|
+
paths: ["."],
|
|
100
|
+
pattern: /^security_warnings_state/,
|
|
101
|
+
impactWarning: "Security trust state will be reset. You may need to re-approve projects.",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "ide-locks",
|
|
105
|
+
description: "IDE integration process state",
|
|
106
|
+
sensitivity: "low",
|
|
107
|
+
paths: ["ide"],
|
|
108
|
+
impactWarning: "IDE lock files will be removed. Active IDE connections may be disrupted.",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "agents",
|
|
112
|
+
description: "Custom agent definitions",
|
|
113
|
+
sensitivity: "low",
|
|
114
|
+
paths: ["agents"],
|
|
115
|
+
impactWarning: "Custom agent definitions will be deleted. Your configured agents will need to be recreated.",
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "sessions-index",
|
|
119
|
+
description: "Session metadata, first prompts, timestamps",
|
|
120
|
+
sensitivity: "medium",
|
|
121
|
+
paths: ["projects"],
|
|
122
|
+
pattern: /sessions-index\.json$/,
|
|
123
|
+
impactWarning: "Session index metadata will be deleted. Quick session lookup functionality may be affected.",
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
function formatBytes(bytes) {
|
|
127
|
+
if (bytes < 1024)
|
|
128
|
+
return `${bytes} B`;
|
|
129
|
+
if (bytes < 1024 * 1024)
|
|
130
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
131
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
132
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
133
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
134
|
+
}
|
|
135
|
+
function walkFiles(dir) {
|
|
136
|
+
const files = [];
|
|
137
|
+
function walk(d) {
|
|
138
|
+
try {
|
|
139
|
+
const entries = fs.readdirSync(d, { withFileTypes: true });
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
const full = path.join(d, entry.name);
|
|
142
|
+
if (entry.isDirectory()) {
|
|
143
|
+
walk(full);
|
|
144
|
+
}
|
|
145
|
+
else if (entry.isFile()) {
|
|
146
|
+
try {
|
|
147
|
+
const stat = fs.statSync(full);
|
|
148
|
+
files.push({ path: full, size: stat.size, mtime: stat.mtime });
|
|
149
|
+
}
|
|
150
|
+
catch { /* skip */ }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch { /* skip */ }
|
|
155
|
+
}
|
|
156
|
+
walk(dir);
|
|
157
|
+
return files;
|
|
158
|
+
}
|
|
159
|
+
function categorizeFile(filePath, claudeDir) {
|
|
160
|
+
const relPath = path.relative(claudeDir, filePath);
|
|
161
|
+
for (const cat of TRACE_CATEGORIES) {
|
|
162
|
+
for (const catPath of cat.paths) {
|
|
163
|
+
if (catPath === ".") {
|
|
164
|
+
if (path.dirname(relPath) === "." && (!cat.pattern || cat.pattern.test(path.basename(relPath)))) {
|
|
165
|
+
return cat;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (relPath.startsWith(catPath + path.sep) || relPath.startsWith(catPath + "/")) {
|
|
169
|
+
if (cat.pattern) {
|
|
170
|
+
if (cat.pattern.test(relPath))
|
|
171
|
+
return cat;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
return cat;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
function matchesExclusion(filePath, category, claudeDir, exclusions) {
|
|
182
|
+
if (!exclusions || exclusions.length === 0)
|
|
183
|
+
return null;
|
|
184
|
+
const relPath = path.relative(claudeDir, filePath);
|
|
185
|
+
for (const exc of exclusions) {
|
|
186
|
+
if (exc.type === "category" && exc.value === category) {
|
|
187
|
+
return exc;
|
|
188
|
+
}
|
|
189
|
+
if (exc.type === "project") {
|
|
190
|
+
if (relPath.includes(exc.value) || filePath.includes(exc.value)) {
|
|
191
|
+
return exc;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (exc.type === "path") {
|
|
195
|
+
const pattern = exc.value.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
196
|
+
if (new RegExp(pattern).test(relPath)) {
|
|
197
|
+
return exc;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
function truncatePath(fullPath, maxSegments = 3) {
|
|
204
|
+
const parts = fullPath.split(path.sep);
|
|
205
|
+
if (parts.length <= maxSegments)
|
|
206
|
+
return fullPath;
|
|
207
|
+
return "..." + path.sep + parts.slice(-maxSegments).join(path.sep);
|
|
208
|
+
}
|
|
209
|
+
export function generateEnhancedPreview(claudeDir = CLAUDE_DIR, options) {
|
|
210
|
+
const inventory = inventoryTraces(claudeDir);
|
|
211
|
+
const threshold = options.days ? Date.now() - options.days * 24 * 60 * 60 * 1000 : 0;
|
|
212
|
+
const result = {
|
|
213
|
+
summary: {
|
|
214
|
+
totalFiles: 0,
|
|
215
|
+
totalSize: 0,
|
|
216
|
+
criticalFiles: 0,
|
|
217
|
+
highFiles: 0,
|
|
218
|
+
mediumFiles: 0,
|
|
219
|
+
lowFiles: 0,
|
|
220
|
+
},
|
|
221
|
+
byCategory: [],
|
|
222
|
+
preserved: {
|
|
223
|
+
byExclusion: [],
|
|
224
|
+
settings: true,
|
|
225
|
+
totalPreserved: 0,
|
|
226
|
+
},
|
|
227
|
+
warnings: [],
|
|
228
|
+
};
|
|
229
|
+
const exclusionMatches = new Map();
|
|
230
|
+
const catDefs = new Map();
|
|
231
|
+
for (const def of TRACE_CATEGORIES) {
|
|
232
|
+
catDefs.set(def.name, def);
|
|
233
|
+
}
|
|
234
|
+
for (const category of inventory.categories) {
|
|
235
|
+
if (options.categories && !options.categories.includes(category.name))
|
|
236
|
+
continue;
|
|
237
|
+
if (!options.categories && options.operation === "clean" && (category.name === "agents" || category.name === "ide-locks"))
|
|
238
|
+
continue;
|
|
239
|
+
const catDef = catDefs.get(category.name);
|
|
240
|
+
const samplePaths = [];
|
|
241
|
+
let includedFiles = 0;
|
|
242
|
+
let includedSize = 0;
|
|
243
|
+
for (const item of category.items) {
|
|
244
|
+
if (threshold && item.modified.getTime() > threshold)
|
|
245
|
+
continue;
|
|
246
|
+
const matchedExc = matchesExclusion(item.path, category.name, claudeDir, options.exclusions);
|
|
247
|
+
if (matchedExc) {
|
|
248
|
+
const excKey = matchedExc.id || `${matchedExc.type}:${matchedExc.value}`;
|
|
249
|
+
const existing = exclusionMatches.get(excKey) || { exclusion: matchedExc, files: 0, size: 0 };
|
|
250
|
+
existing.files++;
|
|
251
|
+
existing.size += item.size;
|
|
252
|
+
exclusionMatches.set(excKey, existing);
|
|
253
|
+
result.preserved.totalPreserved++;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
includedFiles++;
|
|
257
|
+
includedSize += item.size;
|
|
258
|
+
if (samplePaths.length < 5) {
|
|
259
|
+
samplePaths.push(truncatePath(item.path));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (includedFiles > 0) {
|
|
263
|
+
result.summary.totalFiles += includedFiles;
|
|
264
|
+
result.summary.totalSize += includedSize;
|
|
265
|
+
if (category.sensitivity === "critical")
|
|
266
|
+
result.summary.criticalFiles += includedFiles;
|
|
267
|
+
else if (category.sensitivity === "high")
|
|
268
|
+
result.summary.highFiles += includedFiles;
|
|
269
|
+
else if (category.sensitivity === "medium")
|
|
270
|
+
result.summary.mediumFiles += includedFiles;
|
|
271
|
+
else
|
|
272
|
+
result.summary.lowFiles += includedFiles;
|
|
273
|
+
result.byCategory.push({
|
|
274
|
+
name: category.name,
|
|
275
|
+
sensitivity: category.sensitivity,
|
|
276
|
+
description: category.description,
|
|
277
|
+
fileCount: includedFiles,
|
|
278
|
+
totalSize: includedSize,
|
|
279
|
+
samplePaths,
|
|
280
|
+
impactWarning: catDef?.impactWarning || category.description,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
result.preserved.byExclusion = Array.from(exclusionMatches.values()).map(m => ({
|
|
285
|
+
exclusion: m.exclusion,
|
|
286
|
+
matchedFiles: m.files,
|
|
287
|
+
matchedSize: m.size,
|
|
288
|
+
}));
|
|
289
|
+
if (result.summary.criticalFiles > 0) {
|
|
290
|
+
result.warnings.push(`⚠ ${result.summary.criticalFiles} critical sensitivity files will be deleted including conversation history and file edit backups.`);
|
|
291
|
+
}
|
|
292
|
+
if (options.operation === "wipe") {
|
|
293
|
+
result.warnings.push("⚠ SECURE WIPE: Files will be overwritten with zeros before deletion. Recovery is NOT possible.");
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
export function inventoryTraces(claudeDir = CLAUDE_DIR, options) {
|
|
298
|
+
const categoryMap = new Map();
|
|
299
|
+
for (const def of TRACE_CATEGORIES) {
|
|
300
|
+
categoryMap.set(def.name, {
|
|
301
|
+
name: def.name,
|
|
302
|
+
description: def.description,
|
|
303
|
+
sensitivity: def.sensitivity,
|
|
304
|
+
items: [],
|
|
305
|
+
totalSize: 0,
|
|
306
|
+
fileCount: 0,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
const allFiles = walkFiles(claudeDir);
|
|
310
|
+
for (const file of allFiles) {
|
|
311
|
+
if (file.path.includes("node_modules"))
|
|
312
|
+
continue;
|
|
313
|
+
if (file.path.includes(path.join("plugins", "marketplaces")))
|
|
314
|
+
continue;
|
|
315
|
+
if (file.path.includes(path.join("local", "node_modules")))
|
|
316
|
+
continue;
|
|
317
|
+
if (options?.project) {
|
|
318
|
+
const relPath = path.relative(claudeDir, file.path);
|
|
319
|
+
if (relPath.startsWith("projects") && !relPath.includes(options.project))
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const catDef = categorizeFile(file.path, claudeDir);
|
|
323
|
+
if (!catDef)
|
|
324
|
+
continue;
|
|
325
|
+
const category = categoryMap.get(catDef.name);
|
|
326
|
+
category.items.push({
|
|
327
|
+
category: catDef.name,
|
|
328
|
+
path: file.path,
|
|
329
|
+
size: file.size,
|
|
330
|
+
modified: file.mtime,
|
|
331
|
+
sensitivity: catDef.sensitivity,
|
|
332
|
+
});
|
|
333
|
+
category.totalSize += file.size;
|
|
334
|
+
category.fileCount++;
|
|
335
|
+
if (!category.oldestFile || file.mtime < category.oldestFile) {
|
|
336
|
+
category.oldestFile = file.mtime;
|
|
337
|
+
}
|
|
338
|
+
if (!category.newestFile || file.mtime > category.newestFile) {
|
|
339
|
+
category.newestFile = file.mtime;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const categories = Array.from(categoryMap.values()).filter(c => c.fileCount > 0);
|
|
343
|
+
const totalSize = categories.reduce((sum, c) => sum + c.totalSize, 0);
|
|
344
|
+
const totalFiles = categories.reduce((sum, c) => sum + c.fileCount, 0);
|
|
345
|
+
const criticalItems = categories.filter(c => c.sensitivity === "critical").reduce((sum, c) => sum + c.fileCount, 0);
|
|
346
|
+
const highItems = categories.filter(c => c.sensitivity === "high").reduce((sum, c) => sum + c.fileCount, 0);
|
|
347
|
+
return {
|
|
348
|
+
totalSize,
|
|
349
|
+
totalFiles,
|
|
350
|
+
categories,
|
|
351
|
+
criticalItems,
|
|
352
|
+
highItems,
|
|
353
|
+
analyzedAt: new Date(),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
export function cleanTraces(claudeDir = CLAUDE_DIR, options = {}) {
|
|
357
|
+
const dryRun = options.dryRun ?? true;
|
|
358
|
+
const preserveSettings = options.preserveSettings ?? true;
|
|
359
|
+
const inventory = inventoryTraces(claudeDir, { project: options.project });
|
|
360
|
+
const threshold = options.days ? Date.now() - options.days * 24 * 60 * 60 * 1000 : 0;
|
|
361
|
+
const result = {
|
|
362
|
+
deleted: [],
|
|
363
|
+
freed: 0,
|
|
364
|
+
errors: [],
|
|
365
|
+
categoriesAffected: [],
|
|
366
|
+
dryRun,
|
|
367
|
+
};
|
|
368
|
+
const preserveFiles = new Set();
|
|
369
|
+
if (preserveSettings) {
|
|
370
|
+
preserveFiles.add(path.join(claudeDir, "settings.json"));
|
|
371
|
+
preserveFiles.add(path.join(claudeDir, "CLAUDE.md"));
|
|
372
|
+
}
|
|
373
|
+
for (const category of inventory.categories) {
|
|
374
|
+
if (options.categories && !options.categories.includes(category.name))
|
|
375
|
+
continue;
|
|
376
|
+
if (!options.categories && (category.name === "agents" || category.name === "ide-locks"))
|
|
377
|
+
continue;
|
|
378
|
+
let affected = false;
|
|
379
|
+
for (const item of category.items) {
|
|
380
|
+
if (preserveFiles.has(item.path))
|
|
381
|
+
continue;
|
|
382
|
+
if (threshold && item.modified.getTime() > threshold)
|
|
383
|
+
continue;
|
|
384
|
+
if (matchesExclusion(item.path, category.name, claudeDir, options.exclusions))
|
|
385
|
+
continue;
|
|
386
|
+
if (dryRun) {
|
|
387
|
+
result.deleted.push(item.path);
|
|
388
|
+
result.freed += item.size;
|
|
389
|
+
affected = true;
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
try {
|
|
393
|
+
fs.unlinkSync(item.path);
|
|
394
|
+
result.deleted.push(item.path);
|
|
395
|
+
result.freed += item.size;
|
|
396
|
+
affected = true;
|
|
397
|
+
}
|
|
398
|
+
catch (e) {
|
|
399
|
+
result.errors.push(`${item.path}: ${e}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (affected)
|
|
404
|
+
result.categoriesAffected.push(category.name);
|
|
405
|
+
}
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
408
|
+
function secureDelete(filePath) {
|
|
409
|
+
try {
|
|
410
|
+
const stat = fs.statSync(filePath);
|
|
411
|
+
const zeros = Buffer.alloc(Math.min(stat.size, 1024 * 1024));
|
|
412
|
+
const fd = fs.openSync(filePath, "w");
|
|
413
|
+
let written = 0;
|
|
414
|
+
while (written < stat.size) {
|
|
415
|
+
const toWrite = Math.min(zeros.length, stat.size - written);
|
|
416
|
+
fs.writeSync(fd, zeros, 0, toWrite);
|
|
417
|
+
written += toWrite;
|
|
418
|
+
}
|
|
419
|
+
fs.closeSync(fd);
|
|
420
|
+
fs.unlinkSync(filePath);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
try {
|
|
424
|
+
fs.unlinkSync(filePath);
|
|
425
|
+
}
|
|
426
|
+
catch { /* skip */ }
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
export function wipeAllTraces(claudeDir = CLAUDE_DIR, options = {}) {
|
|
430
|
+
if (!options.confirm) {
|
|
431
|
+
return {
|
|
432
|
+
filesWiped: 0,
|
|
433
|
+
bytesFreed: 0,
|
|
434
|
+
categoriesWiped: [],
|
|
435
|
+
preserved: ["Wipe not confirmed. Pass confirm: true to execute."],
|
|
436
|
+
wipeReceipt: "",
|
|
437
|
+
completedAt: new Date(),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const inventory = inventoryTraces(claudeDir);
|
|
441
|
+
const result = {
|
|
442
|
+
filesWiped: 0,
|
|
443
|
+
bytesFreed: 0,
|
|
444
|
+
categoriesWiped: [],
|
|
445
|
+
preserved: [],
|
|
446
|
+
wipeReceipt: "",
|
|
447
|
+
completedAt: new Date(),
|
|
448
|
+
};
|
|
449
|
+
const skipPaths = new Set();
|
|
450
|
+
if (options.keepSettings) {
|
|
451
|
+
skipPaths.add(path.join(claudeDir, "settings.json"));
|
|
452
|
+
skipPaths.add(path.join(claudeDir, "CLAUDE.md"));
|
|
453
|
+
result.preserved.push("settings.json", "CLAUDE.md");
|
|
454
|
+
}
|
|
455
|
+
if (options.keepPlugins) {
|
|
456
|
+
result.preserved.push("plugins/");
|
|
457
|
+
}
|
|
458
|
+
const wipedCategories = new Set();
|
|
459
|
+
const receiptLines = [
|
|
460
|
+
`Trace Wipe Receipt`,
|
|
461
|
+
`Date: ${new Date().toISOString()}`,
|
|
462
|
+
`Directory: ${claudeDir}`,
|
|
463
|
+
``,
|
|
464
|
+
];
|
|
465
|
+
let skippedByExclusion = 0;
|
|
466
|
+
for (const category of inventory.categories) {
|
|
467
|
+
if (options.keepPlugins && category.name === "plugins")
|
|
468
|
+
continue;
|
|
469
|
+
let catWiped = 0;
|
|
470
|
+
let catBytes = 0;
|
|
471
|
+
for (const item of category.items) {
|
|
472
|
+
if (skipPaths.has(item.path))
|
|
473
|
+
continue;
|
|
474
|
+
if (matchesExclusion(item.path, category.name, claudeDir, options.exclusions)) {
|
|
475
|
+
skippedByExclusion++;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
secureDelete(item.path);
|
|
479
|
+
result.filesWiped++;
|
|
480
|
+
result.bytesFreed += item.size;
|
|
481
|
+
catWiped++;
|
|
482
|
+
catBytes += item.size;
|
|
483
|
+
}
|
|
484
|
+
if (catWiped > 0) {
|
|
485
|
+
wipedCategories.add(category.name);
|
|
486
|
+
receiptLines.push(`${category.name}: ${catWiped} files (${formatBytes(catBytes)})`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (skippedByExclusion > 0) {
|
|
490
|
+
result.preserved.push(`${skippedByExclusion} files (by exclusion rules)`);
|
|
491
|
+
}
|
|
492
|
+
// Clean up empty directories
|
|
493
|
+
const dirs = ["debug", "todos", "shell-snapshots", "file-history", "session-env", "plans", "statsig", "agents"];
|
|
494
|
+
for (const dir of dirs) {
|
|
495
|
+
const dirPath = path.join(claudeDir, dir);
|
|
496
|
+
try {
|
|
497
|
+
const entries = fs.readdirSync(dirPath);
|
|
498
|
+
if (entries.length === 0)
|
|
499
|
+
continue;
|
|
500
|
+
for (const entry of entries) {
|
|
501
|
+
const full = path.join(dirPath, entry);
|
|
502
|
+
const stat = fs.statSync(full);
|
|
503
|
+
if (stat.isDirectory()) {
|
|
504
|
+
try {
|
|
505
|
+
fs.rmSync(full, { recursive: true });
|
|
506
|
+
}
|
|
507
|
+
catch { /* skip */ }
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch { /* skip */ }
|
|
512
|
+
}
|
|
513
|
+
receiptLines.push(``, `Total: ${result.filesWiped} files, ${formatBytes(result.bytesFreed)}`);
|
|
514
|
+
result.categoriesWiped = Array.from(wipedCategories);
|
|
515
|
+
result.wipeReceipt = receiptLines.join("\n");
|
|
516
|
+
result.completedAt = new Date();
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
export function generateTraceGuardHooks(options) {
|
|
520
|
+
const mode = options?.mode || "moderate";
|
|
521
|
+
const hooks = [];
|
|
522
|
+
if (mode === "paranoid") {
|
|
523
|
+
hooks.push({
|
|
524
|
+
event: "PostToolUse",
|
|
525
|
+
matcher: "Write",
|
|
526
|
+
command: `sh -c 'echo "$TOOL_INPUT" | grep -qi "CLAUDE.md" && exit 2 || exit 0'`,
|
|
527
|
+
description: "Block CLAUDE.md writes",
|
|
528
|
+
});
|
|
529
|
+
hooks.push({
|
|
530
|
+
event: "SessionEnd",
|
|
531
|
+
command: `sh -c 'find ~/.claude/debug -name "*.txt" -mmin +5 -delete 2>/dev/null; find ~/.claude/shell-snapshots -name "*.sh" -delete 2>/dev/null; exit 0'`,
|
|
532
|
+
description: "Delete debug logs and shell snapshots after every session",
|
|
533
|
+
});
|
|
534
|
+
hooks.push({
|
|
535
|
+
event: "SessionEnd",
|
|
536
|
+
command: `sh -c 'find ~/.claude/projects -name "*.jsonl" -not -name "*.backup.*" -delete 2>/dev/null; exit 0'`,
|
|
537
|
+
description: "Delete conversation transcripts after every session",
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
else if (mode === "moderate") {
|
|
541
|
+
hooks.push({
|
|
542
|
+
event: "SessionEnd",
|
|
543
|
+
command: `sh -c 'find ~/.claude/debug -name "*.txt" -mtime +1 -delete 2>/dev/null; find ~/.claude/shell-snapshots -name "*.sh" -mtime +1 -delete 2>/dev/null; exit 0'`,
|
|
544
|
+
description: "Delete debug logs and snapshots older than 24 hours",
|
|
545
|
+
});
|
|
546
|
+
hooks.push({
|
|
547
|
+
event: "SessionEnd",
|
|
548
|
+
command: `sh -c 'find ~/.claude/projects -name "*.jsonl" -not -name "*.backup.*" -mtime +1 -delete 2>/dev/null; exit 0'`,
|
|
549
|
+
description: "Delete conversation transcripts older than 24 hours",
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
hooks.push({
|
|
554
|
+
event: "SessionEnd",
|
|
555
|
+
command: `sh -c 'find ~/.claude/debug -name "*.txt" -mtime +7 -delete 2>/dev/null; find ~/.claude/shell-snapshots -name "*.sh" -mtime +7 -delete 2>/dev/null; exit 0'`,
|
|
556
|
+
description: "Delete debug logs and snapshots older than 7 days",
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
const hooksConfig = {};
|
|
560
|
+
for (const hook of hooks) {
|
|
561
|
+
if (!hooksConfig[hook.event])
|
|
562
|
+
hooksConfig[hook.event] = [];
|
|
563
|
+
const hookDef = {
|
|
564
|
+
type: "command",
|
|
565
|
+
command: hook.command,
|
|
566
|
+
};
|
|
567
|
+
if (hook.matcher) {
|
|
568
|
+
hookDef.matcher = hook.matcher;
|
|
569
|
+
}
|
|
570
|
+
hooksConfig[hook.event].push({
|
|
571
|
+
matcher: hook.matcher || "*",
|
|
572
|
+
hooks: [hookDef],
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
const settingsJson = JSON.stringify({ hooks: hooksConfig }, null, 2);
|
|
576
|
+
let instructions = `Trace Guard Configuration (${mode} mode)\n\n`;
|
|
577
|
+
instructions += `To install, add the following to your ~/.claude/settings.json:\n\n`;
|
|
578
|
+
instructions += settingsJson;
|
|
579
|
+
instructions += `\n\nOr for project-level enforcement, add to .claude/settings.json in your project.\n`;
|
|
580
|
+
instructions += `\nFor managed/enterprise deployment, place in:\n`;
|
|
581
|
+
instructions += ` macOS: /Library/Application Support/ClaudeCode/managed-settings.json\n`;
|
|
582
|
+
instructions += ` Linux: /etc/claude-code/managed-settings.json\n`;
|
|
583
|
+
if (mode === "paranoid") {
|
|
584
|
+
instructions += `\n⚠ PARANOID MODE: All traces deleted after every session. No conversation history will be preserved.\n`;
|
|
585
|
+
}
|
|
586
|
+
else if (mode === "moderate") {
|
|
587
|
+
instructions += `\nMODERATE MODE: Traces older than 24h deleted. Recent session available for --resume.\n`;
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
instructions += `\nMINIMAL MODE: Only old debug/snapshot traces cleaned. Conversations preserved.\n`;
|
|
591
|
+
}
|
|
592
|
+
return {
|
|
593
|
+
mode,
|
|
594
|
+
hooks,
|
|
595
|
+
settingsJson,
|
|
596
|
+
instructions,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
export function formatTraceInventory(inventory) {
|
|
600
|
+
let output = "";
|
|
601
|
+
output += "╔══════════════════════════════════════════════╗\n";
|
|
602
|
+
output += "║ TRACE INVENTORY ║\n";
|
|
603
|
+
output += "╚══════════════════════════════════════════════╝\n\n";
|
|
604
|
+
output += `Total trace data: ${formatBytes(inventory.totalSize)}\n`;
|
|
605
|
+
output += `Total files: ${inventory.totalFiles}\n`;
|
|
606
|
+
output += `Critical sensitivity: ${inventory.criticalItems} files\n`;
|
|
607
|
+
output += `High sensitivity: ${inventory.highItems} files\n\n`;
|
|
608
|
+
output += "Category Sensitivity Files Size\n";
|
|
609
|
+
output += "──────────────────────────────────────────────────\n";
|
|
610
|
+
const sorted = [...inventory.categories].sort((a, b) => b.totalSize - a.totalSize);
|
|
611
|
+
for (const cat of sorted) {
|
|
612
|
+
const name = cat.name.padEnd(19);
|
|
613
|
+
const sens = cat.sensitivity.padEnd(11);
|
|
614
|
+
const files = String(cat.fileCount).padStart(5);
|
|
615
|
+
const size = formatBytes(cat.totalSize).padStart(10);
|
|
616
|
+
output += `${name} ${sens} ${files} ${size}\n`;
|
|
617
|
+
}
|
|
618
|
+
output += "\nCategory Descriptions:\n";
|
|
619
|
+
for (const cat of sorted) {
|
|
620
|
+
output += ` ${cat.name}: ${cat.description}\n`;
|
|
621
|
+
}
|
|
622
|
+
return output;
|
|
623
|
+
}
|
|
624
|
+
export function formatTraceCleanReport(result) {
|
|
625
|
+
let output = "";
|
|
626
|
+
if (result.dryRun) {
|
|
627
|
+
output += "[DRY RUN] Trace cleanup preview:\n\n";
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
output += "Trace Cleanup Results:\n\n";
|
|
631
|
+
}
|
|
632
|
+
output += `Files affected: ${result.deleted.length}\n`;
|
|
633
|
+
output += `Space to free: ${formatBytes(result.freed)}\n`;
|
|
634
|
+
output += `Categories: ${result.categoriesAffected.join(", ") || "none"}\n`;
|
|
635
|
+
if (result.errors.length > 0) {
|
|
636
|
+
output += `\nErrors (${result.errors.length}):\n`;
|
|
637
|
+
for (const err of result.errors.slice(0, 10)) {
|
|
638
|
+
output += ` ✗ ${err}\n`;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (result.dryRun) {
|
|
642
|
+
output += "\nRun without --dry-run to perform cleanup.\n";
|
|
643
|
+
}
|
|
644
|
+
return output;
|
|
645
|
+
}
|
|
646
|
+
export function formatTraceGuardConfig(config) {
|
|
647
|
+
return config.instructions;
|
|
648
|
+
}
|
|
649
|
+
//# sourceMappingURL=trace.js.map
|