@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
package/dist/index.js
CHANGED
|
@@ -10,7 +10,13 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextpro
|
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import * as path from "path";
|
|
12
12
|
import * as os from "os";
|
|
13
|
-
import { findAllJsonlFiles, findBackupFiles, scanFile, fixFile, getConversationStats, restoreFromBackup, deleteOldBackups, } from "./lib/scanner.js";
|
|
13
|
+
import { findAllJsonlFiles, findBackupFiles, scanFile, fixFile, getConversationStats, restoreFromBackup, deleteOldBackups, exportConversation, estimateContextSize, generateUsageAnalytics, findDuplicates, findArchiveCandidates, archiveConversations, runMaintenance, } from "./lib/scanner.js";
|
|
14
|
+
import { analyzeClaudeStorage, cleanClaudeDirectory, } from "./lib/storage.js";
|
|
15
|
+
import { diagnoseMcpServers, } from "./lib/mcp-validator.js";
|
|
16
|
+
import { listSessions, diagnoseSession, repairSession, extractSessionContent, } from "./lib/session-recovery.js";
|
|
17
|
+
import { scanForSecrets, auditSession, enforceRetention, } from "./lib/security.js";
|
|
18
|
+
import { inventoryTraces, cleanTraces, wipeAllTraces, generateTraceGuardHooks, } from "./lib/trace.js";
|
|
19
|
+
import { startDashboard } from "./lib/dashboard.js";
|
|
14
20
|
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
15
21
|
const PROJECTS_DIR = path.join(CLAUDE_DIR, "projects");
|
|
16
22
|
function formatBytes(bytes) {
|
|
@@ -34,7 +40,7 @@ function formatContentType(type) {
|
|
|
34
40
|
}
|
|
35
41
|
const server = new Server({
|
|
36
42
|
name: "claude-code-toolkit",
|
|
37
|
-
version: "1.0
|
|
43
|
+
version: "1.2.0",
|
|
38
44
|
}, {
|
|
39
45
|
capabilities: {
|
|
40
46
|
tools: {},
|
|
@@ -148,6 +154,225 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
148
154
|
properties: {},
|
|
149
155
|
},
|
|
150
156
|
},
|
|
157
|
+
{
|
|
158
|
+
name: "export_conversation",
|
|
159
|
+
description: "Export a Claude Code conversation to markdown or JSON format for backup or sharing.",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
path: {
|
|
164
|
+
type: "string",
|
|
165
|
+
description: "The path to the conversation file to export (required)",
|
|
166
|
+
},
|
|
167
|
+
format: {
|
|
168
|
+
type: "string",
|
|
169
|
+
enum: ["markdown", "json"],
|
|
170
|
+
description: "Export format: markdown or json. Default: markdown",
|
|
171
|
+
},
|
|
172
|
+
include_tool_results: {
|
|
173
|
+
type: "boolean",
|
|
174
|
+
description: "Include tool results in the export. Default: false",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
required: ["path"],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "estimate_context_size",
|
|
182
|
+
description: "Estimate the context/token usage of a Claude Code conversation. Shows breakdown by message type, images, documents, and tool usage.",
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties: {
|
|
186
|
+
path: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "Path to a specific conversation file. If omitted, shows summary of all conversations sorted by context size.",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "usage_analytics",
|
|
195
|
+
description: "Generate a usage analytics dashboard showing conversation statistics, activity trends, top projects, tool usage breakdown, and media stats.",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
days: {
|
|
200
|
+
type: "number",
|
|
201
|
+
description: "Number of days to include in the analysis. Default: 30",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "find_duplicates",
|
|
208
|
+
description: "Scan for duplicate content across Claude Code conversations. Finds duplicate conversations, images, and documents that waste storage and context space.",
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: {},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: "archive_conversations",
|
|
216
|
+
description: "Archive old/inactive conversations to free up space. Moves conversations that haven't been modified in a specified number of days to an archive directory.",
|
|
217
|
+
inputSchema: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: {
|
|
220
|
+
days: {
|
|
221
|
+
type: "number",
|
|
222
|
+
description: "Archive conversations inactive for this many days. Default: 30",
|
|
223
|
+
},
|
|
224
|
+
dry_run: {
|
|
225
|
+
type: "boolean",
|
|
226
|
+
description: "Preview what would be archived without making changes. Default: true",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "run_maintenance",
|
|
233
|
+
description: "Run maintenance checks on Claude Code installation. Identifies issues, old backups, and archive candidates. Can optionally perform fixes automatically.",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: {
|
|
237
|
+
auto: {
|
|
238
|
+
type: "boolean",
|
|
239
|
+
description: "Automatically perform maintenance actions. Default: false (dry run)",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "clean_claude_directory",
|
|
246
|
+
description: "Analyze and clean the .claude directory. Removes debug logs, empty todos, old snapshots, and orphaned data to free disk space.",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
dry_run: { type: "boolean", description: "Preview without deleting. Default: true" },
|
|
251
|
+
days: { type: "number", description: "Age threshold in days. Default: 7" },
|
|
252
|
+
category: { type: "string", description: "Clean specific category: debug, todos, shell-snapshots, file-history, session-env, cache" },
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "validate_mcp_config",
|
|
258
|
+
description: "Validate MCP server configurations. Checks JSON syntax, command existence, and optionally tests server connectivity.",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
test: { type: "boolean", description: "Test server connectivity (spawns processes with 5s timeout). Default: false" },
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "list_sessions",
|
|
268
|
+
description: "List all Claude Code sessions with health status. Shows session ID, project, size, message count, and whether the session is healthy, corrupted, empty, or orphaned.",
|
|
269
|
+
inputSchema: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
project: { type: "string", description: "Filter by project path" },
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "recover_session",
|
|
278
|
+
description: "Diagnose, repair, or extract content from a Claude Code session. Use for corrupted or crashed sessions.",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
session_id: { type: "string", description: "Session ID (or prefix) to recover" },
|
|
283
|
+
repair: { type: "boolean", description: "Attempt to repair by removing invalid lines. Default: false" },
|
|
284
|
+
extract: { type: "boolean", description: "Extract salvageable content (messages, edits, commands). Default: false" },
|
|
285
|
+
},
|
|
286
|
+
required: ["session_id"],
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "security_scan",
|
|
291
|
+
description: "Scan conversation files for leaked secrets (AWS keys, API tokens, passwords, private keys, connection strings, JWTs).",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
file: { type: "string", description: "Scan a specific file. Default: all conversation files" },
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "audit_session",
|
|
301
|
+
description: "Generate an audit report for a session showing all files read/written, commands executed, MCP tools used, and URLs fetched.",
|
|
302
|
+
inputSchema: {
|
|
303
|
+
type: "object",
|
|
304
|
+
properties: {
|
|
305
|
+
session_id: { type: "string", description: "Session ID (or prefix) to audit" },
|
|
306
|
+
},
|
|
307
|
+
required: ["session_id"],
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "enforce_retention",
|
|
312
|
+
description: "Apply data retention policy by deleting sessions older than specified days.",
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: "object",
|
|
315
|
+
properties: {
|
|
316
|
+
days: { type: "number", description: "Delete sessions older than this many days. Default: 30" },
|
|
317
|
+
dry_run: { type: "boolean", description: "Preview without deleting. Default: true" },
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "inventory_traces",
|
|
323
|
+
description: "Show complete inventory of all traces Claude Code has stored on disk, categorized by sensitivity level (critical/high/medium/low).",
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
project: { type: "string", description: "Filter by project path" },
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "clean_traces",
|
|
333
|
+
description: "Selectively clean Claude Code traces by category, age, or project.",
|
|
334
|
+
inputSchema: {
|
|
335
|
+
type: "object",
|
|
336
|
+
properties: {
|
|
337
|
+
categories: { type: "string", description: "Comma-separated categories to clean" },
|
|
338
|
+
days: { type: "number", description: "Only clean traces older than this many days" },
|
|
339
|
+
project: { type: "string", description: "Only clean traces for specific project" },
|
|
340
|
+
dry_run: { type: "boolean", description: "Preview without deleting. Default: true" },
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "wipe_traces",
|
|
346
|
+
description: "Securely wipe ALL Claude Code traces. Overwrites files with zeros before deletion. Requires explicit confirmation.",
|
|
347
|
+
inputSchema: {
|
|
348
|
+
type: "object",
|
|
349
|
+
properties: {
|
|
350
|
+
confirm: { type: "boolean", description: "Must be true to execute wipe" },
|
|
351
|
+
keep_settings: { type: "boolean", description: "Preserve settings.json and CLAUDE.md. Default: false" },
|
|
352
|
+
},
|
|
353
|
+
required: ["confirm"],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: "generate_trace_guard",
|
|
358
|
+
description: "Generate hook configurations for ongoing trace prevention. Outputs JSON ready to add to settings.",
|
|
359
|
+
inputSchema: {
|
|
360
|
+
type: "object",
|
|
361
|
+
properties: {
|
|
362
|
+
mode: { type: "string", description: "Guard mode: paranoid (delete everything), moderate (24h retention), minimal (7-day cleanup). Default: moderate" },
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "start_dashboard",
|
|
368
|
+
description: "Start the web dashboard on a local port. Returns the URL to open in a browser.",
|
|
369
|
+
inputSchema: {
|
|
370
|
+
type: "object",
|
|
371
|
+
properties: {
|
|
372
|
+
port: { type: "number", description: "Port number to listen on. Default: 1405" },
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
},
|
|
151
376
|
],
|
|
152
377
|
};
|
|
153
378
|
});
|
|
@@ -515,6 +740,728 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
515
740
|
}
|
|
516
741
|
return { content: [{ type: "text", text: output }] };
|
|
517
742
|
}
|
|
743
|
+
case "export_conversation": {
|
|
744
|
+
const targetPath = typedArgs.path;
|
|
745
|
+
const format = typedArgs.format || "markdown";
|
|
746
|
+
const includeToolResults = typedArgs.include_tool_results || false;
|
|
747
|
+
if (!targetPath) {
|
|
748
|
+
return {
|
|
749
|
+
content: [{ type: "text", text: "Error: path is required. Please specify the conversation file to export." }],
|
|
750
|
+
isError: true,
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
if (!fs.existsSync(targetPath)) {
|
|
754
|
+
return {
|
|
755
|
+
content: [{ type: "text", text: `Error: File not found: ${targetPath}` }],
|
|
756
|
+
isError: true,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
const result = exportConversation(targetPath, {
|
|
761
|
+
format,
|
|
762
|
+
includeToolResults,
|
|
763
|
+
includeTimestamps: true,
|
|
764
|
+
});
|
|
765
|
+
let output = `📤 **Conversation Exported**\n\n`;
|
|
766
|
+
output += `- Source: \`${path.relative(PROJECTS_DIR, targetPath)}\`\n`;
|
|
767
|
+
output += `- Format: ${format}\n`;
|
|
768
|
+
output += `- Messages: ${result.messageCount}\n\n`;
|
|
769
|
+
output += `---\n\n`;
|
|
770
|
+
output += result.content;
|
|
771
|
+
return { content: [{ type: "text", text: output }] };
|
|
772
|
+
}
|
|
773
|
+
catch (e) {
|
|
774
|
+
return {
|
|
775
|
+
content: [{ type: "text", text: `Error exporting conversation: ${e instanceof Error ? e.message : String(e)}` }],
|
|
776
|
+
isError: true,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
case "estimate_context_size": {
|
|
781
|
+
const targetPath = typedArgs.path;
|
|
782
|
+
if (!fs.existsSync(PROJECTS_DIR)) {
|
|
783
|
+
return {
|
|
784
|
+
content: [{ type: "text", text: `Claude projects directory not found: ${PROJECTS_DIR}` }],
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
if (targetPath) {
|
|
788
|
+
if (!fs.existsSync(targetPath)) {
|
|
789
|
+
return {
|
|
790
|
+
content: [{ type: "text", text: `Error: File not found: ${targetPath}` }],
|
|
791
|
+
isError: true,
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
const estimate = estimateContextSize(targetPath);
|
|
795
|
+
const b = estimate.breakdown;
|
|
796
|
+
let output = `📏 **Context Size Estimate**\n\n`;
|
|
797
|
+
output += `**File:** \`${path.relative(PROJECTS_DIR, targetPath)}\`\n\n`;
|
|
798
|
+
output += `**Total:** ~${estimate.totalTokens.toLocaleString()} tokens\n`;
|
|
799
|
+
output += `**Messages:** ${estimate.messageCount}\n\n`;
|
|
800
|
+
output += `**Breakdown:**\n`;
|
|
801
|
+
output += `| Category | Tokens |\n`;
|
|
802
|
+
output += `|----------|--------|\n`;
|
|
803
|
+
output += `| User messages | ${b.userTokens.toLocaleString()} |\n`;
|
|
804
|
+
output += `| Assistant messages | ${b.assistantTokens.toLocaleString()} |\n`;
|
|
805
|
+
if (b.systemTokens > 0) {
|
|
806
|
+
output += `| System messages | ${b.systemTokens.toLocaleString()} |\n`;
|
|
807
|
+
}
|
|
808
|
+
output += `| Tool calls | ${b.toolUseTokens.toLocaleString()} |\n`;
|
|
809
|
+
output += `| Tool results | ${b.toolResultTokens.toLocaleString()} |\n`;
|
|
810
|
+
if (b.imageTokens > 0) {
|
|
811
|
+
output += `| Images | ${b.imageTokens.toLocaleString()} |\n`;
|
|
812
|
+
}
|
|
813
|
+
if (b.documentTokens > 0) {
|
|
814
|
+
output += `| Documents | ${b.documentTokens.toLocaleString()} |\n`;
|
|
815
|
+
}
|
|
816
|
+
if (estimate.largestMessage) {
|
|
817
|
+
output += `\n**Largest message:** Line ${estimate.largestMessage.line} (${estimate.largestMessage.role})\n`;
|
|
818
|
+
output += `~${estimate.largestMessage.tokens.toLocaleString()} tokens\n`;
|
|
819
|
+
}
|
|
820
|
+
if (estimate.warnings.length > 0) {
|
|
821
|
+
output += `\n**Warnings:**\n`;
|
|
822
|
+
for (const warning of estimate.warnings) {
|
|
823
|
+
output += `- ⚠️ ${warning}\n`;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return { content: [{ type: "text", text: output }] };
|
|
827
|
+
}
|
|
828
|
+
// Summary of all conversations
|
|
829
|
+
const files = findAllJsonlFiles(PROJECTS_DIR);
|
|
830
|
+
const estimates = [];
|
|
831
|
+
for (const file of files) {
|
|
832
|
+
try {
|
|
833
|
+
estimates.push(estimateContextSize(file));
|
|
834
|
+
}
|
|
835
|
+
catch {
|
|
836
|
+
// Skip
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
estimates.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
840
|
+
const totalTokens = estimates.reduce((sum, e) => sum + e.totalTokens, 0);
|
|
841
|
+
const displayed = estimates.slice(0, 10);
|
|
842
|
+
let output = `📏 **Context Usage Summary**\n\n`;
|
|
843
|
+
output += `**Total conversations:** ${estimates.length}\n`;
|
|
844
|
+
output += `**Combined tokens:** ~${totalTokens.toLocaleString()}\n\n`;
|
|
845
|
+
output += `**Top 10 by context size:**\n\n`;
|
|
846
|
+
for (const estimate of displayed) {
|
|
847
|
+
const relPath = path.relative(PROJECTS_DIR, estimate.file);
|
|
848
|
+
const shortPath = relPath.length > 45 ? "..." + relPath.slice(-42) : relPath;
|
|
849
|
+
output += `### ${shortPath}\n`;
|
|
850
|
+
output += `- ~${estimate.totalTokens.toLocaleString()} tokens (${estimate.messageCount} messages)\n`;
|
|
851
|
+
if (estimate.warnings.length > 0) {
|
|
852
|
+
output += `- ⚠️ ${estimate.warnings[0]}\n`;
|
|
853
|
+
}
|
|
854
|
+
output += `\n`;
|
|
855
|
+
}
|
|
856
|
+
return { content: [{ type: "text", text: output }] };
|
|
857
|
+
}
|
|
858
|
+
case "usage_analytics": {
|
|
859
|
+
const days = typedArgs.days || 30;
|
|
860
|
+
if (!fs.existsSync(PROJECTS_DIR)) {
|
|
861
|
+
return {
|
|
862
|
+
content: [{ type: "text", text: `Claude projects directory not found: ${PROJECTS_DIR}` }],
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
const analytics = generateUsageAnalytics(PROJECTS_DIR, days);
|
|
866
|
+
const o = analytics.overview;
|
|
867
|
+
let output = `📊 **Usage Analytics Dashboard**\n\n`;
|
|
868
|
+
output += `## Overview\n`;
|
|
869
|
+
output += `| Metric | Value |\n`;
|
|
870
|
+
output += `|--------|-------|\n`;
|
|
871
|
+
output += `| Conversations | ${o.totalConversations.toLocaleString()} |\n`;
|
|
872
|
+
output += `| Total Messages | ${o.totalMessages.toLocaleString()} |\n`;
|
|
873
|
+
output += `| Total Tokens | ~${o.totalTokens.toLocaleString()} |\n`;
|
|
874
|
+
output += `| Total Size | ${formatBytes(o.totalSize)} |\n`;
|
|
875
|
+
output += `| Active Projects | ${o.activeProjects} |\n`;
|
|
876
|
+
output += `| Avg Messages/Conv | ${o.avgMessagesPerConversation} |\n`;
|
|
877
|
+
output += `| Avg Tokens/Conv | ~${o.avgTokensPerConversation.toLocaleString()} |\n`;
|
|
878
|
+
output += `\n`;
|
|
879
|
+
// Activity (last 7 days)
|
|
880
|
+
const last7Days = analytics.dailyActivity.slice(-7);
|
|
881
|
+
output += `## Activity (Last 7 days)\n`;
|
|
882
|
+
output += `| Day | Messages |\n`;
|
|
883
|
+
output += `|-----|----------|\n`;
|
|
884
|
+
for (const day of last7Days) {
|
|
885
|
+
const dayName = new Date(day.date).toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
|
|
886
|
+
output += `| ${dayName} | ${day.messages} |\n`;
|
|
887
|
+
}
|
|
888
|
+
output += `\n`;
|
|
889
|
+
// Top projects
|
|
890
|
+
if (analytics.topProjects.length > 0) {
|
|
891
|
+
output += `## Top Projects\n`;
|
|
892
|
+
output += `| Project | Messages | Tokens |\n`;
|
|
893
|
+
output += `|---------|----------|--------|\n`;
|
|
894
|
+
for (const proj of analytics.topProjects.slice(0, 5)) {
|
|
895
|
+
const shortName = proj.project.length > 30 ? "..." + proj.project.slice(-27) : proj.project;
|
|
896
|
+
output += `| ${shortName} | ${proj.messages.toLocaleString()} | ~${proj.tokens.toLocaleString()} |\n`;
|
|
897
|
+
}
|
|
898
|
+
output += `\n`;
|
|
899
|
+
}
|
|
900
|
+
// Top tools
|
|
901
|
+
if (analytics.toolUsage.length > 0) {
|
|
902
|
+
output += `## Top Tools\n`;
|
|
903
|
+
output += `| Tool | Count | % |\n`;
|
|
904
|
+
output += `|------|-------|---|\n`;
|
|
905
|
+
for (const tool of analytics.toolUsage.slice(0, 8)) {
|
|
906
|
+
output += `| ${tool.name} | ${tool.count.toLocaleString()} | ${tool.percentage}% |\n`;
|
|
907
|
+
}
|
|
908
|
+
output += `\n`;
|
|
909
|
+
}
|
|
910
|
+
// Media stats
|
|
911
|
+
const m = analytics.mediaStats;
|
|
912
|
+
output += `## Media\n`;
|
|
913
|
+
output += `- Images: ${m.totalImages}\n`;
|
|
914
|
+
output += `- Documents: ${m.totalDocuments}\n`;
|
|
915
|
+
if (m.problematicContent > 0) {
|
|
916
|
+
output += `- ⚠️ Oversized: ${m.problematicContent}\n`;
|
|
917
|
+
}
|
|
918
|
+
return { content: [{ type: "text", text: output }] };
|
|
919
|
+
}
|
|
920
|
+
case "find_duplicates": {
|
|
921
|
+
if (!fs.existsSync(PROJECTS_DIR)) {
|
|
922
|
+
return {
|
|
923
|
+
content: [{ type: "text", text: `Claude projects directory not found: ${PROJECTS_DIR}` }],
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
const report = findDuplicates(PROJECTS_DIR);
|
|
927
|
+
let output = `🔍 **Duplicate Detection Report**\n\n`;
|
|
928
|
+
output += `## Summary\n`;
|
|
929
|
+
output += `| Metric | Value |\n`;
|
|
930
|
+
output += `|--------|-------|\n`;
|
|
931
|
+
output += `| Duplicate groups | ${report.totalDuplicateGroups} |\n`;
|
|
932
|
+
output += `| Duplicate images | ${report.summary.duplicateImages} |\n`;
|
|
933
|
+
output += `| Duplicate documents | ${report.summary.duplicateDocuments} |\n`;
|
|
934
|
+
output += `| Wasted space | ${formatBytes(report.totalWastedSize)} |\n`;
|
|
935
|
+
output += `\n`;
|
|
936
|
+
if (report.conversationDuplicates.length > 0) {
|
|
937
|
+
output += `## Duplicate Conversations\n`;
|
|
938
|
+
for (const group of report.conversationDuplicates.slice(0, 5)) {
|
|
939
|
+
output += `### ${group.locations.length} copies (~${formatBytes(group.wastedSize)} wasted)\n`;
|
|
940
|
+
for (const loc of group.locations.slice(0, 3)) {
|
|
941
|
+
const shortPath = path.relative(PROJECTS_DIR, loc.file);
|
|
942
|
+
output += `- \`${shortPath}\`\n`;
|
|
943
|
+
}
|
|
944
|
+
if (group.locations.length > 3) {
|
|
945
|
+
output += `- ... and ${group.locations.length - 3} more\n`;
|
|
946
|
+
}
|
|
947
|
+
output += `\n`;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (report.contentDuplicates.length > 0) {
|
|
951
|
+
output += `## Duplicate Content\n`;
|
|
952
|
+
output += `| Type | Copies | Wasted |\n`;
|
|
953
|
+
output += `|------|--------|--------|\n`;
|
|
954
|
+
for (const group of report.contentDuplicates.slice(0, 10)) {
|
|
955
|
+
const typeIcon = group.contentType === "image" ? "🖼️" : "📄";
|
|
956
|
+
output += `| ${typeIcon} ${group.contentType} | ${group.locations.length} | ${formatBytes(group.wastedSize)} |\n`;
|
|
957
|
+
}
|
|
958
|
+
output += `\n`;
|
|
959
|
+
}
|
|
960
|
+
if (report.totalDuplicateGroups === 0) {
|
|
961
|
+
output += `✓ No duplicates found!\n`;
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
output += `**Recommendations:**\n`;
|
|
965
|
+
if (report.conversationDuplicates.length > 0) {
|
|
966
|
+
output += `- Review duplicate conversations and consider removing copies\n`;
|
|
967
|
+
}
|
|
968
|
+
if (report.summary.duplicateImages > 0 || report.summary.duplicateDocuments > 0) {
|
|
969
|
+
output += `- Same content appears multiple times across conversations\n`;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return { content: [{ type: "text", text: output }] };
|
|
973
|
+
}
|
|
974
|
+
case "archive_conversations": {
|
|
975
|
+
const days = typedArgs.days || 30;
|
|
976
|
+
const dryRun = typedArgs.dry_run !== false;
|
|
977
|
+
if (!fs.existsSync(PROJECTS_DIR)) {
|
|
978
|
+
return {
|
|
979
|
+
content: [{ type: "text", text: `Claude projects directory not found: ${PROJECTS_DIR}` }],
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
const candidates = findArchiveCandidates(PROJECTS_DIR, { minDaysInactive: days });
|
|
983
|
+
if (candidates.length === 0) {
|
|
984
|
+
return {
|
|
985
|
+
content: [{ type: "text", text: `✓ No conversations eligible for archiving (inactive ${days}+ days).` }],
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
const result = archiveConversations(PROJECTS_DIR, { minDaysInactive: days, dryRun });
|
|
989
|
+
const totalSize = candidates.reduce((sum, c) => sum + c.sizeBytes, 0);
|
|
990
|
+
let output = `📦 **Archive ${dryRun ? "Preview" : "Complete"}**\n\n`;
|
|
991
|
+
output += `| Metric | Value |\n`;
|
|
992
|
+
output += `|--------|-------|\n`;
|
|
993
|
+
output += `| Conversations | ${candidates.length} |\n`;
|
|
994
|
+
output += `| Total size | ${formatBytes(totalSize)} |\n`;
|
|
995
|
+
output += `| Days inactive | ${days}+ |\n`;
|
|
996
|
+
output += `\n`;
|
|
997
|
+
output += `**Conversations:**\n`;
|
|
998
|
+
for (const c of candidates.slice(0, 10)) {
|
|
999
|
+
const shortPath = path.relative(PROJECTS_DIR, c.file);
|
|
1000
|
+
output += `- \`${shortPath}\` (${c.daysSinceActivity} days, ${formatBytes(c.sizeBytes)})\n`;
|
|
1001
|
+
}
|
|
1002
|
+
if (candidates.length > 10) {
|
|
1003
|
+
output += `- ... and ${candidates.length - 10} more\n`;
|
|
1004
|
+
}
|
|
1005
|
+
output += `\n`;
|
|
1006
|
+
if (dryRun) {
|
|
1007
|
+
output += `ℹ️ This is a preview. Set \`dry_run: false\` to actually archive.\n`;
|
|
1008
|
+
}
|
|
1009
|
+
else {
|
|
1010
|
+
output += `✓ Archived to: \`${result.archiveDir}\`\n`;
|
|
1011
|
+
}
|
|
1012
|
+
return { content: [{ type: "text", text: output }] };
|
|
1013
|
+
}
|
|
1014
|
+
case "run_maintenance": {
|
|
1015
|
+
const auto = typedArgs.auto === true;
|
|
1016
|
+
if (!fs.existsSync(PROJECTS_DIR)) {
|
|
1017
|
+
return {
|
|
1018
|
+
content: [{ type: "text", text: `Claude projects directory not found: ${PROJECTS_DIR}` }],
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
const report = runMaintenance(PROJECTS_DIR, { dryRun: !auto });
|
|
1022
|
+
const statusIcon = report.status === "healthy" ? "✓" : report.status === "critical" ? "✗" : "⚠️";
|
|
1023
|
+
const statusText = report.status === "healthy" ? "Healthy" : report.status === "critical" ? "Critical" : "Needs Attention";
|
|
1024
|
+
let output = `🔧 **Maintenance Report**\n\n`;
|
|
1025
|
+
output += `**Status:** ${statusIcon} ${statusText}\n\n`;
|
|
1026
|
+
if (report.actions.length > 0) {
|
|
1027
|
+
output += `## ${auto ? "Actions Taken" : "Pending Actions"}\n`;
|
|
1028
|
+
output += `| Type | Description | Count |\n`;
|
|
1029
|
+
output += `|------|-------------|-------|\n`;
|
|
1030
|
+
for (const action of report.actions) {
|
|
1031
|
+
const icon = action.type === "fix" ? "🔧" : action.type === "cleanup" ? "🗑️" : "📦";
|
|
1032
|
+
output += `| ${icon} ${action.type} | ${action.description} | ${action.count} |\n`;
|
|
1033
|
+
}
|
|
1034
|
+
output += `\n`;
|
|
1035
|
+
}
|
|
1036
|
+
if (report.recommendations.length > 0) {
|
|
1037
|
+
output += `## Recommendations\n`;
|
|
1038
|
+
for (const rec of report.recommendations) {
|
|
1039
|
+
output += `- ${rec}\n`;
|
|
1040
|
+
}
|
|
1041
|
+
output += `\n`;
|
|
1042
|
+
}
|
|
1043
|
+
if (report.actions.length === 0 && report.recommendations.length === 0) {
|
|
1044
|
+
output += `✓ Everything looks good! No maintenance needed.\n`;
|
|
1045
|
+
}
|
|
1046
|
+
else if (!auto) {
|
|
1047
|
+
output += `\nℹ️ Set \`auto: true\` to perform maintenance actions automatically.\n`;
|
|
1048
|
+
}
|
|
1049
|
+
return { content: [{ type: "text", text: output }] };
|
|
1050
|
+
}
|
|
1051
|
+
case "clean_claude_directory": {
|
|
1052
|
+
const dryRun = typedArgs.dry_run !== false;
|
|
1053
|
+
const days = typedArgs.days || 7;
|
|
1054
|
+
const category = typedArgs.category;
|
|
1055
|
+
const analysis = analyzeClaudeStorage();
|
|
1056
|
+
const result = cleanClaudeDirectory(undefined, {
|
|
1057
|
+
dryRun,
|
|
1058
|
+
days,
|
|
1059
|
+
categories: category ? [category] : undefined,
|
|
1060
|
+
});
|
|
1061
|
+
const totalFiles = analysis.categories.reduce((sum, c) => sum + c.fileCount, 0);
|
|
1062
|
+
let output = `🧹 **Directory ${dryRun ? "Analysis" : "Cleanup"}**\n\n`;
|
|
1063
|
+
output += `## Storage Analysis\n`;
|
|
1064
|
+
output += `| Category | Size | Files |\n`;
|
|
1065
|
+
output += `|----------|------|-------|\n`;
|
|
1066
|
+
for (const cat of analysis.categories) {
|
|
1067
|
+
output += `| ${cat.name} | ${formatBytes(cat.totalSize)} | ${cat.fileCount} |\n`;
|
|
1068
|
+
}
|
|
1069
|
+
output += `| **Total** | **${formatBytes(analysis.totalSize)}** | **${totalFiles}** |\n\n`;
|
|
1070
|
+
if (result.deleted.length > 0) {
|
|
1071
|
+
output += `## ${dryRun ? "Cleanable Items" : "Cleaned Items"}\n`;
|
|
1072
|
+
output += `- Files: ${result.deleted.length}\n`;
|
|
1073
|
+
output += `- Space ${dryRun ? "freeable" : "freed"}: ${formatBytes(result.freed)}\n\n`;
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
output += `✓ Nothing to clean.\n\n`;
|
|
1077
|
+
}
|
|
1078
|
+
if (dryRun && result.deleted.length > 0) {
|
|
1079
|
+
output += `ℹ️ Set \`dry_run: false\` to actually clean.\n`;
|
|
1080
|
+
}
|
|
1081
|
+
if (result.errors.length > 0) {
|
|
1082
|
+
output += `\n⚠️ Errors:\n`;
|
|
1083
|
+
for (const err of result.errors) {
|
|
1084
|
+
output += `- ${err}\n`;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return { content: [{ type: "text", text: output }] };
|
|
1088
|
+
}
|
|
1089
|
+
case "validate_mcp_config": {
|
|
1090
|
+
const test = typedArgs.test === true;
|
|
1091
|
+
const report = await diagnoseMcpServers({ test });
|
|
1092
|
+
let output = `🔌 **MCP Config Validation**\n\n`;
|
|
1093
|
+
output += `| Metric | Value |\n`;
|
|
1094
|
+
output += `|--------|-------|\n`;
|
|
1095
|
+
output += `| Config files | ${report.configs.length} |\n`;
|
|
1096
|
+
output += `| Total servers | ${report.totalServers} |\n`;
|
|
1097
|
+
output += `| Healthy | ${report.healthyServers} |\n\n`;
|
|
1098
|
+
for (const config of report.configs) {
|
|
1099
|
+
output += `### ${config.configPath}\n`;
|
|
1100
|
+
for (const server of config.servers) {
|
|
1101
|
+
const hasErrors = config.issues.some(i => i.server === server.name && i.severity === "error");
|
|
1102
|
+
output += `- **${server.name}** \`${server.command}\` ${hasErrors ? "✗" : "✓"}\n`;
|
|
1103
|
+
}
|
|
1104
|
+
if (config.issues.length > 0) {
|
|
1105
|
+
output += `\n**Issues:**\n`;
|
|
1106
|
+
for (const issue of config.issues) {
|
|
1107
|
+
const icon = issue.severity === "error" ? "✗" : issue.severity === "warning" ? "⚠️" : "ℹ️";
|
|
1108
|
+
output += `- ${icon} [${issue.server}] ${issue.message}\n`;
|
|
1109
|
+
if (issue.fix)
|
|
1110
|
+
output += ` Fix: ${issue.fix}\n`;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
output += `\n`;
|
|
1114
|
+
}
|
|
1115
|
+
if (report.testResults) {
|
|
1116
|
+
output += `## Connectivity Tests\n`;
|
|
1117
|
+
output += `| Server | Status | Time |\n`;
|
|
1118
|
+
output += `|--------|--------|------|\n`;
|
|
1119
|
+
for (const t of report.testResults) {
|
|
1120
|
+
const status = t.reachable ? "✓ reachable" : "✗ unreachable";
|
|
1121
|
+
const time = t.startupTime ? `${t.startupTime}ms` : "-";
|
|
1122
|
+
output += `| ${t.server} | ${status} | ${time} |\n`;
|
|
1123
|
+
}
|
|
1124
|
+
output += `\n`;
|
|
1125
|
+
}
|
|
1126
|
+
if (report.duplicateServers.length > 0) {
|
|
1127
|
+
output += `## Duplicates\n`;
|
|
1128
|
+
for (const dup of report.duplicateServers) {
|
|
1129
|
+
output += `- **${dup.name}** found in ${dup.locations.length} configs\n`;
|
|
1130
|
+
}
|
|
1131
|
+
output += `\n`;
|
|
1132
|
+
}
|
|
1133
|
+
if (report.recommendations.length > 0) {
|
|
1134
|
+
output += `## Recommendations\n`;
|
|
1135
|
+
for (const rec of report.recommendations) {
|
|
1136
|
+
output += `- ${rec}\n`;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return { content: [{ type: "text", text: output }] };
|
|
1140
|
+
}
|
|
1141
|
+
case "list_sessions": {
|
|
1142
|
+
const project = typedArgs.project;
|
|
1143
|
+
const sessions = listSessions(undefined, { project });
|
|
1144
|
+
if (sessions.length === 0) {
|
|
1145
|
+
return { content: [{ type: "text", text: "No sessions found." }] };
|
|
1146
|
+
}
|
|
1147
|
+
let output = `📋 **Sessions** (${sessions.length})\n\n`;
|
|
1148
|
+
output += `| ID | Status | Messages | Size | Project |\n`;
|
|
1149
|
+
output += `|----|--------|----------|------|--------|\n`;
|
|
1150
|
+
for (const s of sessions.slice(0, 50)) {
|
|
1151
|
+
const shortId = s.id.slice(0, 8);
|
|
1152
|
+
const icon = s.status === "healthy" ? "✓" : s.status === "corrupted" ? "✗" : s.status === "empty" ? "○" : "?";
|
|
1153
|
+
const shortProject = s.project ? (s.project.length > 25 ? "..." + s.project.slice(-22) : s.project) : "-";
|
|
1154
|
+
output += `| ${shortId} | ${icon} ${s.status} | ${s.messageCount} | ${formatBytes(s.sizeBytes)} | ${shortProject} |\n`;
|
|
1155
|
+
}
|
|
1156
|
+
if (sessions.length > 50) {
|
|
1157
|
+
output += `\n... and ${sessions.length - 50} more sessions\n`;
|
|
1158
|
+
}
|
|
1159
|
+
const healthy = sessions.filter(s => s.status === "healthy").length;
|
|
1160
|
+
const corrupted = sessions.filter(s => s.status === "corrupted").length;
|
|
1161
|
+
const orphaned = sessions.filter(s => s.status === "orphaned").length;
|
|
1162
|
+
output += `\n**Summary:** ${healthy} healthy, ${corrupted} corrupted, ${orphaned} orphaned\n`;
|
|
1163
|
+
return { content: [{ type: "text", text: output }] };
|
|
1164
|
+
}
|
|
1165
|
+
case "recover_session": {
|
|
1166
|
+
const sessionId = typedArgs.session_id;
|
|
1167
|
+
const repair = typedArgs.repair === true;
|
|
1168
|
+
const extract = typedArgs.extract === true;
|
|
1169
|
+
const sessions = listSessions();
|
|
1170
|
+
const match = sessions.find(s => s.id === sessionId || s.id.startsWith(sessionId));
|
|
1171
|
+
if (!match) {
|
|
1172
|
+
return {
|
|
1173
|
+
content: [{ type: "text", text: `Session not found: ${sessionId}` }],
|
|
1174
|
+
isError: true,
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
const diagnosis = diagnoseSession(match.filePath);
|
|
1178
|
+
let output = `🔍 **Session Recovery: ${match.id.slice(0, 8)}**\n\n`;
|
|
1179
|
+
output += `- Path: \`${match.filePath}\`\n`;
|
|
1180
|
+
output += `- Status: ${diagnosis.issues.length === 0 ? "✓ Healthy" : "✗ Issues found"}\n`;
|
|
1181
|
+
output += `- Total lines: ${diagnosis.totalLines}\n`;
|
|
1182
|
+
output += `- Valid lines: ${diagnosis.validLines}\n`;
|
|
1183
|
+
output += `- Corrupted lines: ${diagnosis.corruptedLines}\n\n`;
|
|
1184
|
+
if (diagnosis.issues.length > 0) {
|
|
1185
|
+
output += `**Issues:**\n`;
|
|
1186
|
+
for (const issue of diagnosis.issues.slice(0, 20)) {
|
|
1187
|
+
output += `- Line ${issue.line}: ${issue.type} - ${issue.message}\n`;
|
|
1188
|
+
}
|
|
1189
|
+
if (diagnosis.issues.length > 20) {
|
|
1190
|
+
output += `- ... and ${diagnosis.issues.length - 20} more\n`;
|
|
1191
|
+
}
|
|
1192
|
+
output += `\n`;
|
|
1193
|
+
}
|
|
1194
|
+
if (repair) {
|
|
1195
|
+
const repairResult = repairSession(match.filePath);
|
|
1196
|
+
output += `## Repair\n`;
|
|
1197
|
+
output += `- Lines removed: ${repairResult.linesRemoved}\n`;
|
|
1198
|
+
output += `- Lines fixed: ${repairResult.linesFixed}\n`;
|
|
1199
|
+
if (repairResult.backupPath) {
|
|
1200
|
+
output += `- Backup: \`${repairResult.backupPath}\`\n`;
|
|
1201
|
+
}
|
|
1202
|
+
output += `\n`;
|
|
1203
|
+
}
|
|
1204
|
+
if (extract) {
|
|
1205
|
+
const content = extractSessionContent(match.filePath);
|
|
1206
|
+
output += `## Extracted Content\n`;
|
|
1207
|
+
output += `- User messages: ${content.userMessages.length}\n`;
|
|
1208
|
+
output += `- Assistant messages: ${content.assistantMessages.length}\n`;
|
|
1209
|
+
output += `- File edits: ${content.fileEdits.length}\n`;
|
|
1210
|
+
output += `- Commands: ${content.commandsRun.length}\n\n`;
|
|
1211
|
+
if (content.userMessages.length > 0) {
|
|
1212
|
+
output += `**User Messages (first 5):**\n`;
|
|
1213
|
+
for (const msg of content.userMessages.slice(0, 5)) {
|
|
1214
|
+
const preview = msg.length > 100 ? msg.slice(0, 100) + "..." : msg;
|
|
1215
|
+
output += `- ${preview}\n`;
|
|
1216
|
+
}
|
|
1217
|
+
output += `\n`;
|
|
1218
|
+
}
|
|
1219
|
+
if (content.fileEdits.length > 0) {
|
|
1220
|
+
output += `**Files Modified:**\n`;
|
|
1221
|
+
for (const f of content.fileEdits.slice(0, 10)) {
|
|
1222
|
+
output += `- \`${f.path}\`\n`;
|
|
1223
|
+
}
|
|
1224
|
+
output += `\n`;
|
|
1225
|
+
}
|
|
1226
|
+
if (content.commandsRun.length > 0) {
|
|
1227
|
+
output += `**Commands Run:**\n`;
|
|
1228
|
+
for (const cmd of content.commandsRun.slice(0, 10)) {
|
|
1229
|
+
output += `- \`${cmd}\`\n`;
|
|
1230
|
+
}
|
|
1231
|
+
output += `\n`;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return { content: [{ type: "text", text: output }] };
|
|
1235
|
+
}
|
|
1236
|
+
case "security_scan": {
|
|
1237
|
+
const file = typedArgs.file;
|
|
1238
|
+
const result = scanForSecrets(undefined, { file });
|
|
1239
|
+
if (result.totalFindings === 0) {
|
|
1240
|
+
return {
|
|
1241
|
+
content: [{ type: "text", text: `✓ Scanned ${result.filesScanned} file(s). No secrets found.` }],
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
let output = `🔐 **Security Scan**\n\n`;
|
|
1245
|
+
output += `| Metric | Value |\n`;
|
|
1246
|
+
output += `|--------|-------|\n`;
|
|
1247
|
+
output += `| Files scanned | ${result.filesScanned} |\n`;
|
|
1248
|
+
output += `| Findings | ${result.totalFindings} |\n\n`;
|
|
1249
|
+
output += `## Findings by Type\n`;
|
|
1250
|
+
output += `| Type | Count |\n`;
|
|
1251
|
+
output += `|------|-------|\n`;
|
|
1252
|
+
for (const [type, count] of Object.entries(result.summary)) {
|
|
1253
|
+
output += `| ${type} | ${count} |\n`;
|
|
1254
|
+
}
|
|
1255
|
+
output += `\n`;
|
|
1256
|
+
output += `## Details\n`;
|
|
1257
|
+
for (const finding of result.findings.slice(0, 25)) {
|
|
1258
|
+
const icon = finding.severity === "critical" ? "🔴" : finding.severity === "high" ? "🟠" : "🟡";
|
|
1259
|
+
const shortFile = path.basename(finding.file);
|
|
1260
|
+
output += `- ${icon} **${finding.pattern}** [${finding.severity}]\n`;
|
|
1261
|
+
output += ` File: \`${shortFile}\` Line: ${finding.line}\n`;
|
|
1262
|
+
output += ` Preview: \`${finding.maskedPreview}\`\n`;
|
|
1263
|
+
}
|
|
1264
|
+
if (result.findings.length > 25) {
|
|
1265
|
+
output += `\n... and ${result.findings.length - 25} more findings\n`;
|
|
1266
|
+
}
|
|
1267
|
+
return { content: [{ type: "text", text: output }] };
|
|
1268
|
+
}
|
|
1269
|
+
case "audit_session": {
|
|
1270
|
+
const sessionId = typedArgs.session_id;
|
|
1271
|
+
const sessions = listSessions();
|
|
1272
|
+
const match = sessions.find(s => s.id === sessionId || s.id.startsWith(sessionId));
|
|
1273
|
+
if (!match) {
|
|
1274
|
+
return {
|
|
1275
|
+
content: [{ type: "text", text: `Session not found: ${sessionId}` }],
|
|
1276
|
+
isError: true,
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
const audit = auditSession(match.filePath);
|
|
1280
|
+
let output = `📝 **Session Audit: ${match.id.slice(0, 8)}**\n\n`;
|
|
1281
|
+
output += `- Total actions: ${audit.actions.length}\n`;
|
|
1282
|
+
if (audit.duration)
|
|
1283
|
+
output += `- Duration: ${audit.duration} minutes\n`;
|
|
1284
|
+
output += `\n`;
|
|
1285
|
+
if (audit.filesRead.length > 0) {
|
|
1286
|
+
output += `## Files Read (${audit.filesRead.length})\n`;
|
|
1287
|
+
for (const f of audit.filesRead.slice(0, 20)) {
|
|
1288
|
+
output += `- \`${f}\`\n`;
|
|
1289
|
+
}
|
|
1290
|
+
if (audit.filesRead.length > 20)
|
|
1291
|
+
output += `- ... and ${audit.filesRead.length - 20} more\n`;
|
|
1292
|
+
output += `\n`;
|
|
1293
|
+
}
|
|
1294
|
+
if (audit.filesWritten.length > 0) {
|
|
1295
|
+
output += `## Files Written (${audit.filesWritten.length})\n`;
|
|
1296
|
+
for (const f of audit.filesWritten.slice(0, 20)) {
|
|
1297
|
+
output += `- \`${f}\`\n`;
|
|
1298
|
+
}
|
|
1299
|
+
if (audit.filesWritten.length > 20)
|
|
1300
|
+
output += `- ... and ${audit.filesWritten.length - 20} more\n`;
|
|
1301
|
+
output += `\n`;
|
|
1302
|
+
}
|
|
1303
|
+
if (audit.commandsRun.length > 0) {
|
|
1304
|
+
output += `## Commands (${audit.commandsRun.length})\n`;
|
|
1305
|
+
for (const cmd of audit.commandsRun.slice(0, 20)) {
|
|
1306
|
+
output += `- \`${cmd}\`\n`;
|
|
1307
|
+
}
|
|
1308
|
+
if (audit.commandsRun.length > 20)
|
|
1309
|
+
output += `- ... and ${audit.commandsRun.length - 20} more\n`;
|
|
1310
|
+
output += `\n`;
|
|
1311
|
+
}
|
|
1312
|
+
if (audit.mcpToolsUsed.length > 0) {
|
|
1313
|
+
output += `## MCP Tools (${audit.mcpToolsUsed.length})\n`;
|
|
1314
|
+
for (const tool of audit.mcpToolsUsed) {
|
|
1315
|
+
output += `- \`${tool}\`\n`;
|
|
1316
|
+
}
|
|
1317
|
+
output += `\n`;
|
|
1318
|
+
}
|
|
1319
|
+
if (audit.urlsFetched.length > 0) {
|
|
1320
|
+
output += `## URLs Fetched (${audit.urlsFetched.length})\n`;
|
|
1321
|
+
for (const url of audit.urlsFetched.slice(0, 10)) {
|
|
1322
|
+
output += `- ${url}\n`;
|
|
1323
|
+
}
|
|
1324
|
+
output += `\n`;
|
|
1325
|
+
}
|
|
1326
|
+
return { content: [{ type: "text", text: output }] };
|
|
1327
|
+
}
|
|
1328
|
+
case "enforce_retention": {
|
|
1329
|
+
const days = typedArgs.days || 30;
|
|
1330
|
+
const dryRun = typedArgs.dry_run !== false;
|
|
1331
|
+
const result = enforceRetention(undefined, { days, dryRun });
|
|
1332
|
+
let output = `🗑️ **Retention ${dryRun ? "Preview" : "Enforced"}**\n\n`;
|
|
1333
|
+
output += `| Metric | Value |\n`;
|
|
1334
|
+
output += `|--------|-------|\n`;
|
|
1335
|
+
output += `| Sessions ${dryRun ? "eligible" : "deleted"} | ${result.sessionsDeleted} |\n`;
|
|
1336
|
+
output += `| Space ${dryRun ? "freeable" : "freed"} | ${formatBytes(result.spaceFreed)} |\n`;
|
|
1337
|
+
output += `| Threshold | ${days} days |\n\n`;
|
|
1338
|
+
if (result.sessionsDeleted === 0) {
|
|
1339
|
+
output += `✓ No sessions older than ${days} days.\n`;
|
|
1340
|
+
}
|
|
1341
|
+
else if (dryRun) {
|
|
1342
|
+
output += `ℹ️ Set \`dry_run: false\` to actually delete.\n`;
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
output += `✓ ${result.sessionsDeleted} session(s) deleted.\n`;
|
|
1346
|
+
}
|
|
1347
|
+
if (result.errors.length > 0) {
|
|
1348
|
+
output += `\n⚠️ Errors:\n`;
|
|
1349
|
+
for (const err of result.errors) {
|
|
1350
|
+
output += `- ${err}\n`;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return { content: [{ type: "text", text: output }] };
|
|
1354
|
+
}
|
|
1355
|
+
case "inventory_traces": {
|
|
1356
|
+
const project = typedArgs.project;
|
|
1357
|
+
const inventory = inventoryTraces(undefined, { project });
|
|
1358
|
+
let output = `🔎 **Trace Inventory**\n\n`;
|
|
1359
|
+
output += `| Metric | Value |\n`;
|
|
1360
|
+
output += `|--------|-------|\n`;
|
|
1361
|
+
output += `| Total files | ${inventory.totalFiles} |\n`;
|
|
1362
|
+
output += `| Total size | ${formatBytes(inventory.totalSize)} |\n`;
|
|
1363
|
+
output += `| Critical items | ${inventory.criticalItems} |\n`;
|
|
1364
|
+
output += `| High items | ${inventory.highItems} |\n\n`;
|
|
1365
|
+
output += `## Categories\n`;
|
|
1366
|
+
output += `| Category | Sensitivity | Files | Size |\n`;
|
|
1367
|
+
output += `|----------|-------------|-------|------|\n`;
|
|
1368
|
+
for (const cat of inventory.categories) {
|
|
1369
|
+
if (cat.fileCount === 0)
|
|
1370
|
+
continue;
|
|
1371
|
+
const icon = cat.sensitivity === "critical" ? "🔴" : cat.sensitivity === "high" ? "🟠" : cat.sensitivity === "medium" ? "🟡" : "🟢";
|
|
1372
|
+
output += `| ${icon} ${cat.name} | ${cat.sensitivity} | ${cat.fileCount} | ${formatBytes(cat.totalSize)} |\n`;
|
|
1373
|
+
}
|
|
1374
|
+
output += `\n`;
|
|
1375
|
+
if (inventory.criticalItems > 0) {
|
|
1376
|
+
output += `⚠️ ${inventory.criticalItems} critical trace items found. Consider running \`clean_traces\` or \`wipe_traces\`.\n`;
|
|
1377
|
+
}
|
|
1378
|
+
return { content: [{ type: "text", text: output }] };
|
|
1379
|
+
}
|
|
1380
|
+
case "clean_traces": {
|
|
1381
|
+
const categories = typedArgs.categories;
|
|
1382
|
+
const days = typedArgs.days;
|
|
1383
|
+
const project = typedArgs.project;
|
|
1384
|
+
const dryRun = typedArgs.dry_run !== false;
|
|
1385
|
+
const result = cleanTraces(undefined, {
|
|
1386
|
+
categories: categories ? categories.split(",").map(c => c.trim()) : undefined,
|
|
1387
|
+
days,
|
|
1388
|
+
project,
|
|
1389
|
+
dryRun,
|
|
1390
|
+
});
|
|
1391
|
+
let output = `🧹 **Trace ${dryRun ? "Cleanup Preview" : "Cleanup Complete"}**\n\n`;
|
|
1392
|
+
output += `| Metric | Value |\n`;
|
|
1393
|
+
output += `|--------|-------|\n`;
|
|
1394
|
+
output += `| Files ${dryRun ? "to delete" : "deleted"} | ${result.deleted.length} |\n`;
|
|
1395
|
+
output += `| Space ${dryRun ? "freeable" : "freed"} | ${formatBytes(result.freed)} |\n`;
|
|
1396
|
+
output += `| Categories | ${result.categoriesAffected.join(", ") || "none"} |\n\n`;
|
|
1397
|
+
if (result.deleted.length === 0) {
|
|
1398
|
+
output += `✓ Nothing to clean.\n`;
|
|
1399
|
+
}
|
|
1400
|
+
else if (dryRun) {
|
|
1401
|
+
output += `ℹ️ Set \`dry_run: false\` to actually delete.\n`;
|
|
1402
|
+
}
|
|
1403
|
+
else {
|
|
1404
|
+
output += `✓ Cleanup complete.\n`;
|
|
1405
|
+
}
|
|
1406
|
+
if (result.errors.length > 0) {
|
|
1407
|
+
output += `\n⚠️ Errors:\n`;
|
|
1408
|
+
for (const err of result.errors) {
|
|
1409
|
+
output += `- ${err}\n`;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return { content: [{ type: "text", text: output }] };
|
|
1413
|
+
}
|
|
1414
|
+
case "wipe_traces": {
|
|
1415
|
+
const confirm = typedArgs.confirm === true;
|
|
1416
|
+
const keepSettings = typedArgs.keep_settings === true;
|
|
1417
|
+
if (!confirm) {
|
|
1418
|
+
return {
|
|
1419
|
+
content: [{ type: "text", text: "⚠️ **Wipe requires explicit confirmation.** Set `confirm: true` to securely wipe all Claude Code traces. This action is irreversible." }],
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
const result = wipeAllTraces(undefined, {
|
|
1423
|
+
confirm: true,
|
|
1424
|
+
keepSettings,
|
|
1425
|
+
});
|
|
1426
|
+
let output = `💀 **Trace Wipe Complete**\n\n`;
|
|
1427
|
+
output += `| Metric | Value |\n`;
|
|
1428
|
+
output += `|--------|-------|\n`;
|
|
1429
|
+
output += `| Files wiped | ${result.filesWiped} |\n`;
|
|
1430
|
+
output += `| Space freed | ${formatBytes(result.bytesFreed)} |\n`;
|
|
1431
|
+
output += `| Categories | ${result.categoriesWiped.join(", ")} |\n\n`;
|
|
1432
|
+
if (result.preserved.length > 0) {
|
|
1433
|
+
output += `**Preserved:** ${result.preserved.join(", ")}\n\n`;
|
|
1434
|
+
}
|
|
1435
|
+
output += `✓ All traces securely wiped.\n`;
|
|
1436
|
+
return { content: [{ type: "text", text: output }] };
|
|
1437
|
+
}
|
|
1438
|
+
case "generate_trace_guard": {
|
|
1439
|
+
const mode = typedArgs.mode || "moderate";
|
|
1440
|
+
const config = generateTraceGuardHooks({ mode });
|
|
1441
|
+
let output = `🛡️ **Trace Guard Configuration (${mode} mode)**\n\n`;
|
|
1442
|
+
output += `## Hooks\n`;
|
|
1443
|
+
for (const hook of config.hooks) {
|
|
1444
|
+
output += `### ${hook.event}\n`;
|
|
1445
|
+
output += `- ${hook.description}\n`;
|
|
1446
|
+
output += `- Command: \`${hook.command}\`\n`;
|
|
1447
|
+
if (hook.matcher)
|
|
1448
|
+
output += `- Matcher: \`${hook.matcher}\`\n`;
|
|
1449
|
+
output += `\n`;
|
|
1450
|
+
}
|
|
1451
|
+
output += `## Installation\n`;
|
|
1452
|
+
output += `Add this to your \`~/.claude/settings.json\`:\n\n`;
|
|
1453
|
+
output += `\`\`\`json\n${config.settingsJson}\n\`\`\`\n\n`;
|
|
1454
|
+
output += config.instructions + `\n`;
|
|
1455
|
+
return { content: [{ type: "text", text: output }] };
|
|
1456
|
+
}
|
|
1457
|
+
case "start_dashboard": {
|
|
1458
|
+
const port = typedArgs.port || 1405;
|
|
1459
|
+
await startDashboard({ port, open: false });
|
|
1460
|
+
const url = `http://localhost:${port}`;
|
|
1461
|
+
return {
|
|
1462
|
+
content: [{ type: "text", text: `Dashboard started at ${url}\n\nOpen this URL in your browser to view the dashboard.` }],
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
518
1465
|
default:
|
|
519
1466
|
throw new Error(`Unknown tool: ${name}`);
|
|
520
1467
|
}
|
|
@@ -529,7 +1476,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
529
1476
|
async function main() {
|
|
530
1477
|
const transport = new StdioServerTransport();
|
|
531
1478
|
await server.connect(transport);
|
|
532
|
-
console.error("Claude Code Toolkit MCP Server v1.0
|
|
1479
|
+
console.error("Claude Code Toolkit MCP Server v1.2.0 running on stdio");
|
|
533
1480
|
}
|
|
534
1481
|
main().catch(console.error);
|
|
535
1482
|
//# sourceMappingURL=index.js.map
|