claude-sessions-mcp 0.2.2 → 0.4.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 +25 -78
- package/dist/index.js +308 -0
- package/dist/index.js.map +1 -0
- package/package.json +14 -64
- package/dist/chunk-7MUU7A32.js +0 -47
- package/dist/chunk-7MUU7A32.js.map +0 -1
- package/dist/mcp/index.js +0 -673
- package/dist/mcp/index.js.map +0 -1
- package/dist/server.d.ts +0 -10
- package/dist/server.js +0 -9
- package/dist/server.js.map +0 -1
- package/dist/web/client/_app/immutable/assets/0.B2Ed6Mpr.css +0 -1
- package/dist/web/client/_app/immutable/assets/0.B2Ed6Mpr.css.br +0 -0
- package/dist/web/client/_app/immutable/assets/0.B2Ed6Mpr.css.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/ChUNEGSF.js +0 -1
- package/dist/web/client/_app/immutable/chunks/ChUNEGSF.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/ChUNEGSF.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/CkYrZ5uP.js +0 -1
- package/dist/web/client/_app/immutable/chunks/CkYrZ5uP.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/CkYrZ5uP.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/DMIwXBU2.js +0 -1
- package/dist/web/client/_app/immutable/chunks/DMIwXBU2.js.br +0 -1
- package/dist/web/client/_app/immutable/chunks/DMIwXBU2.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/DhpCfsq_.js +0 -2
- package/dist/web/client/_app/immutable/chunks/DhpCfsq_.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/DhpCfsq_.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/JuVRPOYA.js +0 -1
- package/dist/web/client/_app/immutable/chunks/JuVRPOYA.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/JuVRPOYA.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/qEz027d4.js +0 -1
- package/dist/web/client/_app/immutable/chunks/qEz027d4.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/qEz027d4.js.gz +0 -0
- package/dist/web/client/_app/immutable/entry/app.BtK9g-Sl.js +0 -2
- package/dist/web/client/_app/immutable/entry/app.BtK9g-Sl.js.br +0 -0
- package/dist/web/client/_app/immutable/entry/app.BtK9g-Sl.js.gz +0 -0
- package/dist/web/client/_app/immutable/entry/start.D-1GJX6_.js +0 -1
- package/dist/web/client/_app/immutable/entry/start.D-1GJX6_.js.br +0 -2
- package/dist/web/client/_app/immutable/entry/start.D-1GJX6_.js.gz +0 -0
- package/dist/web/client/_app/immutable/nodes/0.D-CLOlPL.js +0 -1
- package/dist/web/client/_app/immutable/nodes/0.D-CLOlPL.js.br +0 -0
- package/dist/web/client/_app/immutable/nodes/0.D-CLOlPL.js.gz +0 -0
- package/dist/web/client/_app/immutable/nodes/1.ypm2nJK5.js +0 -1
- package/dist/web/client/_app/immutable/nodes/1.ypm2nJK5.js.br +0 -0
- package/dist/web/client/_app/immutable/nodes/1.ypm2nJK5.js.gz +0 -0
- package/dist/web/client/_app/immutable/nodes/2.CNmGqqiX.js +0 -71
- package/dist/web/client/_app/immutable/nodes/2.CNmGqqiX.js.br +0 -0
- package/dist/web/client/_app/immutable/nodes/2.CNmGqqiX.js.gz +0 -0
- package/dist/web/client/_app/version.json +0 -1
- package/dist/web/client/_app/version.json.br +0 -1
- package/dist/web/client/_app/version.json.gz +0 -0
- package/dist/web/client/favicon.png +0 -0
- package/dist/web/env.js +0 -45
- package/dist/web/handler.js +0 -1390
- package/dist/web/index.js +0 -334
- package/dist/web/server/chunks/0-CSg0FjrW.js +0 -17
- package/dist/web/server/chunks/0-CSg0FjrW.js.map +0 -1
- package/dist/web/server/chunks/1-DITZbj6v.js +0 -9
- package/dist/web/server/chunks/1-DITZbj6v.js.map +0 -1
- package/dist/web/server/chunks/2-DruheCUS.js +0 -9
- package/dist/web/server/chunks/2-DruheCUS.js.map +0 -1
- package/dist/web/server/chunks/_layout.svelte-g4K2eFdi.js +0 -29
- package/dist/web/server/chunks/_layout.svelte-g4K2eFdi.js.map +0 -1
- package/dist/web/server/chunks/_page.svelte-Bu5w4fE4.js +0 -105
- package/dist/web/server/chunks/_page.svelte-Bu5w4fE4.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-9dSpOSZD.js +0 -28
- package/dist/web/server/chunks/_server.ts-9dSpOSZD.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-BoS0ijI9.js +0 -19
- package/dist/web/server/chunks/_server.ts-BoS0ijI9.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-Bw_uJ6TN.js +0 -11
- package/dist/web/server/chunks/_server.ts-Bw_uJ6TN.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-CHX8x48n.js +0 -27
- package/dist/web/server/chunks/_server.ts-CHX8x48n.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-CUFgemWt.js +0 -8
- package/dist/web/server/chunks/_server.ts-CUFgemWt.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-DCZgqkA_.js +0 -37
- package/dist/web/server/chunks/_server.ts-DCZgqkA_.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-DNSYjrWg.js +0 -18
- package/dist/web/server/chunks/_server.ts-DNSYjrWg.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-DmMLJ93T.js +0 -18
- package/dist/web/server/chunks/_server.ts-DmMLJ93T.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-Dq5i1Bg9.js +0 -18
- package/dist/web/server/chunks/_server.ts-Dq5i1Bg9.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-XEsSs7Il.js +0 -14
- package/dist/web/server/chunks/_server.ts-XEsSs7Il.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-uDQ4gQW0.js +0 -19
- package/dist/web/server/chunks/_server.ts-uDQ4gQW0.js.map +0 -1
- package/dist/web/server/chunks/_server.ts-zkRNh3d2.js +0 -26
- package/dist/web/server/chunks/_server.ts-zkRNh3d2.js.map +0 -1
- package/dist/web/server/chunks/context-R2425nfV.js +0 -64
- package/dist/web/server/chunks/context-R2425nfV.js.map +0 -1
- package/dist/web/server/chunks/error.svelte-DazOwnSn.js +0 -45
- package/dist/web/server/chunks/error.svelte-DazOwnSn.js.map +0 -1
- package/dist/web/server/chunks/exports-BzHwARwz.js +0 -326
- package/dist/web/server/chunks/exports-BzHwARwz.js.map +0 -1
- package/dist/web/server/chunks/index-CXFDTUAl.js +0 -913
- package/dist/web/server/chunks/index-CXFDTUAl.js.map +0 -1
- package/dist/web/server/chunks/index-CoD1IJuy.js +0 -190
- package/dist/web/server/chunks/index-CoD1IJuy.js.map +0 -1
- package/dist/web/server/chunks/session-BAE596_s.js +0 -414
- package/dist/web/server/chunks/session-BAE596_s.js.map +0 -1
- package/dist/web/server/index.js +0 -7938
- package/dist/web/server/index.js.map +0 -1
- package/dist/web/server/manifest.js +0 -130
- package/dist/web/server/manifest.js.map +0 -1
- package/dist/web/shims.js +0 -32
- /package/dist/{mcp/index.d.ts → index.d.ts} +0 -0
package/dist/mcp/index.js
DELETED
|
@@ -1,673 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
startWebServer,
|
|
4
|
-
stopWebServer
|
|
5
|
-
} from "../chunk-7MUU7A32.js";
|
|
6
|
-
|
|
7
|
-
// src/mcp/index.ts
|
|
8
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
-
import { Effect as Effect2 } from "effect";
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
|
|
13
|
-
// src/lib/session.ts
|
|
14
|
-
import { Effect, pipe, Array as A, Option as O } from "effect";
|
|
15
|
-
import * as fs from "fs/promises";
|
|
16
|
-
import * as path from "path";
|
|
17
|
-
import * as os from "os";
|
|
18
|
-
var getSessionsDir = () => path.join(os.homedir(), ".claude", "projects");
|
|
19
|
-
var listProjects = Effect.gen(function* () {
|
|
20
|
-
const sessionsDir = getSessionsDir();
|
|
21
|
-
const exists = yield* Effect.tryPromise(
|
|
22
|
-
() => fs.access(sessionsDir).then(() => true).catch(() => false)
|
|
23
|
-
);
|
|
24
|
-
if (!exists) {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
const entries = yield* Effect.tryPromise(() => fs.readdir(sessionsDir, { withFileTypes: true }));
|
|
28
|
-
const projects = yield* Effect.all(
|
|
29
|
-
entries.filter((e) => e.isDirectory()).map(
|
|
30
|
-
(entry) => Effect.gen(function* () {
|
|
31
|
-
const projectPath = path.join(sessionsDir, entry.name);
|
|
32
|
-
const files = yield* Effect.tryPromise(() => fs.readdir(projectPath));
|
|
33
|
-
const sessionFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
34
|
-
return {
|
|
35
|
-
name: entry.name,
|
|
36
|
-
path: projectPath,
|
|
37
|
-
sessionCount: sessionFiles.length
|
|
38
|
-
};
|
|
39
|
-
})
|
|
40
|
-
),
|
|
41
|
-
{ concurrency: 10 }
|
|
42
|
-
);
|
|
43
|
-
return projects;
|
|
44
|
-
});
|
|
45
|
-
var listSessions = (projectName) => Effect.gen(function* () {
|
|
46
|
-
const projectPath = path.join(getSessionsDir(), projectName);
|
|
47
|
-
const files = yield* Effect.tryPromise(() => fs.readdir(projectPath));
|
|
48
|
-
const sessionFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
49
|
-
const sessions = yield* Effect.all(
|
|
50
|
-
sessionFiles.map(
|
|
51
|
-
(file) => Effect.gen(function* () {
|
|
52
|
-
const filePath = path.join(projectPath, file);
|
|
53
|
-
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
54
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
55
|
-
const messages = lines.map((line) => JSON.parse(line));
|
|
56
|
-
const sessionId = file.replace(".jsonl", "");
|
|
57
|
-
const firstMessage = messages[0];
|
|
58
|
-
const lastMessage = messages[messages.length - 1];
|
|
59
|
-
const title = pipe(
|
|
60
|
-
messages,
|
|
61
|
-
A.findFirst((m) => m.type === "human"),
|
|
62
|
-
O.map((m) => {
|
|
63
|
-
const msg = m.message;
|
|
64
|
-
const content2 = msg?.content ?? "";
|
|
65
|
-
return content2.slice(0, 50) + (content2.length > 50 ? "..." : "");
|
|
66
|
-
}),
|
|
67
|
-
O.getOrElse(() => "Untitled")
|
|
68
|
-
);
|
|
69
|
-
return {
|
|
70
|
-
id: sessionId,
|
|
71
|
-
projectName,
|
|
72
|
-
title,
|
|
73
|
-
messageCount: messages.length,
|
|
74
|
-
createdAt: firstMessage?.timestamp,
|
|
75
|
-
updatedAt: lastMessage?.timestamp
|
|
76
|
-
};
|
|
77
|
-
})
|
|
78
|
-
),
|
|
79
|
-
{ concurrency: 10 }
|
|
80
|
-
);
|
|
81
|
-
return sessions;
|
|
82
|
-
});
|
|
83
|
-
var readSession = (projectName, sessionId) => Effect.gen(function* () {
|
|
84
|
-
const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
85
|
-
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
86
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
87
|
-
return lines.map((line) => JSON.parse(line));
|
|
88
|
-
});
|
|
89
|
-
var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
|
|
90
|
-
const projectPath = path.join(getSessionsDir(), projectName);
|
|
91
|
-
const files = yield* Effect.tryPromise(() => fs.readdir(projectPath));
|
|
92
|
-
const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
93
|
-
const linkedAgents = [];
|
|
94
|
-
for (const agentFile of agentFiles) {
|
|
95
|
-
const filePath = path.join(projectPath, agentFile);
|
|
96
|
-
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
97
|
-
const firstLine = content.split("\n")[0];
|
|
98
|
-
if (firstLine) {
|
|
99
|
-
try {
|
|
100
|
-
const parsed = JSON.parse(firstLine);
|
|
101
|
-
if (parsed.sessionId === sessionId) {
|
|
102
|
-
linkedAgents.push(agentFile.replace(".jsonl", ""));
|
|
103
|
-
}
|
|
104
|
-
} catch {
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return linkedAgents;
|
|
109
|
-
});
|
|
110
|
-
var deleteSession = (projectName, sessionId) => Effect.gen(function* () {
|
|
111
|
-
const sessionsDir = getSessionsDir();
|
|
112
|
-
const projectPath = path.join(sessionsDir, projectName);
|
|
113
|
-
const filePath = path.join(projectPath, `${sessionId}.jsonl`);
|
|
114
|
-
const backupDir = path.join(projectPath, ".bak");
|
|
115
|
-
yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }));
|
|
116
|
-
const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
|
|
117
|
-
const deletedAgents = [];
|
|
118
|
-
for (const agentId of linkedAgents) {
|
|
119
|
-
const agentPath = path.join(projectPath, `${agentId}.jsonl`);
|
|
120
|
-
const agentBackupPath = path.join(backupDir, `${agentId}.jsonl`);
|
|
121
|
-
yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath));
|
|
122
|
-
deletedAgents.push(agentId);
|
|
123
|
-
}
|
|
124
|
-
const backupPath = path.join(backupDir, `${sessionId}.jsonl`);
|
|
125
|
-
yield* Effect.tryPromise(() => fs.rename(filePath, backupPath));
|
|
126
|
-
return { success: true, backupPath, deletedAgents };
|
|
127
|
-
});
|
|
128
|
-
var renameSession = (projectName, sessionId, newTitle) => Effect.gen(function* () {
|
|
129
|
-
const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
130
|
-
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
131
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
132
|
-
if (lines.length === 0) {
|
|
133
|
-
return { success: false, error: "Empty session" };
|
|
134
|
-
}
|
|
135
|
-
const messages = lines.map((line) => JSON.parse(line));
|
|
136
|
-
const firstMsg = messages[0];
|
|
137
|
-
if (firstMsg && typeof firstMsg.message === "object" && firstMsg.message !== null) {
|
|
138
|
-
const msg = firstMsg.message;
|
|
139
|
-
if (msg.content) {
|
|
140
|
-
const cleanContent = msg.content.replace(/^\[.*?\]\s*/, "");
|
|
141
|
-
msg.content = `[${newTitle}] ${cleanContent}`;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
145
|
-
yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, "utf-8"));
|
|
146
|
-
return { success: true };
|
|
147
|
-
});
|
|
148
|
-
var deleteMessage = (projectName, sessionId, messageUuid) => Effect.gen(function* () {
|
|
149
|
-
const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
150
|
-
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
151
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
152
|
-
const messages = lines.map((line) => JSON.parse(line));
|
|
153
|
-
const targetIndex = messages.findIndex(
|
|
154
|
-
(m) => m.uuid === messageUuid || m.messageId === messageUuid
|
|
155
|
-
);
|
|
156
|
-
if (targetIndex === -1) {
|
|
157
|
-
return { success: false, error: "Message not found" };
|
|
158
|
-
}
|
|
159
|
-
const deletedMsg = messages[targetIndex];
|
|
160
|
-
const parentUuid = deletedMsg?.parentUuid;
|
|
161
|
-
const nextMsg = messages[targetIndex + 1];
|
|
162
|
-
if (nextMsg) {
|
|
163
|
-
nextMsg.parentUuid = parentUuid;
|
|
164
|
-
}
|
|
165
|
-
messages.splice(targetIndex, 1);
|
|
166
|
-
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
167
|
-
yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, "utf-8"));
|
|
168
|
-
return { success: true };
|
|
169
|
-
});
|
|
170
|
-
var previewCleanup = (projectName) => Effect.gen(function* () {
|
|
171
|
-
const projects = yield* listProjects;
|
|
172
|
-
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
173
|
-
const results = yield* Effect.all(
|
|
174
|
-
targetProjects.map(
|
|
175
|
-
(project) => Effect.gen(function* () {
|
|
176
|
-
const sessions = yield* listSessions(project.name);
|
|
177
|
-
const emptySessions = sessions.filter((s) => s.messageCount === 0);
|
|
178
|
-
const invalidSessions = sessions.filter(
|
|
179
|
-
(s) => s.title?.includes("Invalid API key") || s.title?.includes("API key")
|
|
180
|
-
);
|
|
181
|
-
return {
|
|
182
|
-
project: project.name,
|
|
183
|
-
emptySessions,
|
|
184
|
-
invalidSessions
|
|
185
|
-
};
|
|
186
|
-
})
|
|
187
|
-
),
|
|
188
|
-
{ concurrency: 5 }
|
|
189
|
-
);
|
|
190
|
-
return results;
|
|
191
|
-
});
|
|
192
|
-
var findOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
193
|
-
const projectPath = path.join(getSessionsDir(), projectName);
|
|
194
|
-
const files = yield* Effect.tryPromise(() => fs.readdir(projectPath));
|
|
195
|
-
const sessionIds = new Set(
|
|
196
|
-
files.filter((f) => !f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => f.replace(".jsonl", ""))
|
|
197
|
-
);
|
|
198
|
-
const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
199
|
-
const orphanAgents = [];
|
|
200
|
-
for (const agentFile of agentFiles) {
|
|
201
|
-
const filePath = path.join(projectPath, agentFile);
|
|
202
|
-
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
203
|
-
const firstLine = content.split("\n")[0];
|
|
204
|
-
if (firstLine) {
|
|
205
|
-
try {
|
|
206
|
-
const parsed = JSON.parse(firstLine);
|
|
207
|
-
if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {
|
|
208
|
-
orphanAgents.push({
|
|
209
|
-
agentId: agentFile.replace(".jsonl", ""),
|
|
210
|
-
sessionId: parsed.sessionId
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
} catch {
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return orphanAgents;
|
|
218
|
-
});
|
|
219
|
-
var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
220
|
-
const projectPath = path.join(getSessionsDir(), projectName);
|
|
221
|
-
const orphans = yield* findOrphanAgents(projectName);
|
|
222
|
-
const backupDir = path.join(projectPath, ".bak");
|
|
223
|
-
yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }));
|
|
224
|
-
const deletedAgents = [];
|
|
225
|
-
for (const orphan of orphans) {
|
|
226
|
-
const agentPath = path.join(projectPath, `${orphan.agentId}.jsonl`);
|
|
227
|
-
const agentBackupPath = path.join(backupDir, `${orphan.agentId}.jsonl`);
|
|
228
|
-
yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath));
|
|
229
|
-
deletedAgents.push(orphan.agentId);
|
|
230
|
-
}
|
|
231
|
-
return { success: true, deletedAgents, count: deletedAgents.length };
|
|
232
|
-
});
|
|
233
|
-
var clearSessions = (options) => Effect.gen(function* () {
|
|
234
|
-
const {
|
|
235
|
-
projectName,
|
|
236
|
-
clearEmpty = true,
|
|
237
|
-
clearInvalid = true,
|
|
238
|
-
clearOrphanAgents = true
|
|
239
|
-
} = options;
|
|
240
|
-
const cleanupPreview = yield* previewCleanup(projectName);
|
|
241
|
-
let deletedCount = 0;
|
|
242
|
-
let deletedAgentCount = 0;
|
|
243
|
-
for (const result of cleanupPreview) {
|
|
244
|
-
const toDelete = [
|
|
245
|
-
...clearEmpty ? result.emptySessions : [],
|
|
246
|
-
...clearInvalid ? result.invalidSessions : []
|
|
247
|
-
];
|
|
248
|
-
for (const session of toDelete) {
|
|
249
|
-
const deleteResult = yield* deleteSession(result.project, session.id);
|
|
250
|
-
deletedCount++;
|
|
251
|
-
deletedAgentCount += deleteResult.deletedAgents.length;
|
|
252
|
-
}
|
|
253
|
-
if (clearOrphanAgents) {
|
|
254
|
-
const orphanResult = yield* deleteOrphanAgents(result.project);
|
|
255
|
-
deletedAgentCount += orphanResult.count;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return { success: true, deletedCount, deletedAgentCount };
|
|
259
|
-
});
|
|
260
|
-
var getSessionFiles = (projectName, sessionId) => Effect.gen(function* () {
|
|
261
|
-
const messages = yield* readSession(projectName, sessionId);
|
|
262
|
-
const fileChanges = [];
|
|
263
|
-
const seenFiles = /* @__PURE__ */ new Set();
|
|
264
|
-
for (const msg of messages) {
|
|
265
|
-
if (msg.type === "file-history-snapshot") {
|
|
266
|
-
const snapshot = msg;
|
|
267
|
-
const backups = snapshot.snapshot?.trackedFileBackups;
|
|
268
|
-
if (backups && typeof backups === "object") {
|
|
269
|
-
for (const filePath of Object.keys(backups)) {
|
|
270
|
-
if (!seenFiles.has(filePath)) {
|
|
271
|
-
seenFiles.add(filePath);
|
|
272
|
-
fileChanges.push({
|
|
273
|
-
path: filePath,
|
|
274
|
-
action: "modified",
|
|
275
|
-
timestamp: snapshot.snapshot?.timestamp,
|
|
276
|
-
messageUuid: snapshot.messageId ?? msg.uuid
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
if (msg.type === "assistant" && msg.message) {
|
|
283
|
-
const assistantMsg = msg.message;
|
|
284
|
-
const content = assistantMsg.content;
|
|
285
|
-
if (Array.isArray(content)) {
|
|
286
|
-
for (const block of content) {
|
|
287
|
-
if (block.type === "tool_use" && (block.name === "Write" || block.name === "Edit")) {
|
|
288
|
-
const filePath = block.input?.file_path;
|
|
289
|
-
if (filePath && !seenFiles.has(filePath)) {
|
|
290
|
-
seenFiles.add(filePath);
|
|
291
|
-
fileChanges.push({
|
|
292
|
-
path: filePath,
|
|
293
|
-
action: block.name === "Write" ? "created" : "modified",
|
|
294
|
-
timestamp: msg.timestamp,
|
|
295
|
-
messageUuid: msg.uuid
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return {
|
|
304
|
-
sessionId,
|
|
305
|
-
projectName,
|
|
306
|
-
files: fileChanges,
|
|
307
|
-
totalChanges: fileChanges.length
|
|
308
|
-
};
|
|
309
|
-
});
|
|
310
|
-
var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect.gen(function* () {
|
|
311
|
-
const projectPath = path.join(getSessionsDir(), projectName);
|
|
312
|
-
const filePath = path.join(projectPath, `${sessionId}.jsonl`);
|
|
313
|
-
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
314
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
315
|
-
const allMessages = lines.map((line) => JSON.parse(line));
|
|
316
|
-
const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid);
|
|
317
|
-
if (splitIndex === -1) {
|
|
318
|
-
return { success: false, error: "Message not found" };
|
|
319
|
-
}
|
|
320
|
-
if (splitIndex === 0) {
|
|
321
|
-
return { success: false, error: "Cannot split at first message" };
|
|
322
|
-
}
|
|
323
|
-
const newSessionId = crypto.randomUUID();
|
|
324
|
-
const remainingMessages = allMessages.slice(0, splitIndex);
|
|
325
|
-
const movedMessages = allMessages.slice(splitIndex);
|
|
326
|
-
const updatedMovedMessages = movedMessages.map((msg, index) => {
|
|
327
|
-
const updated = { ...msg, sessionId: newSessionId };
|
|
328
|
-
if (index === 0) {
|
|
329
|
-
updated.parentUuid = null;
|
|
330
|
-
}
|
|
331
|
-
return updated;
|
|
332
|
-
});
|
|
333
|
-
const remainingContent = remainingMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
334
|
-
yield* Effect.tryPromise(() => fs.writeFile(filePath, remainingContent, "utf-8"));
|
|
335
|
-
const newFilePath = path.join(projectPath, `${newSessionId}.jsonl`);
|
|
336
|
-
const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
337
|
-
yield* Effect.tryPromise(() => fs.writeFile(newFilePath, newContent, "utf-8"));
|
|
338
|
-
const agentFiles = yield* Effect.tryPromise(() => fs.readdir(projectPath));
|
|
339
|
-
const agentJsonlFiles = agentFiles.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
340
|
-
for (const agentFile of agentJsonlFiles) {
|
|
341
|
-
const agentPath = path.join(projectPath, agentFile);
|
|
342
|
-
const agentContent = yield* Effect.tryPromise(() => fs.readFile(agentPath, "utf-8"));
|
|
343
|
-
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
344
|
-
if (agentLines.length === 0) continue;
|
|
345
|
-
const firstAgentMsg = JSON.parse(agentLines[0]);
|
|
346
|
-
if (firstAgentMsg.sessionId === sessionId) {
|
|
347
|
-
const agentId = agentFile.replace("agent-", "").replace(".jsonl", "");
|
|
348
|
-
const isRelatedToMoved = movedMessages.some(
|
|
349
|
-
(msg) => msg.agentId === agentId
|
|
350
|
-
);
|
|
351
|
-
if (isRelatedToMoved) {
|
|
352
|
-
const updatedAgentMessages = agentLines.map((line) => {
|
|
353
|
-
const msg = JSON.parse(line);
|
|
354
|
-
return JSON.stringify({ ...msg, sessionId: newSessionId });
|
|
355
|
-
});
|
|
356
|
-
const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
|
|
357
|
-
yield* Effect.tryPromise(() => fs.writeFile(agentPath, updatedAgentContent, "utf-8"));
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
return {
|
|
362
|
-
success: true,
|
|
363
|
-
newSessionId,
|
|
364
|
-
newSessionPath: newFilePath,
|
|
365
|
-
movedMessageCount: movedMessages.length
|
|
366
|
-
};
|
|
367
|
-
});
|
|
368
|
-
var getSessionDiffSummary = (projectName, sessionId) => Effect.gen(function* () {
|
|
369
|
-
const messages = yield* readSession(projectName, sessionId);
|
|
370
|
-
const title = pipe(
|
|
371
|
-
messages,
|
|
372
|
-
A.findFirst((m) => m.type === "human"),
|
|
373
|
-
O.map((m) => {
|
|
374
|
-
const msg = m.message;
|
|
375
|
-
const content = msg?.content ?? "";
|
|
376
|
-
return content.slice(0, 50) + (content.length > 50 ? "..." : "");
|
|
377
|
-
}),
|
|
378
|
-
O.getOrElse(() => "Untitled")
|
|
379
|
-
);
|
|
380
|
-
const changes = [];
|
|
381
|
-
const seenFiles = /* @__PURE__ */ new Set();
|
|
382
|
-
let snapshotCount = 0;
|
|
383
|
-
for (const msg of messages) {
|
|
384
|
-
if (msg.type === "file-history-snapshot") {
|
|
385
|
-
snapshotCount++;
|
|
386
|
-
const snapshot = msg;
|
|
387
|
-
const backups = snapshot.snapshot?.trackedFileBackups;
|
|
388
|
-
if (backups && typeof backups === "object") {
|
|
389
|
-
for (const [filePath, backup] of Object.entries(backups)) {
|
|
390
|
-
if (!seenFiles.has(filePath)) {
|
|
391
|
-
seenFiles.add(filePath);
|
|
392
|
-
const backupData = backup;
|
|
393
|
-
const content = backupData?.content ?? "";
|
|
394
|
-
changes.push({
|
|
395
|
-
path: filePath,
|
|
396
|
-
action: "modified",
|
|
397
|
-
hasBackup: content.length > 0,
|
|
398
|
-
backupPreview: content.slice(0, 100) + (content.length > 100 ? "..." : "")
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return {
|
|
406
|
-
sessionId,
|
|
407
|
-
projectName,
|
|
408
|
-
title,
|
|
409
|
-
changes,
|
|
410
|
-
totalFiles: changes.length,
|
|
411
|
-
snapshotCount
|
|
412
|
-
};
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// src/mcp/index.ts
|
|
416
|
-
var server = new McpServer({
|
|
417
|
-
name: "claude-sessions-mcp",
|
|
418
|
-
version: "0.1.0"
|
|
419
|
-
});
|
|
420
|
-
server.tool("list_projects", "List all Claude Code projects with session counts", {}, async () => {
|
|
421
|
-
const result = await Effect2.runPromise(listProjects);
|
|
422
|
-
return {
|
|
423
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
424
|
-
};
|
|
425
|
-
});
|
|
426
|
-
server.tool(
|
|
427
|
-
"list_sessions",
|
|
428
|
-
"List all sessions in a project",
|
|
429
|
-
{
|
|
430
|
-
project_name: z.string().describe("Project folder name (e.g., '-Users-young-works-myproject')")
|
|
431
|
-
},
|
|
432
|
-
async ({ project_name }) => {
|
|
433
|
-
const result = await Effect2.runPromise(listSessions(project_name));
|
|
434
|
-
return {
|
|
435
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
);
|
|
439
|
-
server.tool(
|
|
440
|
-
"rename_session",
|
|
441
|
-
"Rename a session by adding a title prefix to the first message",
|
|
442
|
-
{
|
|
443
|
-
project_name: z.string().describe("Project folder name"),
|
|
444
|
-
session_id: z.string().describe("Session ID (filename without .jsonl)"),
|
|
445
|
-
new_title: z.string().describe("New title to add as prefix")
|
|
446
|
-
},
|
|
447
|
-
async ({ project_name, session_id, new_title }) => {
|
|
448
|
-
const result = await Effect2.runPromise(
|
|
449
|
-
renameSession(project_name, session_id, new_title)
|
|
450
|
-
);
|
|
451
|
-
return {
|
|
452
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
);
|
|
456
|
-
server.tool(
|
|
457
|
-
"delete_session",
|
|
458
|
-
"Delete a session (moves to .bak folder for recovery)",
|
|
459
|
-
{
|
|
460
|
-
project_name: z.string().describe("Project folder name"),
|
|
461
|
-
session_id: z.string().describe("Session ID to delete")
|
|
462
|
-
},
|
|
463
|
-
async ({ project_name, session_id }) => {
|
|
464
|
-
const result = await Effect2.runPromise(deleteSession(project_name, session_id));
|
|
465
|
-
return {
|
|
466
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
);
|
|
470
|
-
server.tool(
|
|
471
|
-
"delete_message",
|
|
472
|
-
"Delete a message from a session and repair the parentUuid chain",
|
|
473
|
-
{
|
|
474
|
-
project_name: z.string().describe("Project folder name"),
|
|
475
|
-
session_id: z.string().describe("Session ID"),
|
|
476
|
-
message_uuid: z.string().describe("UUID of the message to delete")
|
|
477
|
-
},
|
|
478
|
-
async ({ project_name, session_id, message_uuid }) => {
|
|
479
|
-
const result = await Effect2.runPromise(
|
|
480
|
-
deleteMessage(project_name, session_id, message_uuid)
|
|
481
|
-
);
|
|
482
|
-
return {
|
|
483
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
);
|
|
487
|
-
server.tool(
|
|
488
|
-
"preview_cleanup",
|
|
489
|
-
"Preview sessions that would be cleaned (empty and invalid API key sessions)",
|
|
490
|
-
{
|
|
491
|
-
project_name: z.string().optional().describe("Optional: filter by project name")
|
|
492
|
-
},
|
|
493
|
-
async ({ project_name }) => {
|
|
494
|
-
const result = await Effect2.runPromise(previewCleanup(project_name));
|
|
495
|
-
return {
|
|
496
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
);
|
|
500
|
-
server.tool(
|
|
501
|
-
"clear_sessions",
|
|
502
|
-
"Delete all empty sessions and invalid API key sessions",
|
|
503
|
-
{
|
|
504
|
-
project_name: z.string().optional().describe("Optional: filter by project name"),
|
|
505
|
-
clear_empty: z.boolean().default(true).describe("Clear empty sessions (default: true)"),
|
|
506
|
-
clear_invalid: z.boolean().default(true).describe("Clear invalid API key sessions (default: true)"),
|
|
507
|
-
clear_orphan_agents: z.boolean().default(true).describe("Clear orphan agent files whose session no longer exists (default: true)")
|
|
508
|
-
},
|
|
509
|
-
async ({ project_name, clear_empty, clear_invalid, clear_orphan_agents }) => {
|
|
510
|
-
const result = await Effect2.runPromise(
|
|
511
|
-
clearSessions({
|
|
512
|
-
projectName: project_name,
|
|
513
|
-
clearEmpty: clear_empty,
|
|
514
|
-
clearInvalid: clear_invalid,
|
|
515
|
-
clearOrphanAgents: clear_orphan_agents
|
|
516
|
-
})
|
|
517
|
-
);
|
|
518
|
-
return {
|
|
519
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
);
|
|
523
|
-
server.tool(
|
|
524
|
-
"get_session_files",
|
|
525
|
-
"Get list of all files changed in a session (from file-history-snapshot and tool_use)",
|
|
526
|
-
{
|
|
527
|
-
project_name: z.string().describe("Project folder name"),
|
|
528
|
-
session_id: z.string().describe("Session ID")
|
|
529
|
-
},
|
|
530
|
-
async ({ project_name, session_id }) => {
|
|
531
|
-
const result = await Effect2.runPromise(getSessionFiles(project_name, session_id));
|
|
532
|
-
return {
|
|
533
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
);
|
|
537
|
-
server.tool(
|
|
538
|
-
"get_session_diff",
|
|
539
|
-
"Get diff summary for a session including file changes and snapshot info",
|
|
540
|
-
{
|
|
541
|
-
project_name: z.string().describe("Project folder name"),
|
|
542
|
-
session_id: z.string().describe("Session ID")
|
|
543
|
-
},
|
|
544
|
-
async ({ project_name, session_id }) => {
|
|
545
|
-
const result = await Effect2.runPromise(getSessionDiffSummary(project_name, session_id));
|
|
546
|
-
return {
|
|
547
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
);
|
|
551
|
-
server.tool(
|
|
552
|
-
"split_session",
|
|
553
|
-
"Split a session at a specific message, creating a new session with messages from that point onwards",
|
|
554
|
-
{
|
|
555
|
-
project_name: z.string().describe("Project folder name"),
|
|
556
|
-
session_id: z.string().describe("Session ID to split"),
|
|
557
|
-
message_uuid: z.string().describe(
|
|
558
|
-
"UUID of the message where the split starts (this message becomes the first message of the new session)"
|
|
559
|
-
)
|
|
560
|
-
},
|
|
561
|
-
async ({ project_name, session_id, message_uuid }) => {
|
|
562
|
-
const result = await Effect2.runPromise(
|
|
563
|
-
splitSession(project_name, session_id, message_uuid)
|
|
564
|
-
);
|
|
565
|
-
return {
|
|
566
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
);
|
|
570
|
-
var webServerInstance = null;
|
|
571
|
-
server.tool(
|
|
572
|
-
"start_gui",
|
|
573
|
-
"Start the web GUI for session management and open it in browser",
|
|
574
|
-
{
|
|
575
|
-
port: z.number().default(5173).describe("Port to run the web server on (default: 5173)"),
|
|
576
|
-
open_browser: z.boolean().default(true).describe("Whether to open browser automatically (default: true)"),
|
|
577
|
-
restart: z.boolean().default(false).describe("Restart the server if already running (default: false)")
|
|
578
|
-
},
|
|
579
|
-
async ({ port, open_browser, restart }) => {
|
|
580
|
-
try {
|
|
581
|
-
if (webServerInstance) {
|
|
582
|
-
if (restart) {
|
|
583
|
-
await stopWebServer(webServerInstance);
|
|
584
|
-
webServerInstance = null;
|
|
585
|
-
} else {
|
|
586
|
-
return {
|
|
587
|
-
content: [
|
|
588
|
-
{
|
|
589
|
-
type: "text",
|
|
590
|
-
text: JSON.stringify(
|
|
591
|
-
{
|
|
592
|
-
success: true,
|
|
593
|
-
message: "Web GUI is already running",
|
|
594
|
-
url: `http://localhost:${port}`
|
|
595
|
-
},
|
|
596
|
-
null,
|
|
597
|
-
2
|
|
598
|
-
)
|
|
599
|
-
}
|
|
600
|
-
]
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
webServerInstance = await startWebServer(port, open_browser);
|
|
605
|
-
return {
|
|
606
|
-
content: [
|
|
607
|
-
{
|
|
608
|
-
type: "text",
|
|
609
|
-
text: JSON.stringify(
|
|
610
|
-
{
|
|
611
|
-
success: true,
|
|
612
|
-
message: "Web GUI started successfully",
|
|
613
|
-
url: `http://localhost:${port}`,
|
|
614
|
-
pid: process.pid
|
|
615
|
-
},
|
|
616
|
-
null,
|
|
617
|
-
2
|
|
618
|
-
)
|
|
619
|
-
}
|
|
620
|
-
]
|
|
621
|
-
};
|
|
622
|
-
} catch (error) {
|
|
623
|
-
return {
|
|
624
|
-
content: [
|
|
625
|
-
{
|
|
626
|
-
type: "text",
|
|
627
|
-
text: JSON.stringify(
|
|
628
|
-
{
|
|
629
|
-
success: false,
|
|
630
|
-
error: String(error)
|
|
631
|
-
},
|
|
632
|
-
null,
|
|
633
|
-
2
|
|
634
|
-
)
|
|
635
|
-
}
|
|
636
|
-
]
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
);
|
|
641
|
-
server.tool("stop_gui", "Stop the web GUI server", {}, async () => {
|
|
642
|
-
if (webServerInstance) {
|
|
643
|
-
const port = webServerInstance.port;
|
|
644
|
-
try {
|
|
645
|
-
await fetch(`http://localhost:${port}/api/shutdown`, { method: "POST" });
|
|
646
|
-
} catch {
|
|
647
|
-
}
|
|
648
|
-
await stopWebServer(webServerInstance);
|
|
649
|
-
webServerInstance = null;
|
|
650
|
-
return {
|
|
651
|
-
content: [
|
|
652
|
-
{
|
|
653
|
-
type: "text",
|
|
654
|
-
text: JSON.stringify({ success: true, message: "Web GUI stopped successfully" }, null, 2)
|
|
655
|
-
}
|
|
656
|
-
]
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
return {
|
|
660
|
-
content: [
|
|
661
|
-
{
|
|
662
|
-
type: "text",
|
|
663
|
-
text: JSON.stringify({ success: true, message: "Web GUI was not running" }, null, 2)
|
|
664
|
-
}
|
|
665
|
-
]
|
|
666
|
-
};
|
|
667
|
-
});
|
|
668
|
-
async function main() {
|
|
669
|
-
const transport = new StdioServerTransport();
|
|
670
|
-
await server.connect(transport);
|
|
671
|
-
}
|
|
672
|
-
main().catch(console.error);
|
|
673
|
-
//# sourceMappingURL=index.js.map
|