ccakashic 0.2.4 → 0.2.6

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.
@@ -1,41 +1,40 @@
1
- 'use strict';
2
-
3
- const { getCSS } = require('./template-assets');
4
-
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateIndex = generateIndex;
4
+ exports.generateSessionList = generateSessionList;
5
+ const template_assets_1 = require("./template-assets");
5
6
  function escapeHtml(str) {
6
- return String(str)
7
- .replace(/&/g, '&')
8
- .replace(/</g, '&lt;')
9
- .replace(/>/g, '&gt;')
10
- .replace(/"/g, '&quot;');
7
+ return String(str)
8
+ .replace(/&/g, '&amp;')
9
+ .replace(/</g, '&lt;')
10
+ .replace(/>/g, '&gt;')
11
+ .replace(/"/g, '&quot;');
11
12
  }
12
-
13
13
  function formatDate(ts) {
14
- if (!ts) return '';
15
- const d = ts instanceof Date ? ts : new Date(ts);
16
- return d.toLocaleDateString('en-CA') + ' ' + d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
14
+ if (!ts)
15
+ return '';
16
+ const d = ts instanceof Date ? ts : new Date(ts);
17
+ return d.toLocaleDateString('en-CA') + ' ' + d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
17
18
  }
18
-
19
19
  function formatTokens(n) {
20
- if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
21
- if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
22
- return String(n);
20
+ if (n >= 1000000)
21
+ return (n / 1000000).toFixed(1) + 'M';
22
+ if (n >= 1000)
23
+ return (n / 1000).toFixed(1) + 'K';
24
+ return String(n);
23
25
  }
24
-
25
26
  const GITHUB_URL = 'https://github.com/ashimon83/ccakashic';
26
-
27
27
  function githubCorner() {
28
- return `<a href="${GITHUB_URL}" class="github-corner" aria-label="View source on GitHub" target="_blank" rel="noopener"><svg width="70" height="70" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>`;
28
+ return `<a href="${GITHUB_URL}" class="github-corner" aria-label="View source on GitHub" target="_blank" rel="noopener"><svg width="70" height="70" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>`;
29
29
  }
30
-
31
30
  function pageShell(title, bodyHtml) {
32
- return `<!DOCTYPE html>
31
+ return `<!DOCTYPE html>
33
32
  <html lang="en">
34
33
  <head>
35
34
  <meta charset="utf-8">
36
35
  <meta name="viewport" content="width=device-width, initial-scale=1">
37
36
  <title>${escapeHtml(title)}</title>
38
- <style>${getCSS()}
37
+ <style>${(0, template_assets_1.getCSS)()}
39
38
  ${indexCSS()}
40
39
  </style>
41
40
  </head>
@@ -45,9 +44,8 @@ ${bodyHtml}
45
44
  </body>
46
45
  </html>`;
47
46
  }
48
-
49
47
  function indexCSS() {
50
- return `
48
+ return `
51
49
  .page-header {
52
50
  max-width: 900px;
53
51
  margin: 0 auto;
@@ -238,20 +236,18 @@ function indexCSS() {
238
236
  }
239
237
  `;
240
238
  }
241
-
242
239
  function generateIndex(projects) {
243
- const items = projects.map(p => {
244
- const href = `/project/${encodeURIComponent(p.rawName)}`;
245
- return `<a class="list-item" href="${href}">
240
+ const items = projects.map((p) => {
241
+ const href = `/project/${encodeURIComponent(p.rawName)}`;
242
+ return `<a class="list-item" href="${href}">
246
243
  <div class="list-item-title">${escapeHtml(p.name)}</div>
247
244
  <div class="list-item-meta">
248
245
  <span>${p.sessionCount} sessions</span>
249
246
  <span>Last: ${formatDate(p.lastModified)}</span>
250
247
  </div>
251
248
  </a>`;
252
- }).join('\n');
253
-
254
- return pageShell('ccakashic', `
249
+ }).join('\n');
250
+ return pageShell('ccakashic', `
255
251
  <div class="page-header">
256
252
  <h1>ccakashic</h1>
257
253
  <div class="subtitle">Claude Code Session Logs</div>
@@ -269,65 +265,56 @@ document.querySelector('.search-box').addEventListener('input', function(e) {
269
265
  });
270
266
  </script>`);
271
267
  }
272
-
273
268
  function formatDateOnly(ts) {
274
- if (!ts) return '';
275
- const d = ts instanceof Date ? ts : new Date(ts);
276
- return d.toLocaleDateString('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' });
269
+ if (!ts)
270
+ return '';
271
+ const d = ts instanceof Date ? ts : new Date(ts);
272
+ return d.toLocaleDateString('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' });
277
273
  }
