claude-code-history 0.2.2
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 +400 -0
- package/dist/cli/commands/export.d.ts +11 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +119 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/list.d.ts +11 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +79 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/migrate.d.ts +11 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -0
- package/dist/cli/commands/migrate.js +144 -0
- package/dist/cli/commands/migrate.js.map +1 -0
- package/dist/cli/commands/search.d.ts +11 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +94 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/view.d.ts +11 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +67 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/formatters/pager.d.ts +25 -0
- package/dist/cli/formatters/pager.d.ts.map +1 -0
- package/dist/cli/formatters/pager.js +119 -0
- package/dist/cli/formatters/pager.js.map +1 -0
- package/dist/cli/formatters/search.d.ts +38 -0
- package/dist/cli/formatters/search.d.ts.map +1 -0
- package/dist/cli/formatters/search.js +119 -0
- package/dist/cli/formatters/search.js.map +1 -0
- package/dist/cli/formatters/session.d.ts +24 -0
- package/dist/cli/formatters/session.d.ts.map +1 -0
- package/dist/cli/formatters/session.js +247 -0
- package/dist/cli/formatters/session.js.map +1 -0
- package/dist/cli/formatters/table.d.ts +25 -0
- package/dist/cli/formatters/table.d.ts.map +1 -0
- package/dist/cli/formatters/table.js +126 -0
- package/dist/cli/formatters/table.js.map +1 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +50 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/config.d.ts +62 -0
- package/dist/cli/utils/config.d.ts.map +1 -0
- package/dist/cli/utils/config.js +65 -0
- package/dist/cli/utils/config.js.map +1 -0
- package/dist/cli/utils/errors.d.ts +86 -0
- package/dist/cli/utils/errors.d.ts.map +1 -0
- package/dist/cli/utils/errors.js +129 -0
- package/dist/cli/utils/errors.js.map +1 -0
- package/dist/cli/utils/output.d.ts +74 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +113 -0
- package/dist/cli/utils/output.js.map +1 -0
- package/dist/lib/config.d.ts +44 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +60 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/errors.d.ts +40 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +61 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/export.d.ts +70 -0
- package/dist/lib/export.d.ts.map +1 -0
- package/dist/lib/export.js +241 -0
- package/dist/lib/export.js.map +1 -0
- package/dist/lib/index.d.ts +16 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +33 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/migrate.d.ts +50 -0
- package/dist/lib/migrate.d.ts.map +1 -0
- package/dist/lib/migrate.js +274 -0
- package/dist/lib/migrate.js.map +1 -0
- package/dist/lib/parser.d.ts +67 -0
- package/dist/lib/parser.d.ts.map +1 -0
- package/dist/lib/parser.js +321 -0
- package/dist/lib/parser.js.map +1 -0
- package/dist/lib/platform.d.ts +51 -0
- package/dist/lib/platform.d.ts.map +1 -0
- package/dist/lib/platform.js +94 -0
- package/dist/lib/platform.js.map +1 -0
- package/dist/lib/search.d.ts +39 -0
- package/dist/lib/search.d.ts.map +1 -0
- package/dist/lib/search.js +217 -0
- package/dist/lib/search.js.map +1 -0
- package/dist/lib/session.d.ts +59 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +326 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/types.d.ts +280 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +7 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration functionality for claude-code-history library.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to copy/move sessions between workspaces
|
|
5
|
+
* while preserving rollback functionality.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, mkdir, unlink, readdir } from 'fs/promises';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { resolveConfig } from './config.js';
|
|
10
|
+
import { getProjectsPath, encodeProjectPath } from './platform.js';
|
|
11
|
+
import { getSession } from './session.js';
|
|
12
|
+
import { WorkspaceNotFoundError } from './errors.js';
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Path Rewriting
|
|
15
|
+
// =============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Rewrite a path from source workspace to destination workspace.
|
|
18
|
+
* Only rewrites paths that start with the source workspace.
|
|
19
|
+
*/
|
|
20
|
+
function rewritePath(path, sourceWorkspace, destWorkspace) {
|
|
21
|
+
// Normalize paths (remove trailing slashes)
|
|
22
|
+
const normalizedSource = sourceWorkspace.replace(/\/+$/, '');
|
|
23
|
+
const normalizedDest = destWorkspace.replace(/\/+$/, '');
|
|
24
|
+
if (path.startsWith(normalizedSource)) {
|
|
25
|
+
return normalizedDest + path.slice(normalizedSource.length);
|
|
26
|
+
}
|
|
27
|
+
return path;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Rewrite paths in tool_use input objects.
|
|
31
|
+
*/
|
|
32
|
+
function rewriteToolInput(input, sourceWorkspace, destWorkspace) {
|
|
33
|
+
const result = {};
|
|
34
|
+
for (const [key, value] of Object.entries(input)) {
|
|
35
|
+
if (key === 'file_path' && typeof value === 'string') {
|
|
36
|
+
result[key] = rewritePath(value, sourceWorkspace, destWorkspace);
|
|
37
|
+
}
|
|
38
|
+
else if (key === 'path' && typeof value === 'string') {
|
|
39
|
+
result[key] = rewritePath(value, sourceWorkspace, destWorkspace);
|
|
40
|
+
}
|
|
41
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
42
|
+
result[key] = rewriteToolInput(value, sourceWorkspace, destWorkspace);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
result[key] = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Rewrite paths in message content.
|
|
52
|
+
*/
|
|
53
|
+
function rewriteMessageContent(content, sourceWorkspace, destWorkspace) {
|
|
54
|
+
if (!Array.isArray(content)) {
|
|
55
|
+
return content;
|
|
56
|
+
}
|
|
57
|
+
return content.map((block) => {
|
|
58
|
+
if (typeof block !== 'object' || block === null) {
|
|
59
|
+
return block;
|
|
60
|
+
}
|
|
61
|
+
const typed = block;
|
|
62
|
+
// Rewrite tool_use inputs
|
|
63
|
+
if (typed.type === 'tool_use' && typeof typed.input === 'object' && typed.input !== null) {
|
|
64
|
+
return {
|
|
65
|
+
...typed,
|
|
66
|
+
input: rewriteToolInput(typed.input, sourceWorkspace, destWorkspace),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return block;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Rewrite paths in trackedFileBackups.
|
|
74
|
+
*/
|
|
75
|
+
function rewriteTrackedFileBackups(backups, sourceWorkspace, destWorkspace) {
|
|
76
|
+
const result = {};
|
|
77
|
+
for (const [filePath, backup] of Object.entries(backups)) {
|
|
78
|
+
const newPath = rewritePath(filePath, sourceWorkspace, destWorkspace);
|
|
79
|
+
result[newPath] = backup;
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Rewrite all paths in a raw session entry.
|
|
85
|
+
*/
|
|
86
|
+
function rewriteEntryPaths(entry, sourceWorkspace, destWorkspace) {
|
|
87
|
+
const result = { ...entry };
|
|
88
|
+
// Rewrite cwd field
|
|
89
|
+
if (result.cwd) {
|
|
90
|
+
result.cwd = rewritePath(result.cwd, sourceWorkspace, destWorkspace);
|
|
91
|
+
}
|
|
92
|
+
// Rewrite message content (for tool_use inputs)
|
|
93
|
+
if (result.message?.content !== undefined) {
|
|
94
|
+
result.message = {
|
|
95
|
+
...result.message,
|
|
96
|
+
content: rewriteMessageContent(result.message.content, sourceWorkspace, destWorkspace),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Rewrite file-history-snapshot paths
|
|
100
|
+
if (result.type === 'file-history-snapshot' && result.snapshot) {
|
|
101
|
+
result.snapshot = {
|
|
102
|
+
...result.snapshot,
|
|
103
|
+
trackedFileBackups: rewriteTrackedFileBackups(result.snapshot.trackedFileBackups, sourceWorkspace, destWorkspace),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
// =============================================================================
|
|
109
|
+
// Migration Implementation
|
|
110
|
+
// =============================================================================
|
|
111
|
+
/**
|
|
112
|
+
* Read and parse a session file, rewrite paths, and return as JSONL string.
|
|
113
|
+
*/
|
|
114
|
+
async function readAndRewriteSession(filePath, sourceWorkspace, destWorkspace) {
|
|
115
|
+
const content = await readFile(filePath, 'utf-8');
|
|
116
|
+
const lines = content.split('\n').filter((line) => line.trim());
|
|
117
|
+
const rewrittenLines = [];
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
try {
|
|
120
|
+
const entry = JSON.parse(line);
|
|
121
|
+
const rewritten = rewriteEntryPaths(entry, sourceWorkspace, destWorkspace);
|
|
122
|
+
rewrittenLines.push(JSON.stringify(rewritten));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Keep original line if parse fails
|
|
126
|
+
rewrittenLines.push(line);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return rewrittenLines.join('\n') + '\n';
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Migrate a single session file.
|
|
133
|
+
*/
|
|
134
|
+
async function migrateSessionFile(sessionId, sourceFilePath, sourceWorkspace, destWorkspace, dataPath, mode) {
|
|
135
|
+
// Compute destination path
|
|
136
|
+
const destEncodedPath = encodeProjectPath(destWorkspace);
|
|
137
|
+
const destProjectDir = join(getProjectsPath(dataPath), destEncodedPath);
|
|
138
|
+
const destFilePath = join(destProjectDir, `${sessionId}.jsonl`);
|
|
139
|
+
// Ensure destination directory exists
|
|
140
|
+
await mkdir(destProjectDir, { recursive: true });
|
|
141
|
+
// Read, rewrite paths, and write to destination
|
|
142
|
+
const rewrittenContent = await readAndRewriteSession(sourceFilePath, sourceWorkspace, destWorkspace);
|
|
143
|
+
await writeFile(destFilePath, rewrittenContent, 'utf-8');
|
|
144
|
+
// Delete source if moving
|
|
145
|
+
if (mode === 'move') {
|
|
146
|
+
await unlink(sourceFilePath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Migrate sessions to a different workspace.
|
|
151
|
+
*
|
|
152
|
+
* This function copies or moves session files while rewriting all
|
|
153
|
+
* absolute paths to point to the new workspace location.
|
|
154
|
+
*
|
|
155
|
+
* @param config - Migration configuration
|
|
156
|
+
* @returns Migration result with success/failure counts
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* // Copy a single session
|
|
161
|
+
* const result = await migrateSession({
|
|
162
|
+
* sessions: 0,
|
|
163
|
+
* destination: '/new/project/path',
|
|
164
|
+
* });
|
|
165
|
+
*
|
|
166
|
+
* // Move multiple sessions
|
|
167
|
+
* const result = await migrateSession({
|
|
168
|
+
* sessions: [0, 1, 'abc123-...'],
|
|
169
|
+
* destination: '/new/project/path',
|
|
170
|
+
* mode: 'move',
|
|
171
|
+
* });
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export async function migrateSession(config, libraryConfig) {
|
|
175
|
+
const resolved = resolveConfig(libraryConfig);
|
|
176
|
+
const mode = config.mode ?? 'copy';
|
|
177
|
+
// Normalize sessions to array
|
|
178
|
+
const sessionIds = Array.isArray(config.sessions) ? config.sessions : [config.sessions];
|
|
179
|
+
const errors = [];
|
|
180
|
+
let successCount = 0;
|
|
181
|
+
for (const sessionId of sessionIds) {
|
|
182
|
+
try {
|
|
183
|
+
// Get session to find its file path and source workspace
|
|
184
|
+
const session = await getSession(sessionId, {
|
|
185
|
+
dataPath: resolved.dataPath,
|
|
186
|
+
workspace: resolved.workspace,
|
|
187
|
+
});
|
|
188
|
+
const sourceFilePath = join(getProjectsPath(resolved.dataPath), session.encodedPath, `${session.id}.jsonl`);
|
|
189
|
+
await migrateSessionFile(session.id, sourceFilePath, session.projectPath, config.destination, resolved.dataPath, mode);
|
|
190
|
+
successCount++;
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
errors.push({
|
|
194
|
+
sessionId: String(sessionId),
|
|
195
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
successCount,
|
|
201
|
+
failedCount: errors.length,
|
|
202
|
+
errors,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Migrate all sessions from one workspace to another.
|
|
207
|
+
*
|
|
208
|
+
* @param config - Workspace migration configuration
|
|
209
|
+
* @returns Migration result with success/failure counts
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* const result = await migrateWorkspace({
|
|
214
|
+
* source: '/old/project/path',
|
|
215
|
+
* destination: '/new/project/path',
|
|
216
|
+
* mode: 'move',
|
|
217
|
+
* });
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
export async function migrateWorkspace(config, libraryConfig) {
|
|
221
|
+
const resolved = resolveConfig(libraryConfig);
|
|
222
|
+
const mode = config.mode ?? 'copy';
|
|
223
|
+
// Find all sessions in source workspace
|
|
224
|
+
const sourceEncodedPath = encodeProjectPath(config.source);
|
|
225
|
+
const sourceProjectDir = join(getProjectsPath(resolved.dataPath), sourceEncodedPath);
|
|
226
|
+
// Check if source workspace exists
|
|
227
|
+
let files;
|
|
228
|
+
try {
|
|
229
|
+
files = await readdir(sourceProjectDir);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
throw new WorkspaceNotFoundError(config.source);
|
|
233
|
+
}
|
|
234
|
+
// Filter to session files only
|
|
235
|
+
const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'));
|
|
236
|
+
const errors = [];
|
|
237
|
+
let successCount = 0;
|
|
238
|
+
for (const filename of sessionFiles) {
|
|
239
|
+
const sessionId = filename.replace('.jsonl', '');
|
|
240
|
+
const sourceFilePath = join(sourceProjectDir, filename);
|
|
241
|
+
try {
|
|
242
|
+
await migrateSessionFile(sessionId, sourceFilePath, config.source, config.destination, resolved.dataPath, mode);
|
|
243
|
+
successCount++;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
errors.push({
|
|
247
|
+
sessionId,
|
|
248
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Also migrate agent sessions
|
|
253
|
+
const agentFiles = files.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'));
|
|
254
|
+
for (const filename of agentFiles) {
|
|
255
|
+
const agentId = filename.replace('.jsonl', '');
|
|
256
|
+
const sourceFilePath = join(sourceProjectDir, filename);
|
|
257
|
+
try {
|
|
258
|
+
await migrateSessionFile(agentId, sourceFilePath, config.source, config.destination, resolved.dataPath, mode);
|
|
259
|
+
successCount++;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
errors.push({
|
|
263
|
+
sessionId: agentId,
|
|
264
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
successCount,
|
|
270
|
+
failedCount: errors.length,
|
|
271
|
+
errors,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=migrate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/lib/migrate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAU5B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAErD,gFAAgF;AAChF,iBAAiB;AACjB,gFAAgF;AAEhF;;;GAGG;AACH,SAAS,WAAW,CAAC,IAAY,EAAE,eAAuB,EAAE,aAAqB;IAC/E,4CAA4C;IAC5C,MAAM,gBAAgB,GAAG,eAAe,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC7D,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEzD,IAAI,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACtC,OAAO,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CACvB,KAA8B,EAC9B,eAAuB,EACvB,aAAqB;IAErB,MAAM,MAAM,GAA4B,EAAE,CAAC;IAE3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,GAAG,KAAK,WAAW,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACrD,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;QACnE,CAAC;aAAM,IAAI,GAAG,KAAK,MAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvD,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;QACnE,CAAC;aAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChF,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAC5B,KAAgC,EAChC,eAAe,EACf,aAAa,CACd,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,qBAAqB,CAC5B,OAAgB,EAChB,eAAuB,EACvB,aAAqB;IAErB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAChD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,KAAK,GAAG,KAAgC,CAAC;QAE/C,0BAA0B;QAC1B,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACzF,OAAO;gBACL,GAAG,KAAK;gBACR,KAAK,EAAE,gBAAgB,CACrB,KAAK,CAAC,KAAgC,EACtC,eAAe,EACf,aAAa,CACd;aACF,CAAC;QACJ,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAS,yBAAyB,CAChC,OAA8C,EAC9C,eAAuB,EACvB,aAAqB;IAErB,MAAM,MAAM,GAA0C,EAAE,CAAC;IAEzD,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACzD,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;QACtE,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC;IAC3B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CACxB,KAAsB,EACtB,eAAuB,EACvB,aAAqB;IAErB,MAAM,MAAM,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;IAE5B,oBAAoB;IACpB,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;QACf,MAAM,CAAC,GAAG,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;IACvE,CAAC;IAED,gDAAgD;IAChD,IAAI,MAAM,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1C,MAAM,CAAC,OAAO,GAAG;YACf,GAAG,MAAM,CAAC,OAAO;YACjB,OAAO,EAAE,qBAAqB,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,aAAa,CAAC;SACvF,CAAC;IACJ,CAAC;IAED,sCAAsC;IACtC,IAAI,MAAM,CAAC,IAAI,KAAK,uBAAuB,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC/D,MAAM,CAAC,QAAQ,GAAG;YAChB,GAAG,MAAM,CAAC,QAAQ;YAClB,kBAAkB,EAAE,yBAAyB,CAC3C,MAAM,CAAC,QAAQ,CAAC,kBAAkB,EAClC,eAAe,EACf,aAAa,CACd;SACF,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gFAAgF;AAChF,2BAA2B;AAC3B,gFAAgF;AAEhF;;GAEG;AACH,KAAK,UAAU,qBAAqB,CAClC,QAAgB,EAChB,eAAuB,EACvB,aAAqB;IAErB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAEhE,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;YAClD,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;YAC3E,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,oCAAoC;YACpC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB,CAC/B,SAAiB,EACjB,cAAsB,EACtB,eAAuB,EACvB,aAAqB,EACrB,QAAgB,EAChB,IAAqB;IAErB,2BAA2B;IAC3B,MAAM,eAAe,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;IACzD,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,CAAC;IACxE,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,SAAS,QAAQ,CAAC,CAAC;IAEhE,sCAAsC;IACtC,MAAM,KAAK,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEjD,gDAAgD;IAChD,MAAM,gBAAgB,GAAG,MAAM,qBAAqB,CAClD,cAAc,EACd,eAAe,EACf,aAAa,CACd,CAAC;IACF,MAAM,SAAS,CAAC,YAAY,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAC;IAEzD,0BAA0B;IAC1B,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAqB,EACrB,aAA6B;IAE7B,MAAM,QAAQ,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC;IAEnC,8BAA8B;IAC9B,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAExF,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,yDAAyD;YACzD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE;gBAC1C,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;aAC9B,CAAC,CAAC;YAEH,MAAM,cAAc,GAAG,IAAI,CACzB,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAClC,OAAO,CAAC,WAAW,EACnB,GAAG,OAAO,CAAC,EAAE,QAAQ,CACtB,CAAC;YAEF,MAAM,kBAAkB,CACtB,OAAO,CAAC,EAAE,EACV,cAAc,EACd,OAAO,CAAC,WAAW,EACnB,MAAM,CAAC,WAAW,EAClB,QAAQ,CAAC,QAAQ,EACjB,IAAI,CACL,CAAC;YAEF,YAAY,EAAE,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC;gBACV,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC;gBAC5B,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;aAChE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO;QACL,YAAY;QACZ,WAAW,EAAE,MAAM,CAAC,MAAM;QAC1B,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAA8B,EAC9B,aAA6B;IAE7B,MAAM,QAAQ,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC;IAEnC,wCAAwC;IACxC,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3D,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAErF,mCAAmC;IACnC,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,sBAAsB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;IAED,+BAA+B;IAC/B,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE1F,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,KAAK,MAAM,QAAQ,IAAI,YAAY,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACjD,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QAExD,IAAI,CAAC;YACH,MAAM,kBAAkB,CACtB,SAAS,EACT,cAAc,EACd,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,WAAW,EAClB,QAAQ,CAAC,QAAQ,EACjB,IAAI,CACL,CAAC;YACF,YAAY,EAAE,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC;gBACV,SAAS;gBACT,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;aAChE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEvF,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC/C,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QAExD,IAAI,CAAC;YACH,MAAM,kBAAkB,CACtB,OAAO,EACP,cAAc,EACd,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,WAAW,EAClB,QAAQ,CAAC,QAAQ,EACjB,IAAI,CACL,CAAC;YACF,YAAY,EAAE,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC;gBACV,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;aAChE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO;QACL,YAAY;QACZ,WAAW,EAAE,MAAM,CAAC,MAAM;QAC1B,MAAM;KACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL parser for Claude Code session files.
|
|
3
|
+
*
|
|
4
|
+
* Provides stream-based parsing with error recovery:
|
|
5
|
+
* - Skips invalid JSON lines and continues processing
|
|
6
|
+
* - Tracks parse warnings for reporting
|
|
7
|
+
* - Transforms raw entries into typed Message objects
|
|
8
|
+
*/
|
|
9
|
+
import type { RawSessionEntry, Message, ParseResult, ParseWarning } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Parse a single JSONL line into a raw session entry.
|
|
12
|
+
* @param line - Raw JSON line from session file
|
|
13
|
+
* @param lineNumber - Line number for error reporting
|
|
14
|
+
* @returns Parsed entry or null if invalid
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseJsonLine(line: string, lineNumber: number): {
|
|
17
|
+
entry: RawSessionEntry;
|
|
18
|
+
warning: null;
|
|
19
|
+
} | {
|
|
20
|
+
entry: null;
|
|
21
|
+
warning: ParseWarning;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Parse all lines from a JSONL file.
|
|
25
|
+
* @param filePath - Path to the JSONL session file
|
|
26
|
+
* @returns Array of raw entries and any parse warnings
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseJsonlFile(filePath: string): Promise<ParseResult<RawSessionEntry[]>>;
|
|
29
|
+
/**
|
|
30
|
+
* Transform a raw session entry into a typed Message.
|
|
31
|
+
* @param entry - Raw parsed entry from JSONL
|
|
32
|
+
* @returns Typed Message or null if not a message entry
|
|
33
|
+
*/
|
|
34
|
+
export declare function transformEntry(entry: RawSessionEntry): Message | null;
|
|
35
|
+
/**
|
|
36
|
+
* Parse a JSONL session file into typed Messages.
|
|
37
|
+
* @param filePath - Path to the JSONL session file
|
|
38
|
+
* @returns Parsed messages and warnings
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseSessionFile(filePath: string): Promise<ParseResult<Message[]>>;
|
|
41
|
+
/**
|
|
42
|
+
* Extract session metadata from raw entries (summary, version, etc.).
|
|
43
|
+
* Reads only the necessary fields without full message parsing.
|
|
44
|
+
*/
|
|
45
|
+
export interface SessionMetadata {
|
|
46
|
+
summary: string | null;
|
|
47
|
+
version: string;
|
|
48
|
+
gitBranch: string | null;
|
|
49
|
+
sessionId: string | null;
|
|
50
|
+
agentId: string | null;
|
|
51
|
+
firstTimestamp: Date | null;
|
|
52
|
+
lastTimestamp: Date | null;
|
|
53
|
+
messageCount: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Extract metadata from raw session entries.
|
|
57
|
+
* @param entries - Raw parsed entries
|
|
58
|
+
* @returns Session metadata
|
|
59
|
+
*/
|
|
60
|
+
export declare function extractMetadata(entries: RawSessionEntry[]): SessionMetadata;
|
|
61
|
+
/**
|
|
62
|
+
* Quick parse to extract only session metadata (faster than full parse).
|
|
63
|
+
* @param filePath - Path to the JSONL session file
|
|
64
|
+
* @returns Session metadata and warnings
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseSessionMetadata(filePath: string): Promise<ParseResult<SessionMetadata>>;
|
|
67
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/lib/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EACV,eAAe,EAIf,OAAO,EAaP,WAAW,EACX,YAAY,EACb,MAAM,YAAY,CAAC;AAMpB;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GACjB;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAsBpF;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC,CAAC,CAyB9F;AA2HD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,GAAG,IAAI,CAiErE;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAaxF;AAMD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,eAAe,CAwD3E;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAIvC"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL parser for Claude Code session files.
|
|
3
|
+
*
|
|
4
|
+
* Provides stream-based parsing with error recovery:
|
|
5
|
+
* - Skips invalid JSON lines and continues processing
|
|
6
|
+
* - Tracks parse warnings for reporting
|
|
7
|
+
* - Transforms raw entries into typed Message objects
|
|
8
|
+
*/
|
|
9
|
+
import { createReadStream } from 'fs';
|
|
10
|
+
import { createInterface } from 'readline';
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Raw Entry Parsing
|
|
13
|
+
// =============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Parse a single JSONL line into a raw session entry.
|
|
16
|
+
* @param line - Raw JSON line from session file
|
|
17
|
+
* @param lineNumber - Line number for error reporting
|
|
18
|
+
* @returns Parsed entry or null if invalid
|
|
19
|
+
*/
|
|
20
|
+
export function parseJsonLine(line, lineNumber) {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
// Skip empty lines
|
|
23
|
+
if (!trimmed) {
|
|
24
|
+
return { entry: null, warning: { line: lineNumber, error: 'Empty line' } };
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const entry = JSON.parse(trimmed);
|
|
28
|
+
return { entry, warning: null };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown parse error';
|
|
32
|
+
return {
|
|
33
|
+
entry: null,
|
|
34
|
+
warning: {
|
|
35
|
+
line: lineNumber,
|
|
36
|
+
error: `Invalid JSON: ${errorMessage}`,
|
|
37
|
+
content: trimmed.length > 100 ? trimmed.slice(0, 100) + '...' : trimmed,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse all lines from a JSONL file.
|
|
44
|
+
* @param filePath - Path to the JSONL session file
|
|
45
|
+
* @returns Array of raw entries and any parse warnings
|
|
46
|
+
*/
|
|
47
|
+
export async function parseJsonlFile(filePath) {
|
|
48
|
+
const entries = [];
|
|
49
|
+
const warnings = [];
|
|
50
|
+
const fileStream = createReadStream(filePath, { encoding: 'utf-8' });
|
|
51
|
+
const rl = createInterface({
|
|
52
|
+
input: fileStream,
|
|
53
|
+
crlfDelay: Infinity,
|
|
54
|
+
});
|
|
55
|
+
let lineNumber = 0;
|
|
56
|
+
for await (const line of rl) {
|
|
57
|
+
lineNumber++;
|
|
58
|
+
const result = parseJsonLine(line, lineNumber);
|
|
59
|
+
if (result.entry) {
|
|
60
|
+
entries.push(result.entry);
|
|
61
|
+
}
|
|
62
|
+
else if (result.warning && result.warning.error !== 'Empty line') {
|
|
63
|
+
// Only track non-empty line warnings
|
|
64
|
+
warnings.push(result.warning);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { data: entries, warnings };
|
|
68
|
+
}
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Message Transformation
|
|
71
|
+
// =============================================================================
|
|
72
|
+
/**
|
|
73
|
+
* Transform raw token usage to typed TokenUsage.
|
|
74
|
+
*/
|
|
75
|
+
function transformTokenUsage(raw) {
|
|
76
|
+
return {
|
|
77
|
+
inputTokens: raw?.input_tokens ?? 0,
|
|
78
|
+
outputTokens: raw?.output_tokens ?? 0,
|
|
79
|
+
cacheCreationInputTokens: raw?.cache_creation_input_tokens ?? 0,
|
|
80
|
+
cacheReadInputTokens: raw?.cache_read_input_tokens ?? 0,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Transform raw file snapshot to typed FileSnapshot.
|
|
85
|
+
*/
|
|
86
|
+
function transformFileSnapshot(raw) {
|
|
87
|
+
const trackedFileBackups = {};
|
|
88
|
+
for (const [path, backup] of Object.entries(raw.trackedFileBackups)) {
|
|
89
|
+
trackedFileBackups[path] = {
|
|
90
|
+
backupFileName: backup.backupFileName,
|
|
91
|
+
version: backup.version,
|
|
92
|
+
backupTime: new Date(backup.backupTime),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
messageId: raw.messageId,
|
|
97
|
+
timestamp: new Date(raw.timestamp),
|
|
98
|
+
trackedFileBackups,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse assistant content from raw message.
|
|
103
|
+
*/
|
|
104
|
+
function parseAssistantContent(rawContent) {
|
|
105
|
+
if (!Array.isArray(rawContent)) {
|
|
106
|
+
// Handle string content (rare but possible)
|
|
107
|
+
if (typeof rawContent === 'string') {
|
|
108
|
+
return [{ type: 'text', text: rawContent }];
|
|
109
|
+
}
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
return rawContent
|
|
113
|
+
.map((item) => {
|
|
114
|
+
if (typeof item !== 'object' || item === null) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const typed = item;
|
|
118
|
+
switch (typed.type) {
|
|
119
|
+
case 'text':
|
|
120
|
+
return {
|
|
121
|
+
type: 'text',
|
|
122
|
+
text: String(typed.text ?? ''),
|
|
123
|
+
};
|
|
124
|
+
case 'tool_use':
|
|
125
|
+
return {
|
|
126
|
+
type: 'tool_use',
|
|
127
|
+
id: String(typed.id ?? ''),
|
|
128
|
+
name: String(typed.name ?? ''),
|
|
129
|
+
input: typed.input ?? {},
|
|
130
|
+
};
|
|
131
|
+
case 'thinking':
|
|
132
|
+
return {
|
|
133
|
+
type: 'thinking',
|
|
134
|
+
thinking: String(typed.thinking ?? ''),
|
|
135
|
+
};
|
|
136
|
+
default:
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
.filter((item) => item !== null);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Parse user content from raw message.
|
|
144
|
+
*/
|
|
145
|
+
function parseUserContent(rawContent) {
|
|
146
|
+
// String content (normal user message)
|
|
147
|
+
if (typeof rawContent === 'string') {
|
|
148
|
+
return rawContent;
|
|
149
|
+
}
|
|
150
|
+
// Array content (tool results)
|
|
151
|
+
if (Array.isArray(rawContent)) {
|
|
152
|
+
return rawContent
|
|
153
|
+
.map((item) => {
|
|
154
|
+
if (typeof item !== 'object' || item === null) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const typed = item;
|
|
158
|
+
if (typed.type === 'tool_result') {
|
|
159
|
+
return {
|
|
160
|
+
type: 'tool_result',
|
|
161
|
+
tool_use_id: String(typed.tool_use_id ?? ''),
|
|
162
|
+
content: String(typed.content ?? ''),
|
|
163
|
+
is_error: typed.is_error === true ? true : undefined,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
})
|
|
168
|
+
.filter((item) => item !== null);
|
|
169
|
+
}
|
|
170
|
+
return '';
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Transform a raw session entry into a typed Message.
|
|
174
|
+
* @param entry - Raw parsed entry from JSONL
|
|
175
|
+
* @returns Typed Message or null if not a message entry
|
|
176
|
+
*/
|
|
177
|
+
export function transformEntry(entry) {
|
|
178
|
+
const timestamp = entry.timestamp ? new Date(entry.timestamp) : new Date();
|
|
179
|
+
const uuid = entry.uuid ?? '';
|
|
180
|
+
const parentUuid = entry.parentUuid ?? null;
|
|
181
|
+
switch (entry.type) {
|
|
182
|
+
case 'user': {
|
|
183
|
+
const message = entry.message;
|
|
184
|
+
return {
|
|
185
|
+
type: 'user',
|
|
186
|
+
uuid,
|
|
187
|
+
parentUuid,
|
|
188
|
+
timestamp,
|
|
189
|
+
role: 'user',
|
|
190
|
+
content: parseUserContent(message?.content),
|
|
191
|
+
cwd: entry.cwd ?? '',
|
|
192
|
+
gitBranch: entry.gitBranch ?? null,
|
|
193
|
+
isSidechain: entry.isSidechain ?? false,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
case 'assistant': {
|
|
197
|
+
const message = entry.message;
|
|
198
|
+
return {
|
|
199
|
+
type: 'assistant',
|
|
200
|
+
uuid,
|
|
201
|
+
parentUuid,
|
|
202
|
+
timestamp,
|
|
203
|
+
role: 'assistant',
|
|
204
|
+
model: message?.model ?? '',
|
|
205
|
+
content: parseAssistantContent(message?.content),
|
|
206
|
+
stopReason: message?.stop_reason ?? null,
|
|
207
|
+
usage: transformTokenUsage(message?.usage),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
case 'summary': {
|
|
211
|
+
return {
|
|
212
|
+
type: 'summary',
|
|
213
|
+
uuid,
|
|
214
|
+
parentUuid,
|
|
215
|
+
timestamp,
|
|
216
|
+
summary: entry.summary ?? '',
|
|
217
|
+
leafUuid: entry.leafUuid ?? '',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
case 'file-history-snapshot': {
|
|
221
|
+
if (!entry.snapshot) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
type: 'file-history-snapshot',
|
|
226
|
+
uuid,
|
|
227
|
+
parentUuid,
|
|
228
|
+
timestamp,
|
|
229
|
+
messageId: entry.messageId ?? '',
|
|
230
|
+
snapshot: transformFileSnapshot(entry.snapshot),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
default:
|
|
234
|
+
// Unknown entry type - skip
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Parse a JSONL session file into typed Messages.
|
|
240
|
+
* @param filePath - Path to the JSONL session file
|
|
241
|
+
* @returns Parsed messages and warnings
|
|
242
|
+
*/
|
|
243
|
+
export async function parseSessionFile(filePath) {
|
|
244
|
+
const { data: entries, warnings } = await parseJsonlFile(filePath);
|
|
245
|
+
const messages = [];
|
|
246
|
+
for (const entry of entries) {
|
|
247
|
+
const message = transformEntry(entry);
|
|
248
|
+
if (message) {
|
|
249
|
+
messages.push(message);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { data: messages, warnings };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Extract metadata from raw session entries.
|
|
256
|
+
* @param entries - Raw parsed entries
|
|
257
|
+
* @returns Session metadata
|
|
258
|
+
*/
|
|
259
|
+
export function extractMetadata(entries) {
|
|
260
|
+
let summary = null;
|
|
261
|
+
let version = '';
|
|
262
|
+
let gitBranch = null;
|
|
263
|
+
let sessionId = null;
|
|
264
|
+
let agentId = null;
|
|
265
|
+
let firstTimestamp = null;
|
|
266
|
+
let lastTimestamp = null;
|
|
267
|
+
let messageCount = 0;
|
|
268
|
+
for (const entry of entries) {
|
|
269
|
+
// Extract summary from summary entry
|
|
270
|
+
if (entry.type === 'summary' && entry.summary) {
|
|
271
|
+
summary = entry.summary;
|
|
272
|
+
}
|
|
273
|
+
// Extract version and git branch from any entry that has them
|
|
274
|
+
if (entry.version && !version) {
|
|
275
|
+
version = entry.version;
|
|
276
|
+
}
|
|
277
|
+
if (entry.gitBranch !== undefined && gitBranch === null) {
|
|
278
|
+
gitBranch = entry.gitBranch;
|
|
279
|
+
}
|
|
280
|
+
if (entry.sessionId && !sessionId) {
|
|
281
|
+
sessionId = entry.sessionId;
|
|
282
|
+
}
|
|
283
|
+
if (entry.agentId && !agentId) {
|
|
284
|
+
agentId = entry.agentId;
|
|
285
|
+
}
|
|
286
|
+
// Track timestamps for user/assistant messages only
|
|
287
|
+
if (entry.type === 'user' || entry.type === 'assistant') {
|
|
288
|
+
messageCount++;
|
|
289
|
+
if (entry.timestamp) {
|
|
290
|
+
const ts = new Date(entry.timestamp);
|
|
291
|
+
if (!firstTimestamp || ts < firstTimestamp) {
|
|
292
|
+
firstTimestamp = ts;
|
|
293
|
+
}
|
|
294
|
+
if (!lastTimestamp || ts > lastTimestamp) {
|
|
295
|
+
lastTimestamp = ts;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
summary,
|
|
302
|
+
version,
|
|
303
|
+
gitBranch,
|
|
304
|
+
sessionId,
|
|
305
|
+
agentId,
|
|
306
|
+
firstTimestamp,
|
|
307
|
+
lastTimestamp,
|
|
308
|
+
messageCount,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Quick parse to extract only session metadata (faster than full parse).
|
|
313
|
+
* @param filePath - Path to the JSONL session file
|
|
314
|
+
* @returns Session metadata and warnings
|
|
315
|
+
*/
|
|
316
|
+
export async function parseSessionMetadata(filePath) {
|
|
317
|
+
const { data: entries, warnings } = await parseJsonlFile(filePath);
|
|
318
|
+
const metadata = extractMetadata(entries);
|
|
319
|
+
return { data: metadata, warnings };
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=parser.js.map
|