agent-replay 0.1.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/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/agent-replay.js +20 -0
- package/build/client/_app/immutable/assets/0.D9-tUsfl.css +1 -0
- package/build/client/_app/immutable/assets/0.D9-tUsfl.css.br +0 -0
- package/build/client/_app/immutable/assets/0.D9-tUsfl.css.gz +0 -0
- package/build/client/_app/immutable/assets/2.Ch4de4ty.css +1 -0
- package/build/client/_app/immutable/assets/2.Ch4de4ty.css.br +0 -0
- package/build/client/_app/immutable/assets/2.Ch4de4ty.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.BP1aEJ1A.css +1 -0
- package/build/client/_app/immutable/assets/3.BP1aEJ1A.css.br +0 -0
- package/build/client/_app/immutable/assets/3.BP1aEJ1A.css.gz +0 -0
- package/build/client/_app/immutable/chunks/8LyLqy0e.js +1 -0
- package/build/client/_app/immutable/chunks/8LyLqy0e.js.br +3 -0
- package/build/client/_app/immutable/chunks/8LyLqy0e.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BmaMsIzI.js +3 -0
- package/build/client/_app/immutable/chunks/BmaMsIzI.js.br +0 -0
- package/build/client/_app/immutable/chunks/BmaMsIzI.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BoUXsunA.js +1 -0
- package/build/client/_app/immutable/chunks/BoUXsunA.js.br +0 -0
- package/build/client/_app/immutable/chunks/BoUXsunA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CCa20sJM.js +1 -0
- package/build/client/_app/immutable/chunks/CCa20sJM.js.br +0 -0
- package/build/client/_app/immutable/chunks/CCa20sJM.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CP-o74wi.js +2 -0
- package/build/client/_app/immutable/chunks/CP-o74wi.js.br +0 -0
- package/build/client/_app/immutable/chunks/CP-o74wi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CZWwZlGk.js +1 -0
- package/build/client/_app/immutable/chunks/CZWwZlGk.js.br +0 -0
- package/build/client/_app/immutable/chunks/CZWwZlGk.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D6SnDtAs.js +1 -0
- package/build/client/_app/immutable/chunks/D6SnDtAs.js.br +0 -0
- package/build/client/_app/immutable/chunks/D6SnDtAs.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DApgqo7x.js +1 -0
- package/build/client/_app/immutable/chunks/DApgqo7x.js.br +0 -0
- package/build/client/_app/immutable/chunks/DApgqo7x.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DCcb0kkP.js +1 -0
- package/build/client/_app/immutable/chunks/DCcb0kkP.js.br +0 -0
- package/build/client/_app/immutable/chunks/DCcb0kkP.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CelDjTDX.js +2 -0
- package/build/client/_app/immutable/entry/app.CelDjTDX.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CelDjTDX.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.Bo8U6P_2.js +1 -0
- package/build/client/_app/immutable/entry/start.Bo8U6P_2.js.br +2 -0
- package/build/client/_app/immutable/entry/start.Bo8U6P_2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.DdgnJbSm.js +1 -0
- package/build/client/_app/immutable/nodes/0.DdgnJbSm.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.DdgnJbSm.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.DUCL1Cc_.js +1 -0
- package/build/client/_app/immutable/nodes/1.DUCL1Cc_.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.DUCL1Cc_.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.FNRc_Nvw.js +1 -0
- package/build/client/_app/immutable/nodes/2.FNRc_Nvw.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.FNRc_Nvw.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.CaEmUkA2.js +29 -0
- package/build/client/_app/immutable/nodes/3.CaEmUkA2.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.CaEmUkA2.js.gz +0 -0
- package/build/client/_app/version.json +1 -0
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/client/favicon.svg +13 -0
- package/build/client/favicon.svg.br +0 -0
- package/build/client/favicon.svg.gz +0 -0
- package/build/client/robots.txt +3 -0
- package/build/client/screenshot.png +0 -0
- package/build/env.js +94 -0
- package/build/handler.js +1494 -0
- package/build/index.js +345 -0
- package/build/server/chunks/0-CkGitm9W.js +9 -0
- package/build/server/chunks/0-CkGitm9W.js.map +1 -0
- package/build/server/chunks/1-BU8Hl3FY.js +9 -0
- package/build/server/chunks/1-BU8Hl3FY.js.map +1 -0
- package/build/server/chunks/2-DenZJmTh.js +27 -0
- package/build/server/chunks/2-DenZJmTh.js.map +1 -0
- package/build/server/chunks/3-Df2VKXDy.js +73 -0
- package/build/server/chunks/3-Df2VKXDy.js.map +1 -0
- package/build/server/chunks/_layout.svelte-BK7oCPlN.js +26 -0
- package/build/server/chunks/_layout.svelte-BK7oCPlN.js.map +1 -0
- package/build/server/chunks/_page.svelte-BMqbzglq.js +178 -0
- package/build/server/chunks/_page.svelte-BMqbzglq.js.map +1 -0
- package/build/server/chunks/_page.svelte-C2kNC2pi.js +504 -0
- package/build/server/chunks/_page.svelte-C2kNC2pi.js.map +1 -0
- package/build/server/chunks/context-JUYyv6NC.js +74 -0
- package/build/server/chunks/context-JUYyv6NC.js.map +1 -0
- package/build/server/chunks/error.svelte-Epasb48G.js +46 -0
- package/build/server/chunks/error.svelte-Epasb48G.js.map +1 -0
- package/build/server/chunks/escaping-CqgfEcN3.js +19 -0
- package/build/server/chunks/escaping-CqgfEcN3.js.map +1 -0
- package/build/server/chunks/exports-CURVw3-F.js +303 -0
- package/build/server/chunks/exports-CURVw3-F.js.map +1 -0
- package/build/server/chunks/format-dOzJq-yD.js +59 -0
- package/build/server/chunks/format-dOzJq-yD.js.map +1 -0
- package/build/server/chunks/index-C0L7m6Jv.js +1497 -0
- package/build/server/chunks/index-C0L7m6Jv.js.map +1 -0
- package/build/server/chunks/index-CoD1IJuy.js +190 -0
- package/build/server/chunks/index-CoD1IJuy.js.map +1 -0
- package/build/server/chunks/index2-f44F-4KW.js +614 -0
- package/build/server/chunks/index2-f44F-4KW.js.map +1 -0
- package/build/server/chunks/shared-server-DaWdgxVh.js +11 -0
- package/build/server/chunks/shared-server-DaWdgxVh.js.map +1 -0
- package/build/server/index.js +8152 -0
- package/build/server/index.js.map +1 -0
- package/build/server/manifest.js +54 -0
- package/build/server/manifest.js.map +1 -0
- package/build/shims.js +32 -0
- package/package.json +71 -0
- package/static/favicon.svg +13 -0
- package/static/robots.txt +3 -0
- package/static/screenshot.png +0 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from 'fs/promises';
|
|
2
|
+
import { join, basename } from 'path';
|
|
3
|
+
import { b as private_env } from './shared-server-DaWdgxVh.js';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
|
|
7
|
+
const CLAUDE_DIR = private_env.CLAUDE_DIR || join(homedir(), ".claude", "projects");
|
|
8
|
+
private_env.DEMO_MODE === "true";
|
|
9
|
+
const PRICING = {
|
|
10
|
+
"claude-opus-4-6": { input: 15, output: 75 },
|
|
11
|
+
"claude-sonnet-4-5-20250929": { input: 3, output: 15 },
|
|
12
|
+
"claude-haiku-4-5-20251001": { input: 0.8, output: 4 },
|
|
13
|
+
// Older models
|
|
14
|
+
"claude-3-5-sonnet-20241022": { input: 3, output: 15 },
|
|
15
|
+
"claude-3-5-haiku-20241022": { input: 0.8, output: 4 },
|
|
16
|
+
"claude-3-opus-20240229": { input: 15, output: 75 },
|
|
17
|
+
"claude-3-sonnet-20240229": { input: 3, output: 15 }
|
|
18
|
+
};
|
|
19
|
+
const DEFAULT_PRICING = { input: 3, output: 15 };
|
|
20
|
+
function estimateCost(model, inputTokens, outputTokens) {
|
|
21
|
+
const pricing = PRICING[model] || DEFAULT_PRICING;
|
|
22
|
+
return (inputTokens * pricing.input + outputTokens * pricing.output) / 1e6;
|
|
23
|
+
}
|
|
24
|
+
function decodeProjectName(dirName) {
|
|
25
|
+
return dirName.replace(/-/g, "/").replace(/^\//, "");
|
|
26
|
+
}
|
|
27
|
+
async function discoverSessions() {
|
|
28
|
+
const sessions = [];
|
|
29
|
+
let projectDirs;
|
|
30
|
+
try {
|
|
31
|
+
projectDirs = await readdir(CLAUDE_DIR);
|
|
32
|
+
} catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
for (const projectDir of projectDirs) {
|
|
36
|
+
const projectPath = join(CLAUDE_DIR, projectDir);
|
|
37
|
+
const projectStat = await stat(projectPath).catch(() => null);
|
|
38
|
+
if (!projectStat?.isDirectory()) continue;
|
|
39
|
+
let files;
|
|
40
|
+
try {
|
|
41
|
+
files = await readdir(projectPath);
|
|
42
|
+
} catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
47
|
+
const filePath = join(projectPath, file);
|
|
48
|
+
const sessionId = basename(file, ".jsonl");
|
|
49
|
+
try {
|
|
50
|
+
const summary = await buildSummary(filePath, sessionId, projectDir);
|
|
51
|
+
if (summary) sessions.push(summary);
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
sessions.sort((a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime());
|
|
57
|
+
return sessions;
|
|
58
|
+
}
|
|
59
|
+
async function buildSummary(filePath, sessionId, projectDir) {
|
|
60
|
+
const content = await readFile(filePath, "utf-8");
|
|
61
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
62
|
+
if (lines.length === 0) return null;
|
|
63
|
+
let slug = "";
|
|
64
|
+
let model = "";
|
|
65
|
+
let version = "";
|
|
66
|
+
let startedAt = "";
|
|
67
|
+
let lastActiveAt = "";
|
|
68
|
+
let inputTokens = 0;
|
|
69
|
+
let outputTokens = 0;
|
|
70
|
+
let toolCallCount = 0;
|
|
71
|
+
let eventCount = 0;
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
let entry;
|
|
74
|
+
try {
|
|
75
|
+
entry = JSON.parse(line);
|
|
76
|
+
} catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (entry.type === "progress" || entry.type === "queue-operation")
|
|
80
|
+
continue;
|
|
81
|
+
if (entry.type === "user") {
|
|
82
|
+
const userEntry = entry;
|
|
83
|
+
if (!startedAt && userEntry.timestamp) startedAt = userEntry.timestamp;
|
|
84
|
+
if (userEntry.timestamp) lastActiveAt = userEntry.timestamp;
|
|
85
|
+
if (userEntry.slug && !slug) slug = userEntry.slug;
|
|
86
|
+
if (userEntry.version && !version) version = userEntry.version;
|
|
87
|
+
eventCount++;
|
|
88
|
+
}
|
|
89
|
+
if (entry.type === "assistant") {
|
|
90
|
+
const assistantEntry = entry;
|
|
91
|
+
if (assistantEntry.timestamp) lastActiveAt = assistantEntry.timestamp;
|
|
92
|
+
if (assistantEntry.message.model && !model && assistantEntry.message.model !== "synthetic")
|
|
93
|
+
model = assistantEntry.message.model;
|
|
94
|
+
const usage = assistantEntry.message.usage;
|
|
95
|
+
if (usage) {
|
|
96
|
+
inputTokens += (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0);
|
|
97
|
+
outputTokens += usage.output_tokens || 0;
|
|
98
|
+
}
|
|
99
|
+
const content2 = assistantEntry.message.content;
|
|
100
|
+
if (Array.isArray(content2)) {
|
|
101
|
+
for (const block of content2) {
|
|
102
|
+
if (block.type === "tool_use") toolCallCount++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
eventCount++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!startedAt) return null;
|
|
109
|
+
return {
|
|
110
|
+
sessionId,
|
|
111
|
+
project: decodeProjectName(projectDir),
|
|
112
|
+
slug,
|
|
113
|
+
startedAt,
|
|
114
|
+
lastActiveAt,
|
|
115
|
+
model,
|
|
116
|
+
version,
|
|
117
|
+
eventCount,
|
|
118
|
+
toolCallCount,
|
|
119
|
+
inputTokens,
|
|
120
|
+
outputTokens,
|
|
121
|
+
estimatedCost: estimateCost(model, inputTokens, outputTokens),
|
|
122
|
+
filePath,
|
|
123
|
+
provider: "claude-code"
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function parseSession(filePath, sessionId, project) {
|
|
127
|
+
const content = await readFile(filePath, "utf-8");
|
|
128
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
129
|
+
const entries = [];
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
try {
|
|
132
|
+
const entry = JSON.parse(line);
|
|
133
|
+
entries.push(entry);
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const filtered = entries.filter(
|
|
138
|
+
(e) => e.type !== "progress" && e.type !== "file-history-snapshot" && e.type !== "queue-operation"
|
|
139
|
+
);
|
|
140
|
+
const assistantTurns = /* @__PURE__ */ new Map();
|
|
141
|
+
const orderedEntries = [];
|
|
142
|
+
const seenAssistantIds = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const entry of filtered) {
|
|
144
|
+
if (entry.type === "user") {
|
|
145
|
+
orderedEntries.push({ kind: "user", entry });
|
|
146
|
+
} else if (entry.type === "assistant") {
|
|
147
|
+
const aEntry = entry;
|
|
148
|
+
const msgId = aEntry.message.id || aEntry.uuid;
|
|
149
|
+
if (!assistantTurns.has(msgId)) {
|
|
150
|
+
assistantTurns.set(msgId, {
|
|
151
|
+
messageId: msgId,
|
|
152
|
+
timestamp: aEntry.timestamp,
|
|
153
|
+
model: aEntry.message.model || "",
|
|
154
|
+
blocks: [],
|
|
155
|
+
inputTokens: 0,
|
|
156
|
+
outputTokens: 0
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const turn = assistantTurns.get(msgId);
|
|
160
|
+
const rawContent = aEntry.message.content;
|
|
161
|
+
if (typeof rawContent === "string") {
|
|
162
|
+
if (rawContent.trim()) {
|
|
163
|
+
turn.blocks.push({ type: "text", text: rawContent });
|
|
164
|
+
}
|
|
165
|
+
} else if (Array.isArray(rawContent)) {
|
|
166
|
+
for (const block of rawContent) {
|
|
167
|
+
turn.blocks.push(block);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const usage = aEntry.message.usage;
|
|
171
|
+
if (usage) {
|
|
172
|
+
turn.inputTokens += (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0);
|
|
173
|
+
turn.outputTokens += usage.output_tokens || 0;
|
|
174
|
+
}
|
|
175
|
+
if (!seenAssistantIds.has(msgId)) {
|
|
176
|
+
seenAssistantIds.add(msgId);
|
|
177
|
+
orderedEntries.push({ kind: "assistant_turn", messageId: msgId });
|
|
178
|
+
}
|
|
179
|
+
} else if (entry.type === "system") {
|
|
180
|
+
orderedEntries.push({ kind: "system", entry });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const toolResults = /* @__PURE__ */ new Map();
|
|
184
|
+
for (const entry of filtered) {
|
|
185
|
+
if (entry.type !== "user") continue;
|
|
186
|
+
const userEntry = entry;
|
|
187
|
+
const rawContent = userEntry.message.content;
|
|
188
|
+
if (!Array.isArray(rawContent)) continue;
|
|
189
|
+
for (const block of rawContent) {
|
|
190
|
+
if (block.type === "tool_result") {
|
|
191
|
+
const trBlock = block;
|
|
192
|
+
let resultText = "";
|
|
193
|
+
if (typeof trBlock.content === "string") {
|
|
194
|
+
resultText = trBlock.content;
|
|
195
|
+
} else if (Array.isArray(trBlock.content)) {
|
|
196
|
+
resultText = trBlock.content.map((b) => b.text).join("\n");
|
|
197
|
+
}
|
|
198
|
+
toolResults.set(trBlock.tool_use_id, {
|
|
199
|
+
content: resultText,
|
|
200
|
+
isError: trBlock.is_error || false
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const events = [];
|
|
206
|
+
let eventIndex = 0;
|
|
207
|
+
let slug = "";
|
|
208
|
+
let version = "";
|
|
209
|
+
let model = "";
|
|
210
|
+
let totalInputTokens = 0;
|
|
211
|
+
let totalOutputTokens = 0;
|
|
212
|
+
let startedAt = "";
|
|
213
|
+
let lastActiveAt = "";
|
|
214
|
+
for (const ordered of orderedEntries) {
|
|
215
|
+
if (ordered.kind === "user") {
|
|
216
|
+
const userEntry = ordered.entry;
|
|
217
|
+
if (!startedAt) startedAt = userEntry.timestamp;
|
|
218
|
+
lastActiveAt = userEntry.timestamp;
|
|
219
|
+
if (userEntry.slug && !slug) slug = userEntry.slug;
|
|
220
|
+
if (userEntry.version && !version) version = userEntry.version;
|
|
221
|
+
const rawContent = userEntry.message.content;
|
|
222
|
+
if (typeof rawContent === "string") {
|
|
223
|
+
if (rawContent.trim()) {
|
|
224
|
+
events.push(makeEvent(eventIndex++, userEntry.timestamp, {
|
|
225
|
+
eventType: "user_message",
|
|
226
|
+
text: rawContent
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
} else if (Array.isArray(rawContent)) {
|
|
230
|
+
const textParts = [];
|
|
231
|
+
for (const block of rawContent) {
|
|
232
|
+
if (block.type === "text") {
|
|
233
|
+
textParts.push(block.text);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (textParts.length > 0) {
|
|
237
|
+
events.push(makeEvent(eventIndex++, userEntry.timestamp, {
|
|
238
|
+
eventType: "user_message",
|
|
239
|
+
text: textParts.join("\n")
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} else if (ordered.kind === "assistant_turn") {
|
|
244
|
+
const turn = assistantTurns.get(ordered.messageId);
|
|
245
|
+
if (!model && turn.model) model = turn.model;
|
|
246
|
+
totalInputTokens += turn.inputTokens;
|
|
247
|
+
totalOutputTokens += turn.outputTokens;
|
|
248
|
+
lastActiveAt = turn.timestamp;
|
|
249
|
+
for (const block of turn.blocks) {
|
|
250
|
+
if (block.type === "thinking") {
|
|
251
|
+
const thinkBlock = block;
|
|
252
|
+
if (thinkBlock.thinking.trim()) {
|
|
253
|
+
events.push(makeEvent(eventIndex++, turn.timestamp, {
|
|
254
|
+
eventType: "thinking",
|
|
255
|
+
thinking: thinkBlock.thinking
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
} else if (block.type === "text") {
|
|
259
|
+
const textBlock = block;
|
|
260
|
+
if (textBlock.text.trim()) {
|
|
261
|
+
events.push(makeEvent(eventIndex++, turn.timestamp, {
|
|
262
|
+
eventType: "assistant_text",
|
|
263
|
+
text: textBlock.text
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
} else if (block.type === "tool_use") {
|
|
267
|
+
const toolBlock = block;
|
|
268
|
+
const result = toolResults.get(toolBlock.id);
|
|
269
|
+
events.push(makeEvent(eventIndex++, turn.timestamp, {
|
|
270
|
+
eventType: "tool_call",
|
|
271
|
+
toolName: toolBlock.name,
|
|
272
|
+
toolUseId: toolBlock.id,
|
|
273
|
+
input: toolBlock.input,
|
|
274
|
+
result
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} else if (ordered.kind === "system") {
|
|
279
|
+
const sysEntry = ordered.entry;
|
|
280
|
+
if (sysEntry.subtype === "compact_boundary") {
|
|
281
|
+
events.push(makeEvent(eventIndex++, sysEntry.timestamp, {
|
|
282
|
+
eventType: "compact_boundary"
|
|
283
|
+
}));
|
|
284
|
+
} else {
|
|
285
|
+
events.push(makeEvent(eventIndex++, sysEntry.timestamp, {
|
|
286
|
+
eventType: "system",
|
|
287
|
+
subtype: sysEntry.subtype || "unknown",
|
|
288
|
+
durationMs: sysEntry.durationMs
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const summary = {
|
|
294
|
+
sessionId,
|
|
295
|
+
project,
|
|
296
|
+
slug,
|
|
297
|
+
startedAt,
|
|
298
|
+
lastActiveAt,
|
|
299
|
+
model,
|
|
300
|
+
version,
|
|
301
|
+
eventCount: events.length,
|
|
302
|
+
toolCallCount: events.filter((e) => e.data.eventType === "tool_call").length,
|
|
303
|
+
inputTokens: totalInputTokens,
|
|
304
|
+
outputTokens: totalOutputTokens,
|
|
305
|
+
estimatedCost: estimateCost(model, totalInputTokens, totalOutputTokens),
|
|
306
|
+
filePath,
|
|
307
|
+
provider: "claude-code"
|
|
308
|
+
};
|
|
309
|
+
return { summary, events };
|
|
310
|
+
}
|
|
311
|
+
function makeEvent(index, timestamp, data) {
|
|
312
|
+
return {
|
|
313
|
+
id: `evt-${index}`,
|
|
314
|
+
index,
|
|
315
|
+
timestamp,
|
|
316
|
+
data
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
class ClaudeCodeProvider {
|
|
320
|
+
type = "claude-code";
|
|
321
|
+
async discoverSessions() {
|
|
322
|
+
return discoverSessions();
|
|
323
|
+
}
|
|
324
|
+
async parseSession(sessionId, meta) {
|
|
325
|
+
return parseSession(meta.filePath, sessionId, meta.project);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const CURSOR_STORAGE = join(
|
|
329
|
+
homedir(),
|
|
330
|
+
"Library",
|
|
331
|
+
"Application Support",
|
|
332
|
+
"Cursor",
|
|
333
|
+
"User",
|
|
334
|
+
"workspaceStorage"
|
|
335
|
+
);
|
|
336
|
+
const CONVERSATION_KEY_PATTERNS = [
|
|
337
|
+
"composer.composerData",
|
|
338
|
+
"workbench.panel.aichat.view.aichat.chatdata"
|
|
339
|
+
];
|
|
340
|
+
function getWorkspaceName(workspaceDir) {
|
|
341
|
+
try {
|
|
342
|
+
const wsPath = join(workspaceDir, "workspace.json");
|
|
343
|
+
const data = JSON.parse(require("fs").readFileSync(wsPath, "utf-8"));
|
|
344
|
+
const folder = data.folder || data.workspace || "";
|
|
345
|
+
const decoded = decodeURIComponent(folder);
|
|
346
|
+
const parts = decoded.replace(/^file:\/\//, "").split("/");
|
|
347
|
+
return parts[parts.length - 1] || parts[parts.length - 2] || "unknown";
|
|
348
|
+
} catch {
|
|
349
|
+
return "unknown";
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function extractMessages(conv) {
|
|
353
|
+
if (conv.bubbles && Array.isArray(conv.bubbles)) {
|
|
354
|
+
return conv.bubbles.map((b) => ({
|
|
355
|
+
role: b.type === "user" ? "user" : b.type === "ai" ? "assistant" : b.type,
|
|
356
|
+
text: b.rawText || b.text || "",
|
|
357
|
+
model: b.modelName,
|
|
358
|
+
tokens: b.tokenCount
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
if (conv.messages && Array.isArray(conv.messages)) return conv.messages;
|
|
362
|
+
if (conv.conversation && Array.isArray(conv.conversation)) return conv.conversation;
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
function getMessageRole(msg) {
|
|
366
|
+
if (msg.role === "user" || msg.type === 1) return "user";
|
|
367
|
+
if (msg.role === "assistant" || msg.role === "ai" || msg.type === 2) return "assistant";
|
|
368
|
+
return "system";
|
|
369
|
+
}
|
|
370
|
+
function getMessageText(msg) {
|
|
371
|
+
return msg.text || msg.content || msg.message || "";
|
|
372
|
+
}
|
|
373
|
+
function getTimestamp(msg) {
|
|
374
|
+
const ts = msg.timestamp || msg.createdAt;
|
|
375
|
+
if (!ts) return (/* @__PURE__ */ new Date()).toISOString();
|
|
376
|
+
const ms = ts > 1e12 ? ts : ts * 1e3;
|
|
377
|
+
return new Date(ms).toISOString();
|
|
378
|
+
}
|
|
379
|
+
function getConvId(conv) {
|
|
380
|
+
return conv.composerId || conv.tabId || conv.id || crypto.randomUUID();
|
|
381
|
+
}
|
|
382
|
+
function getConvTimestamp(conv, position) {
|
|
383
|
+
const messages = extractMessages(conv);
|
|
384
|
+
if (messages.length > 0) {
|
|
385
|
+
const msg = position === "start" ? messages[0] : messages[messages.length - 1];
|
|
386
|
+
return getTimestamp(msg);
|
|
387
|
+
}
|
|
388
|
+
const raw = position === "start" ? conv.createdAt : conv.lastUpdatedAt || conv.updatedAt || conv.createdAt;
|
|
389
|
+
if (typeof raw === "number") {
|
|
390
|
+
const ms = raw > 1e12 ? raw : raw * 1e3;
|
|
391
|
+
return new Date(ms).toISOString();
|
|
392
|
+
}
|
|
393
|
+
if (typeof raw === "string") return raw;
|
|
394
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
395
|
+
}
|
|
396
|
+
class CursorProvider {
|
|
397
|
+
type = "cursor";
|
|
398
|
+
async discoverSessions() {
|
|
399
|
+
const sessions = [];
|
|
400
|
+
let workspaceDirs;
|
|
401
|
+
try {
|
|
402
|
+
workspaceDirs = await readdir(CURSOR_STORAGE);
|
|
403
|
+
} catch {
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
406
|
+
for (const wsDir of workspaceDirs) {
|
|
407
|
+
const wsPath = join(CURSOR_STORAGE, wsDir);
|
|
408
|
+
const dbPath = join(wsPath, "state.vscdb");
|
|
409
|
+
try {
|
|
410
|
+
const s = await stat(dbPath);
|
|
411
|
+
if (!s.isFile()) continue;
|
|
412
|
+
} catch {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
let db;
|
|
416
|
+
try {
|
|
417
|
+
db = new Database(dbPath, { readonly: true });
|
|
418
|
+
} catch {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
const projectName = getWorkspaceName(wsPath);
|
|
423
|
+
for (const keyPattern of CONVERSATION_KEY_PATTERNS) {
|
|
424
|
+
const rows = db.prepare(
|
|
425
|
+
"SELECT key, value FROM ItemTable WHERE key LIKE ?"
|
|
426
|
+
).all(`%${keyPattern}%`);
|
|
427
|
+
for (const row of rows) {
|
|
428
|
+
try {
|
|
429
|
+
const raw = typeof row.value === "string" ? row.value : row.value.toString("utf-8");
|
|
430
|
+
const parsed = JSON.parse(raw);
|
|
431
|
+
const conversations = [];
|
|
432
|
+
if (Array.isArray(parsed)) {
|
|
433
|
+
conversations.push(...parsed);
|
|
434
|
+
} else if (parsed.tabs && Array.isArray(parsed.tabs)) {
|
|
435
|
+
conversations.push(...parsed.tabs);
|
|
436
|
+
} else if (parsed.allComposers && Array.isArray(parsed.allComposers)) {
|
|
437
|
+
conversations.push(...parsed.allComposers);
|
|
438
|
+
} else if (parsed.messages || parsed.bubbles || parsed.conversation) {
|
|
439
|
+
conversations.push(parsed);
|
|
440
|
+
}
|
|
441
|
+
for (const conv of conversations) {
|
|
442
|
+
const messages = extractMessages(conv);
|
|
443
|
+
if (messages.length < 2) continue;
|
|
444
|
+
const convId = getConvId(conv);
|
|
445
|
+
const startedAt = getConvTimestamp(conv, "start");
|
|
446
|
+
const lastActiveAt = getConvTimestamp(conv, "end");
|
|
447
|
+
let inputTokens = 0;
|
|
448
|
+
let outputTokens = 0;
|
|
449
|
+
let toolCallCount = 0;
|
|
450
|
+
let model = "";
|
|
451
|
+
for (const msg of messages) {
|
|
452
|
+
const role = getMessageRole(msg);
|
|
453
|
+
const msgModel = msg.model || msg.modelName;
|
|
454
|
+
if (msgModel && !model) model = msgModel;
|
|
455
|
+
if (msg.inputTokens) inputTokens += msg.inputTokens;
|
|
456
|
+
if (msg.outputTokens) outputTokens += msg.outputTokens;
|
|
457
|
+
if (msg.tokens || msg.tokensUsed) {
|
|
458
|
+
const t = msg.tokens || msg.tokensUsed || 0;
|
|
459
|
+
if (role === "user") inputTokens += t;
|
|
460
|
+
else if (role === "assistant") outputTokens += t;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const slug = conv.title || conv.name || getMessageText(messages.find((m) => getMessageRole(m) === "user") || messages[0]).slice(0, 60) || "Cursor conversation";
|
|
464
|
+
sessions.push({
|
|
465
|
+
sessionId: `cursor-${convId}`,
|
|
466
|
+
project: projectName,
|
|
467
|
+
slug,
|
|
468
|
+
startedAt,
|
|
469
|
+
lastActiveAt,
|
|
470
|
+
model: model || "unknown",
|
|
471
|
+
version: "",
|
|
472
|
+
eventCount: messages.length,
|
|
473
|
+
toolCallCount,
|
|
474
|
+
inputTokens,
|
|
475
|
+
outputTokens,
|
|
476
|
+
estimatedCost: estimateCost(model, inputTokens, outputTokens),
|
|
477
|
+
filePath: dbPath,
|
|
478
|
+
provider: "cursor",
|
|
479
|
+
providerMeta: {
|
|
480
|
+
dbPath,
|
|
481
|
+
conversationKey: row.key,
|
|
482
|
+
conversationId: convId,
|
|
483
|
+
workspaceDir: wsPath
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
} finally {
|
|
492
|
+
db.close();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
sessions.sort((a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime());
|
|
496
|
+
return sessions;
|
|
497
|
+
}
|
|
498
|
+
async parseSession(sessionId, meta) {
|
|
499
|
+
const { dbPath, conversationKey, conversationId } = meta;
|
|
500
|
+
const db = new Database(dbPath, { readonly: true });
|
|
501
|
+
try {
|
|
502
|
+
const rows = db.prepare(
|
|
503
|
+
"SELECT value FROM ItemTable WHERE key LIKE ?"
|
|
504
|
+
).all(`%${conversationKey}%`);
|
|
505
|
+
for (const row of rows) {
|
|
506
|
+
const raw = typeof row.value === "string" ? row.value : row.value.toString("utf-8");
|
|
507
|
+
const parsed = JSON.parse(raw);
|
|
508
|
+
const conversations = [];
|
|
509
|
+
if (Array.isArray(parsed)) {
|
|
510
|
+
conversations.push(...parsed);
|
|
511
|
+
} else if (parsed.tabs && Array.isArray(parsed.tabs)) {
|
|
512
|
+
conversations.push(...parsed.tabs);
|
|
513
|
+
} else if (parsed.allComposers && Array.isArray(parsed.allComposers)) {
|
|
514
|
+
conversations.push(...parsed.allComposers);
|
|
515
|
+
} else if (parsed.messages || parsed.bubbles || parsed.conversation) {
|
|
516
|
+
conversations.push(parsed);
|
|
517
|
+
}
|
|
518
|
+
const conv = conversations.find((c) => getConvId(c) === conversationId);
|
|
519
|
+
if (!conv) continue;
|
|
520
|
+
return buildTimeline(conv, sessionId, meta);
|
|
521
|
+
}
|
|
522
|
+
throw new Error(`Conversation ${conversationId} not found in database`);
|
|
523
|
+
} finally {
|
|
524
|
+
db.close();
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function buildTimeline(conv, sessionId, meta) {
|
|
529
|
+
const messages = extractMessages(conv);
|
|
530
|
+
const events = [];
|
|
531
|
+
let eventIndex = 0;
|
|
532
|
+
let model = "";
|
|
533
|
+
let inputTokens = 0;
|
|
534
|
+
let outputTokens = 0;
|
|
535
|
+
for (const msg of messages) {
|
|
536
|
+
const role = getMessageRole(msg);
|
|
537
|
+
const text = getMessageText(msg);
|
|
538
|
+
const timestamp = getTimestamp(msg);
|
|
539
|
+
const msgModel = msg.model || msg.modelName;
|
|
540
|
+
if (msgModel && !model) model = msgModel;
|
|
541
|
+
if (msg.inputTokens) inputTokens += msg.inputTokens;
|
|
542
|
+
if (msg.outputTokens) outputTokens += msg.outputTokens;
|
|
543
|
+
if (msg.tokens || msg.tokensUsed) {
|
|
544
|
+
const t = msg.tokens || msg.tokensUsed || 0;
|
|
545
|
+
if (role === "user") inputTokens += t;
|
|
546
|
+
else if (role === "assistant") outputTokens += t;
|
|
547
|
+
}
|
|
548
|
+
if (!text.trim()) continue;
|
|
549
|
+
let data;
|
|
550
|
+
if (role === "user") {
|
|
551
|
+
data = { eventType: "user_message", text };
|
|
552
|
+
} else if (role === "assistant") {
|
|
553
|
+
data = { eventType: "assistant_text", text };
|
|
554
|
+
} else {
|
|
555
|
+
data = { eventType: "system", subtype: "cursor" };
|
|
556
|
+
}
|
|
557
|
+
events.push({
|
|
558
|
+
id: `evt-${eventIndex}`,
|
|
559
|
+
index: eventIndex,
|
|
560
|
+
timestamp,
|
|
561
|
+
data
|
|
562
|
+
});
|
|
563
|
+
eventIndex++;
|
|
564
|
+
}
|
|
565
|
+
const startedAt = getConvTimestamp(conv, "start");
|
|
566
|
+
const lastActiveAt = getConvTimestamp(conv, "end");
|
|
567
|
+
const slug = conv.title || conv.name || getMessageText(messages.find((m) => getMessageRole(m) === "user") || messages[0]).slice(0, 60) || "Cursor conversation";
|
|
568
|
+
const summary = {
|
|
569
|
+
sessionId,
|
|
570
|
+
project: meta.project || "unknown",
|
|
571
|
+
slug,
|
|
572
|
+
startedAt,
|
|
573
|
+
lastActiveAt,
|
|
574
|
+
model: model || "unknown",
|
|
575
|
+
version: "",
|
|
576
|
+
eventCount: events.length,
|
|
577
|
+
toolCallCount: 0,
|
|
578
|
+
inputTokens,
|
|
579
|
+
outputTokens,
|
|
580
|
+
estimatedCost: estimateCost(model, inputTokens, outputTokens),
|
|
581
|
+
filePath: meta.dbPath,
|
|
582
|
+
provider: "cursor",
|
|
583
|
+
providerMeta: meta
|
|
584
|
+
};
|
|
585
|
+
return { summary, events };
|
|
586
|
+
}
|
|
587
|
+
const providers = [
|
|
588
|
+
new ClaudeCodeProvider(),
|
|
589
|
+
new CursorProvider()
|
|
590
|
+
];
|
|
591
|
+
const providerMap = new Map(
|
|
592
|
+
providers.map((p) => [p.type, p])
|
|
593
|
+
);
|
|
594
|
+
async function discoverAllSessions() {
|
|
595
|
+
const results = await Promise.allSettled(
|
|
596
|
+
providers.map((p) => p.discoverSessions())
|
|
597
|
+
);
|
|
598
|
+
const sessions = [];
|
|
599
|
+
for (const result of results) {
|
|
600
|
+
if (result.status === "fulfilled") {
|
|
601
|
+
sessions.push(...result.value);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
sessions.sort((a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime());
|
|
605
|
+
return sessions;
|
|
606
|
+
}
|
|
607
|
+
async function parseSessionByProvider(sessionId, provider, meta) {
|
|
608
|
+
const p = providerMap.get(provider);
|
|
609
|
+
if (!p) throw new Error(`Unknown provider: ${provider}`);
|
|
610
|
+
return p.parseSession(sessionId, meta);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export { CLAUDE_DIR as C, parseSession as a, discoverAllSessions as d, parseSessionByProvider as p };
|
|
614
|
+
//# sourceMappingURL=index2-f44F-4KW.js.map
|