278
-
279
274
  function formatTimeOnly(ts) {
280
- if (!ts) return '';
281
- const d = ts instanceof Date ? ts : new Date(ts);
282
- return d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
275
+ if (!ts)
276
+ return '';
277
+ const d = ts instanceof Date ? ts : new Date(ts);
278
+ return d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
283
279
  }
284
-
285
280
  function generateSessionList(project, sessions) {
286
- // Group sessions by date (based on lastModified)
287
- const dateGroups = [];
288
- let currentDate = null;
289
- for (const s of sessions) {
290
- const dateStr = formatDateOnly(new Date(s.lastModified));
291
- if (dateStr !== currentDate) {
292
- currentDate = dateStr;
293
- dateGroups.push({ date: dateStr, sessions: [] });
281
+ const dateGroups = [];
282
+ let currentDate = null;
283
+ for (const s of sessions) {
284
+ const dateStr = formatDateOnly(new Date(s.lastModified));
285
+ if (dateStr !== currentDate) {
286
+ currentDate = dateStr;
287
+ dateGroups.push({ date: dateStr, sessions: [] });
288
+ }
289
+ dateGroups[dateGroups.length - 1].sessions.push(s);
294
290
  }
295
- dateGroups[dateGroups.length - 1].sessions.push(s);
296
- }
297
-
298
- // Build side nav
299
- const sideNavItems = dateGroups.map(g =>
300
- `<a class="sidenav-item" href="#date-${g.date}" data-date="${escapeHtml(g.date)}">${escapeHtml(g.date)} <span class="sidenav-count">(${g.sessions.length})</span></a>`
301
- ).join('\n');
302
-
303
- // Build session items grouped by date
304
- const groupsHtml = dateGroups.map(g => {
305
- const items = g.sessions.map(s => {
306
- const href = `/project/${encodeURIComponent(project.rawName)}/session/${encodeURIComponent(s.id)}`;
307
- const lastModTime = formatTimeOnly(new Date(s.lastModified));
308
- const startedTime = formatDate(s.timestamp);
309
- const slug = s.slug ? `<span class="badge">${escapeHtml(s.slug)}</span>` : '';
310
- const sub = s.hasSubagents ? '<span class="badge">subagents</span>' : '';
311
- const model = s.model ? `<span>${escapeHtml(s.model)}</span>` : '';
312
- const branch = s.gitBranch && s.gitBranch !== 'HEAD' ? `<span>branch: ${escapeHtml(s.gitBranch)}</span>` : '';
313
- const tokens = s.totalTokens ? `<span>${formatTokens(s.totalTokens)} tokens</span>` : '';
314
- const outTok = s.outputTokens ? `<span>out: ${formatTokens(s.outputTokens)}</span>` : '';
315
- const preview = s.preview ? `<div class="list-item-preview">${escapeHtml(s.preview)}</div>` : '';
316
-
317
- return `<a class="list-item" href="${href}">
291
+ // Build side nav
292
+ const sideNavItems = dateGroups.map(g => `<a class="sidenav-item" href="#date-${g.date}" data-date="${escapeHtml(g.date)}">${escapeHtml(g.date)} <span class="sidenav-count">(${g.sessions.length})</span></a>`).join('\n');
293
+ // Build session items grouped by date
294
+ const groupsHtml = dateGroups.map(g => {
295
+ const items = g.sessions.map(s => {
296
+ const href = `/project/${encodeURIComponent(project.rawName)}/session/${encodeURIComponent(s.id)}`;
297
+ const lastModTime = formatTimeOnly(new Date(s.lastModified));
298
+ const startedTime = formatDate(s.timestamp);
299
+ const slug = s.slug ? `<span class="badge">${escapeHtml(s.slug)}</span>` : '';
300
+ const sub = s.hasSubagents ? '<span class="badge">subagents</span>' : '';
301
+ const model = s.model ? `<span>${escapeHtml(s.model)}</span>` : '';
302
+ const branch = s.gitBranch && s.gitBranch !== 'HEAD' ? `<span>branch: ${escapeHtml(s.gitBranch)}</span>` : '';
303
+ const tokens = s.totalTokens ? `<span>${formatTokens(s.totalTokens)} tokens</span>` : '';
304
+ const outTok = s.outputTokens ? `<span>out: ${formatTokens(s.outputTokens)}</span>` : '';
305
+ const preview = s.preview ? `<div class="list-item-preview">${escapeHtml(s.preview)}</div>` : '';
306
+ return `<a class="list-item" href="${href}">
318
307
  <div class="list-item-title">${lastModTime} ${slug} ${sub}</div>
319
308
  <div class="list-item-meta"><span>started: ${startedTime}</span>${model}${branch}${tokens}${outTok}</div>
320
309
  ${preview}
321
310
  </a>`;
322
- }).join('\n');
323
-
324
- return `<div class="date-group" id="date-${g.date}" data-date="${escapeHtml(g.date)}">
311
+ }).join('\n');
312
+ return `<div class="date-group" id="date-${g.date}" data-date="${escapeHtml(g.date)}">
325
313
  <div class="date-heading">${escapeHtml(g.date)}</div>
326
314
  ${items}
327
315
  </div>`;
328
- }).join('\n');
329
-
330
- return pageShell(`${project.name} — ccakashic`, `
316
+ }).join('\n');
317
+ return pageShell(`${project.name} — ccakashic`, `
331
318
  <div class="sticky-date-bar" id="stickyDateBar"></div>
332
319
  <div class="page-header">
333
320
  <div class="breadcrumb"><a href="/">ccakashic</a> / ${escapeHtml(project.name)}</div>
@@ -389,5 +376,3 @@ function generateSessionList(project, sessions) {
389
376
  })();
390
377
  </script>`);
391
378
  }
392
-
393
- module.exports = { generateIndex, generateSessionList };
package/dist/parser.js ADDED
@@ -0,0 +1,465 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseSession = parseSession;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const readline = __importStar(require("readline"));
40
+ async function parseSession(sessionPath) {
41
+ const lines = await readJsonlLines(sessionPath);
42
+ const messages = buildConversation(lines);
43
+ const sessionId = path.basename(sessionPath, '.jsonl');
44
+ const subagentsDir = path.join(path.dirname(sessionPath), sessionId, 'subagents');
45
+ const subagents = {};
46
+ if (fs.existsSync(subagentsDir)) {
47
+ const agentFiles = fs.readdirSync(subagentsDir).filter((f) => f.endsWith('.jsonl'));
48
+ for (const f of agentFiles) {
49
+ const agentLines = await readJsonlLines(path.join(subagentsDir, f));
50
+ const agentMessages = buildConversation(agentLines);
51
+ const agentId = f.replace(/^agent-/, '').replace(/\.jsonl$/, '');
52
+ subagents[agentId] = agentMessages;
53
+ }
54
+ }
55
+ inlineSubagents(messages, subagents);
56
+ const stats = aggregateUsage(lines);
57
+ return { messages, subagents: {}, sessionPath, stats };
58
+ }
59
+ function aggregateUsage(lines) {
60
+ let inputTokens = 0;
61
+ let outputTokens = 0;
62
+ let cacheCreation = 0;
63
+ let cacheRead = 0;
64
+ let turns = 0;
65
+ let firstTimestamp = null;
66
+ let lastTimestamp = null;
67
+ for (const line of lines) {
68
+ if (line.timestamp) {
69
+ if (!firstTimestamp)
70
+ firstTimestamp = line.timestamp;
71
+ lastTimestamp = line.timestamp;
72
+ }
73
+ if (line.type === 'assistant') {
74
+ const usage = line.message?.usage;
75
+ if (usage) {
76
+ turns++;
77
+ inputTokens += usage.input_tokens || 0;
78
+ outputTokens += usage.output_tokens || 0;
79
+ cacheCreation += usage.cache_creation_input_tokens || 0;
80
+ cacheRead += usage.cache_read_input_tokens || 0;
81
+ }
82
+ }
83
+ }
84
+ const totalInput = inputTokens + cacheCreation + cacheRead;
85
+ const totalTokens = totalInput + outputTokens;
86
+ const cacheTotal = cacheCreation + cacheRead;
87
+ const cacheHitRate = cacheTotal > 0 ? cacheRead / cacheTotal : 0;
88
+ let durationMs = 0;
89
+ if (firstTimestamp && lastTimestamp) {
90
+ durationMs = new Date(lastTimestamp).getTime() - new Date(firstTimestamp).getTime();
91
+ }
92
+ return {
93
+ turns,
94
+ inputTokens,
95
+ outputTokens,
96
+ cacheCreation,
97
+ cacheRead,
98
+ totalTokens,
99
+ cacheHitRate,
100
+ durationMs,
101
+ };
102
+ }
103
+ function readJsonlLines(filePath) {
104
+ return new Promise((resolve) => {
105
+ const lines = [];
106
+ const rl = readline.createInterface({
107
+ input: fs.createReadStream(filePath, { encoding: 'utf-8' }),
108
+ crlfDelay: Infinity,
109
+ });
110
+ rl.on('line', (line) => {
111
+ try {
112
+ lines.push(JSON.parse(line));
113
+ }
114
+ catch {
115
+ // skip
116
+ }
117
+ });
118
+ rl.on('close', () => resolve(lines));
119
+ rl.on('error', () => resolve(lines));
120
+ });
121
+ }
122
+ function buildConversation(lines) {
123
+ const messages = [];
124
+ const assistantById = new Map();
125
+ for (const line of lines) {
126
+ if (line.type === 'assistant' && line.message?.id) {
127
+ assistantById.set(line.message.id, line);
128
+ }
129
+ }
130
+ const seenAssistantIds = new Set();
131
+ for (const line of lines) {
132
+ if (['file-history-snapshot', 'last-prompt', 'permission-mode'].includes(line.type)) {
133
+ continue;
134
+ }
135
+ if (line.type === 'user') {
136
+ processUserMessage(line, messages);
137
+ }
138
+ else if (line.type === 'assistant') {
139
+ const msgId = line.message?.id;
140
+ if (msgId) {
141
+ if (seenAssistantIds.has(msgId))
142
+ continue;
143
+ if (assistantById.get(msgId) !== line)
144
+ continue;
145
+ seenAssistantIds.add(msgId);
146
+ }
147
+ processAssistantMessage(line, messages);
148
+ }
149
+ else if (line.type === 'system') {
150
+ processSystemMessage(line, messages);
151
+ }
152
+ }
153
+ pairToolMessages(messages);
154
+ calculateTurnUsage(messages);
155
+ calculateElapsed(messages);
156
+ return messages;
157
+ }
158
+ function parseLocalCommand(text) {
159
+ const inputMatch = text.match(/<bash-input>([\s\S]*?)<\/bash-input>/);
160
+ const stdoutMatch = text.match(/<bash-stdout>([\s\S]*?)<\/bash-stdout>/);
161
+ const stderrMatch = text.match(/<bash-stderr>([\s\S]*?)<\/bash-stderr>/);
162
+ if (inputMatch) {
163
+ return { type: 'local_command', subtype: 'input', command: inputMatch[1] };
164
+ }
165
+ if (stdoutMatch || stderrMatch) {
166
+ return {
167
+ type: 'local_command',
168
+ subtype: 'output',
169
+ stdout: stdoutMatch ? stdoutMatch[1] : '',
170
+ stderr: stderrMatch ? stderrMatch[1] : '',
171
+ };
172
+ }
173
+ return null;
174
+ }
175
+ function processUserText(text, line, messages) {
176
+ if (text.match(/^<local-command-caveat>/)) {
177
+ return;
178
+ }
179
+ const localCmd = parseLocalCommand(text);
180
+ if (localCmd) {
181
+ if (localCmd.subtype === 'input') {
182
+ messages.push({
183
+ type: 'local_command',
184
+ command: localCmd.command,
185
+ timestamp: line.timestamp,
186
+ uuid: line.uuid,
187
+ });
188
+ }
189
+ else if (localCmd.subtype === 'output') {
190
+ const prev = messages.length > 0 ? messages[messages.length - 1] : null;
191
+ if (prev && prev.type === 'local_command' && !prev.stdout) {
192
+ prev.stdout = localCmd.stdout;
193
+ prev.stderr = localCmd.stderr;
194
+ }
195
+ else {
196
+ messages.push({
197
+ type: 'local_command',
198
+ command: '',
199
+ stdout: localCmd.stdout,
200
+ stderr: localCmd.stderr,
201
+ timestamp: line.timestamp,
202
+ uuid: line.uuid,
203
+ });
204
+ }
205
+ }
206
+ return;
207
+ }
208
+ const stripped = text
209
+ .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '')
210
+ .replace(/<command-name>[\s\S]*?<\/command-name>/g, '')
211
+ .replace(/<command-message>[\s\S]*?<\/command-message>/g, '')
212
+ .replace(/<command-args>[\s\S]*?<\/command-args>/g, '')
213
+ .replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, '')
214
+ .trim();
215
+ if (stripped) {
216
+ messages.push({
217
+ type: 'user',
218
+ text: stripped,
219
+ timestamp: line.timestamp,
220
+ uuid: line.uuid,
221
+ });
222
+ }
223
+ }
224
+ function processUserMessage(line, messages) {
225
+ const content = line.message?.content;
226
+ if (!content)
227
+ return;
228
+ if (typeof content === 'string') {
229
+ if (content.trim()) {
230
+ processUserText(content, line, messages);
231
+ }
232
+ return;
233
+ }
234
+ if (Array.isArray(content)) {
235
+ for (const block of content) {
236
+ if (block.type === 'text' && block.text?.trim()) {
237
+ processUserText(block.text, line, messages);
238
+ }
239
+ else if (block.type === 'tool_result') {
240
+ const toolResult = {
241
+ type: 'tool_result',
242
+ toolUseId: block.tool_use_id,
243
+ timestamp: line.timestamp,
244
+ uuid: line.uuid,
245
+ };
246
+ if (line.toolUseResult) {
247
+ toolResult.richResult = line.toolUseResult;
248
+ }
249
+ if (typeof block.content === 'string') {
250
+ toolResult.content = block.content;
251
+ }
252
+ else if (Array.isArray(block.content)) {
253
+ const texts = block.content
254
+ .filter((b) => b.type === 'text')
255
+ .map((b) => b.text);
256
+ toolResult.content = texts.join('\n');
257
+ }
258
+ if (toolResult.content && toolResult.content.includes('<persisted-output>')) {
259
+ const match = toolResult.content.match(/Full output saved to: ([^\n<]+)/);
260
+ if (match) {
261
+ const persistedPath = match[1].trim();
262
+ try {
263
+ if (fs.existsSync(persistedPath)) {
264
+ toolResult.fullContent = fs.readFileSync(persistedPath, 'utf-8');
265
+ }
266
+ }
267
+ catch {
268
+ // ignore
269
+ }
270
+ }
271
+ }
272
+ messages.push(toolResult);
273
+ }
274
+ }
275
+ }
276
+ }
277
+ function processAssistantMessage(line, messages) {
278
+ const content = line.message?.content;
279
+ if (!content || !Array.isArray(content))
280
+ return;
281
+ const usage = line.message?.usage || null;
282
+ let usageAssigned = false;
283
+ for (const block of content) {
284
+ if (block.type === 'thinking') {
285
+ messages.push({
286
+ type: 'thinking',
287
+ timestamp: line.timestamp,
288
+ uuid: line.uuid,
289
+ });
290
+ }
291
+ else if (block.type === 'text' && block.text?.trim()) {
292
+ messages.push({
293
+ type: 'assistant',
294
+ text: block.text,
295
+ model: line.message.model,
296
+ timestamp: line.timestamp,
297
+ uuid: line.uuid,
298
+ usage: !usageAssigned ? usage : null,
299
+ });
300
+ usageAssigned = true;
301
+ }
302
+ else if (block.type === 'tool_use') {
303
+ messages.push({
304
+ type: 'tool_use',
305
+ toolName: block.name,
306
+ toolUseId: block.id,
307
+ input: block.input,
308
+ timestamp: line.timestamp,
309
+ uuid: line.uuid,
310
+ usage: !usageAssigned ? usage : null,
311
+ });
312
+ usageAssigned = true;
313
+ }
314
+ }
315
+ }
316
+ function calculateTurnUsage(messages) {
317
+ for (let i = 0; i < messages.length; i++) {
318
+ if (messages[i].type !== 'user' && messages[i].type !== 'local_command')
319
+ continue;
320
+ let totalInput = 0;
321
+ let totalOutput = 0;
322
+ let totalCacheRead = 0;
323
+ let totalCacheCreate = 0;
324
+ const seenUsageIds = new Set();
325
+ for (let j = i + 1; j < messages.length; j++) {
326
+ const m = messages[j];
327
+ if (m.type === 'user' || m.type === 'local_command')
328
+ break;
329
+ if (m.usage && !seenUsageIds.has(m.uuid + (m.usage.input_tokens || 0))) {
330
+ seenUsageIds.add(m.uuid + (m.usage.input_tokens || 0));
331
+ totalInput += m.usage.input_tokens || 0;
332
+ totalOutput += m.usage.output_tokens || 0;
333
+ totalCacheRead += m.usage.cache_read_input_tokens || 0;
334
+ totalCacheCreate += m.usage.cache_creation_input_tokens || 0;
335
+ }
336
+ }
337
+ let turnDurationMs = 0;
338
+ for (let j = i + 1; j < messages.length; j++) {
339
+ const m = messages[j];
340
+ if (m.type === 'user' || m.type === 'local_command')
341
+ break;
342
+ if (m.type === 'system' && m.subtype === 'turn_duration') {
343
+ turnDurationMs = m.durationMs || 0;
344
+ break;
345
+ }
346
+ }
347
+ const total = totalInput + totalOutput + totalCacheRead + totalCacheCreate;
348
+ if (total > 0 || turnDurationMs > 0) {
349
+ messages[i].turnUsage = {
350
+ input: totalInput,
351
+ output: totalOutput,
352
+ cacheRead: totalCacheRead,
353
+ cacheCreate: totalCacheCreate,
354
+ total,
355
+ durationMs: turnDurationMs,
356
+ };
357
+ }
358
+ }
359
+ }
360
+ function calculateElapsed(messages) {
361
+ for (let i = 1; i < messages.length; i++) {
362
+ const prev = messages[i - 1];
363
+ const curr = messages[i];
364
+ if (prev.timestamp && curr.timestamp) {
365
+ const elapsed = new Date(curr.timestamp).getTime() - new Date(prev.timestamp).getTime();
366
+ if (elapsed > 0) {
367
+ curr.elapsedMs = elapsed;
368
+ }
369
+ }
370
+ if (curr.type === 'tool_use' && curr.result?.timestamp && curr.timestamp) {
371
+ const execTime = new Date(curr.result.timestamp).getTime() - new Date(curr.timestamp).getTime();
372
+ if (execTime > 0) {
373
+ curr.execMs = execTime;
374
+ }
375
+ }
376
+ }
377
+ }
378
+ function processSystemMessage(line, messages) {
379
+ if (line.subtype === 'turn_duration') {
380
+ messages.push({
381
+ type: 'system',
382
+ subtype: 'turn_duration',
383
+ durationMs: line.durationMs,
384
+ timestamp: line.timestamp,
385
+ });
386
+ }
387
+ else if (line.subtype === 'bridge_status') {
388
+ // skip
389
+ }
390
+ else if (line.content) {
391
+ messages.push({
392
+ type: 'system',
393
+ subtype: line.subtype || 'info',
394
+ content: typeof line.content === 'string' ? line.content : JSON.stringify(line.content),
395
+ timestamp: line.timestamp,
396
+ });
397
+ }
398
+ }
399
+ function inlineSubagents(messages, subagents) {
400
+ if (!Object.keys(subagents).length)
401
+ return;
402
+ for (const msg of messages) {
403
+ if (msg.type !== 'tool_use' || msg.toolName !== 'Agent')
404
+ continue;
405
+ if (!msg.result)
406
+ continue;
407
+ const content = msg.result.content || msg.result.fullContent || '';
408
+ const match = content.match(/agentId:\s*([a-f0-9]+)/);
409
+ if (match) {
410
+ const agentId = match[1];
411
+ if (subagents[agentId]) {
412
+ msg.subagentMessages = subagents[agentId];
413
+ msg.subagentId = agentId;
414
+ }
415
+ }
416
+ }
417
+ const usedIds = new Set(messages.filter((m) => m.subagentId).map((m) => m.subagentId));
418
+ const unmatchedAgents = Object.keys(subagents).filter((id) => !usedIds.has(id));
419
+ if (unmatchedAgents.length) {
420
+ for (const msg of messages) {
421
+ if (msg.type !== 'tool_use' || msg.toolName !== 'Agent')
422
+ continue;
423
+ if (msg.subagentMessages)
424
+ continue;
425
+ const desc = (msg.input?.description || '').toLowerCase();
426
+ if (!desc)
427
+ continue;
428
+ for (let i = unmatchedAgents.length - 1; i >= 0; i--) {
429
+ const agentId = unmatchedAgents[i];
430
+ const firstMsg = subagents[agentId]?.[0];
431
+ if (firstMsg?.type === 'user') {
432
+ const agentText = (firstMsg.text || '').toLowerCase();
433
+ if (agentText.includes(desc) || desc.includes(agentText.slice(0, 30))) {
434
+ msg.subagentMessages = subagents[agentId];
435
+ msg.subagentId = agentId;
436
+ unmatchedAgents.splice(i, 1);
437
+ break;
438
+ }
439
+ }
440
+ }
441
+ }
442
+ }
443
+ }
444
+ function pairToolMessages(messages) {
445
+ const toolUseMap = new Map();
446
+ for (let i = 0; i < messages.length; i++) {
447
+ if (messages[i].type === 'tool_use') {
448
+ toolUseMap.set(messages[i].toolUseId, i);
449
+ }
450
+ }
451
+ for (let i = 0; i < messages.length; i++) {
452
+ if (messages[i].type === 'tool_result' && messages[i].toolUseId) {
453
+ const useIdx = toolUseMap.get(messages[i].toolUseId);
454
+ if (useIdx !== undefined) {
455
+ messages[useIdx].result = messages[i];
456
+ messages[i]._paired = true;
457
+ }
458
+ }
459
+ }
460
+ for (let i = messages.length - 1; i >= 0; i--) {
461
+ if (messages[i]._paired) {
462
+ messages.splice(i, 1);
463
+ }
464
+ }
465
+ }