recall-player 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +238 -0
- package/backend/dist/db/connection.d.ts +47 -0
- package/backend/dist/db/connection.d.ts.map +1 -0
- package/backend/dist/db/connection.js +88 -0
- package/backend/dist/db/connection.js.map +1 -0
- package/backend/dist/db/queries.d.ts +172 -0
- package/backend/dist/db/queries.d.ts.map +1 -0
- package/backend/dist/db/queries.js +436 -0
- package/backend/dist/db/queries.js.map +1 -0
- package/backend/dist/db/schema.d.ts +100 -0
- package/backend/dist/db/schema.d.ts.map +1 -0
- package/backend/dist/db/schema.js +6 -0
- package/backend/dist/db/schema.js.map +1 -0
- package/backend/dist/db/transcript-connection.d.ts +57 -0
- package/backend/dist/db/transcript-connection.d.ts.map +1 -0
- package/backend/dist/db/transcript-connection.js +112 -0
- package/backend/dist/db/transcript-connection.js.map +1 -0
- package/backend/dist/db/transcript-queries.d.ts +152 -0
- package/backend/dist/db/transcript-queries.d.ts.map +1 -0
- package/backend/dist/db/transcript-queries.js +706 -0
- package/backend/dist/db/transcript-queries.js.map +1 -0
- package/backend/dist/db/transcript-schema.d.ts +143 -0
- package/backend/dist/db/transcript-schema.d.ts.map +1 -0
- package/backend/dist/db/transcript-schema.js +10 -0
- package/backend/dist/db/transcript-schema.js.map +1 -0
- package/backend/dist/index.d.ts +2 -0
- package/backend/dist/index.d.ts.map +1 -0
- package/backend/dist/index.js +96 -0
- package/backend/dist/index.js.map +1 -0
- package/backend/dist/parser/agent-detector.d.ts +76 -0
- package/backend/dist/parser/agent-detector.d.ts.map +1 -0
- package/backend/dist/parser/agent-detector.js +281 -0
- package/backend/dist/parser/agent-detector.js.map +1 -0
- package/backend/dist/parser/base-parser.d.ts +123 -0
- package/backend/dist/parser/base-parser.d.ts.map +1 -0
- package/backend/dist/parser/base-parser.js +364 -0
- package/backend/dist/parser/base-parser.js.map +1 -0
- package/backend/dist/parser/claude-parser.d.ts +78 -0
- package/backend/dist/parser/claude-parser.d.ts.map +1 -0
- package/backend/dist/parser/claude-parser.js +247 -0
- package/backend/dist/parser/claude-parser.js.map +1 -0
- package/backend/dist/parser/codex-parser.d.ts +128 -0
- package/backend/dist/parser/codex-parser.d.ts.map +1 -0
- package/backend/dist/parser/codex-parser.js +371 -0
- package/backend/dist/parser/codex-parser.js.map +1 -0
- package/backend/dist/parser/gemini-parser.d.ts +107 -0
- package/backend/dist/parser/gemini-parser.d.ts.map +1 -0
- package/backend/dist/parser/gemini-parser.js +360 -0
- package/backend/dist/parser/gemini-parser.js.map +1 -0
- package/backend/dist/parser/parser-factory.d.ts +83 -0
- package/backend/dist/parser/parser-factory.d.ts.map +1 -0
- package/backend/dist/parser/parser-factory.js +163 -0
- package/backend/dist/parser/parser-factory.js.map +1 -0
- package/backend/dist/parser/session-indexer.d.ts +79 -0
- package/backend/dist/parser/session-indexer.d.ts.map +1 -0
- package/backend/dist/parser/session-indexer.js +558 -0
- package/backend/dist/parser/session-indexer.js.map +1 -0
- package/backend/dist/parser/timeline-builder.d.ts +7 -0
- package/backend/dist/parser/timeline-builder.d.ts.map +1 -0
- package/backend/dist/parser/timeline-builder.js +334 -0
- package/backend/dist/parser/timeline-builder.js.map +1 -0
- package/backend/dist/parser/transcript-parser.d.ts +25 -0
- package/backend/dist/parser/transcript-parser.d.ts.map +1 -0
- package/backend/dist/parser/transcript-parser.js +137 -0
- package/backend/dist/parser/transcript-parser.js.map +1 -0
- package/backend/dist/routes/commentary.d.ts +33 -0
- package/backend/dist/routes/commentary.d.ts.map +1 -0
- package/backend/dist/routes/commentary.js +153 -0
- package/backend/dist/routes/commentary.js.map +1 -0
- package/backend/dist/routes/import.d.ts +3 -0
- package/backend/dist/routes/import.d.ts.map +1 -0
- package/backend/dist/routes/import.js +220 -0
- package/backend/dist/routes/import.js.map +1 -0
- package/backend/dist/routes/sessions.d.ts +3 -0
- package/backend/dist/routes/sessions.d.ts.map +1 -0
- package/backend/dist/routes/sessions.js +393 -0
- package/backend/dist/routes/sessions.js.map +1 -0
- package/backend/dist/server.d.ts +25 -0
- package/backend/dist/server.d.ts.map +1 -0
- package/backend/dist/server.js +110 -0
- package/backend/dist/server.js.map +1 -0
- package/backend/dist/services/example-import.d.ts +20 -0
- package/backend/dist/services/example-import.d.ts.map +1 -0
- package/backend/dist/services/example-import.js +93 -0
- package/backend/dist/services/example-import.js.map +1 -0
- package/backend/dist/services/file-watcher.d.ts +22 -0
- package/backend/dist/services/file-watcher.d.ts.map +1 -0
- package/backend/dist/services/file-watcher.js +141 -0
- package/backend/dist/services/file-watcher.js.map +1 -0
- package/backend/dist/services/import-cli.d.ts +11 -0
- package/backend/dist/services/import-cli.d.ts.map +1 -0
- package/backend/dist/services/import-cli.js +171 -0
- package/backend/dist/services/import-cli.js.map +1 -0
- package/backend/dist/services/transcript-importer.d.ts +98 -0
- package/backend/dist/services/transcript-importer.d.ts.map +1 -0
- package/backend/dist/services/transcript-importer.js +342 -0
- package/backend/dist/services/transcript-importer.js.map +1 -0
- package/backend/dist/types/transcript.d.ts +174 -0
- package/backend/dist/types/transcript.d.ts.map +1 -0
- package/backend/dist/types/transcript.js +8 -0
- package/backend/dist/types/transcript.js.map +1 -0
- package/backend/package.json +40 -0
- package/backend/public/.gitkeep +0 -0
- package/backend/public/assets/index-D8IP1zNL.css +1 -0
- package/backend/public/assets/index-GtkMiMqE.js +69 -0
- package/backend/public/index.html +14 -0
- package/bin/recall.js +43 -0
- package/package.json +54 -0
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initializeTranscriptSchema = initializeTranscriptSchema;
|
|
4
|
+
exports.getTranscriptSessions = getTranscriptSessions;
|
|
5
|
+
exports.getTranscriptSessionById = getTranscriptSessionById;
|
|
6
|
+
exports.getTranscriptFrames = getTranscriptFrames;
|
|
7
|
+
exports.getToolExecution = getToolExecution;
|
|
8
|
+
exports.insertSession = insertSession;
|
|
9
|
+
exports.insertFrame = insertFrame;
|
|
10
|
+
exports.insertToolExecution = insertToolExecution;
|
|
11
|
+
exports.insertFileDiff = insertFileDiff;
|
|
12
|
+
exports.updateSessionFrameCount = updateSessionFrameCount;
|
|
13
|
+
exports.updateParsingStatus = updateParsingStatus;
|
|
14
|
+
exports.getTranscriptProjects = getTranscriptProjects;
|
|
15
|
+
exports.getImportStats = getImportStats;
|
|
16
|
+
const transcript_connection_1 = require("./transcript-connection");
|
|
17
|
+
/**
|
|
18
|
+
* Initialize database schema
|
|
19
|
+
*
|
|
20
|
+
* Creates all tables with proper indexes and foreign keys.
|
|
21
|
+
* Safe to call multiple times - uses IF NOT EXISTS.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* initializeTranscriptSchema();
|
|
25
|
+
* console.log('Database schema initialized');
|
|
26
|
+
*/
|
|
27
|
+
function initializeTranscriptSchema() {
|
|
28
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
29
|
+
// 1. SESSION_METADATA - create table first without agent_type for migration compatibility
|
|
30
|
+
db.exec(`
|
|
31
|
+
CREATE TABLE IF NOT EXISTS session_metadata (
|
|
32
|
+
session_id TEXT PRIMARY KEY,
|
|
33
|
+
slug TEXT NOT NULL,
|
|
34
|
+
project TEXT NOT NULL,
|
|
35
|
+
start_time TEXT NOT NULL,
|
|
36
|
+
end_time TEXT,
|
|
37
|
+
duration_seconds INTEGER,
|
|
38
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
39
|
+
frame_count INTEGER NOT NULL DEFAULT 0,
|
|
40
|
+
cwd TEXT NOT NULL,
|
|
41
|
+
first_user_message TEXT,
|
|
42
|
+
parsed_at TEXT NOT NULL
|
|
43
|
+
);
|
|
44
|
+
`);
|
|
45
|
+
// Migration: Add agent_type column if it doesn't exist
|
|
46
|
+
const sessionColumns = db.pragma('table_info(session_metadata)');
|
|
47
|
+
const hasAgentTypeInSessions = sessionColumns.some((col) => col.name === 'agent_type');
|
|
48
|
+
if (!hasAgentTypeInSessions) {
|
|
49
|
+
db.exec(`ALTER TABLE session_metadata ADD COLUMN agent_type TEXT NOT NULL DEFAULT 'claude'`);
|
|
50
|
+
}
|
|
51
|
+
// Create indexes after migration
|
|
52
|
+
db.exec(`
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_session_project
|
|
54
|
+
ON session_metadata(project);
|
|
55
|
+
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_session_start_time
|
|
57
|
+
ON session_metadata(start_time DESC);
|
|
58
|
+
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_agent
|
|
60
|
+
ON session_metadata(agent_type);
|
|
61
|
+
`);
|
|
62
|
+
// 2. PLAYBACK_FRAMES - create table first without agent_type for migration compatibility
|
|
63
|
+
db.exec(`
|
|
64
|
+
CREATE TABLE IF NOT EXISTS playback_frames (
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
session_id TEXT NOT NULL,
|
|
67
|
+
frame_type TEXT NOT NULL,
|
|
68
|
+
timestamp_ms INTEGER NOT NULL,
|
|
69
|
+
duration_ms INTEGER,
|
|
70
|
+
user_message_text TEXT,
|
|
71
|
+
thinking_text TEXT,
|
|
72
|
+
thinking_signature TEXT,
|
|
73
|
+
response_text TEXT,
|
|
74
|
+
cwd TEXT NOT NULL,
|
|
75
|
+
files_read TEXT,
|
|
76
|
+
files_modified TEXT,
|
|
77
|
+
FOREIGN KEY (session_id) REFERENCES session_metadata(session_id)
|
|
78
|
+
);
|
|
79
|
+
`);
|
|
80
|
+
// Migration: Add agent_type column to playback_frames if it doesn't exist
|
|
81
|
+
const frameColumns = db.pragma('table_info(playback_frames)');
|
|
82
|
+
const hasAgentTypeInFrames = frameColumns.some((col) => col.name === 'agent_type');
|
|
83
|
+
if (!hasAgentTypeInFrames) {
|
|
84
|
+
db.exec(`ALTER TABLE playback_frames ADD COLUMN agent_type TEXT NOT NULL DEFAULT 'claude'`);
|
|
85
|
+
}
|
|
86
|
+
// Create indexes after migration
|
|
87
|
+
db.exec(`
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_frame_session_timestamp
|
|
89
|
+
ON playback_frames(session_id, timestamp_ms);
|
|
90
|
+
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_frame_type
|
|
92
|
+
ON playback_frames(frame_type);
|
|
93
|
+
`);
|
|
94
|
+
// 3. TOOL_EXECUTIONS
|
|
95
|
+
db.exec(`
|
|
96
|
+
CREATE TABLE IF NOT EXISTS tool_executions (
|
|
97
|
+
id TEXT PRIMARY KEY,
|
|
98
|
+
frame_id TEXT NOT NULL,
|
|
99
|
+
tool_name TEXT NOT NULL,
|
|
100
|
+
tool_input TEXT NOT NULL,
|
|
101
|
+
output_content TEXT NOT NULL,
|
|
102
|
+
is_error INTEGER NOT NULL DEFAULT 0,
|
|
103
|
+
exit_code INTEGER,
|
|
104
|
+
FOREIGN KEY (frame_id) REFERENCES playback_frames(id)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_tool_frame
|
|
108
|
+
ON tool_executions(frame_id);
|
|
109
|
+
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_tool_name
|
|
111
|
+
ON tool_executions(tool_name);
|
|
112
|
+
`);
|
|
113
|
+
// 4. FILE_DIFFS
|
|
114
|
+
db.exec(`
|
|
115
|
+
CREATE TABLE IF NOT EXISTS file_diffs (
|
|
116
|
+
id TEXT PRIMARY KEY,
|
|
117
|
+
tool_execution_id TEXT NOT NULL,
|
|
118
|
+
file_path TEXT NOT NULL,
|
|
119
|
+
old_content TEXT,
|
|
120
|
+
new_content TEXT NOT NULL,
|
|
121
|
+
language TEXT NOT NULL,
|
|
122
|
+
FOREIGN KEY (tool_execution_id) REFERENCES tool_executions(id)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_diff_tool_execution
|
|
126
|
+
ON file_diffs(tool_execution_id);
|
|
127
|
+
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_diff_file_path
|
|
129
|
+
ON file_diffs(file_path);
|
|
130
|
+
`);
|
|
131
|
+
// 5. PARSING_STATUS
|
|
132
|
+
db.exec(`
|
|
133
|
+
CREATE TABLE IF NOT EXISTS parsing_status (
|
|
134
|
+
session_id TEXT PRIMARY KEY,
|
|
135
|
+
transcript_file_path TEXT NOT NULL,
|
|
136
|
+
total_entries INTEGER NOT NULL,
|
|
137
|
+
frames_created INTEGER NOT NULL DEFAULT 0,
|
|
138
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
139
|
+
started_at TEXT NOT NULL,
|
|
140
|
+
completed_at TEXT,
|
|
141
|
+
error_message TEXT
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_parsing_status
|
|
145
|
+
ON parsing_status(status);
|
|
146
|
+
`);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get all sessions with pagination and filtering
|
|
150
|
+
*
|
|
151
|
+
* @param {TranscriptSessionListQuery} query - Query parameters
|
|
152
|
+
* @returns {{ sessions: SessionMetadata[]; total: number }} Paginated sessions
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* const result = getTranscriptSessions({ limit: 20, offset: 0 });
|
|
156
|
+
* console.log(`Found ${result.total} sessions`);
|
|
157
|
+
*/
|
|
158
|
+
function getTranscriptSessions(query) {
|
|
159
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
160
|
+
const { offset = 0, limit = 20, project, agent, dateStart, dateEnd, } = query;
|
|
161
|
+
// Build WHERE clauses
|
|
162
|
+
const whereClauses = [];
|
|
163
|
+
const params = {};
|
|
164
|
+
if (project) {
|
|
165
|
+
whereClauses.push('project = @project');
|
|
166
|
+
params.project = project;
|
|
167
|
+
}
|
|
168
|
+
if (agent) {
|
|
169
|
+
whereClauses.push('agent_type = @agent');
|
|
170
|
+
params.agent = agent;
|
|
171
|
+
}
|
|
172
|
+
if (dateStart) {
|
|
173
|
+
whereClauses.push('start_time >= @dateStart');
|
|
174
|
+
params.dateStart = dateStart;
|
|
175
|
+
}
|
|
176
|
+
if (dateEnd) {
|
|
177
|
+
whereClauses.push('start_time <= @dateEnd');
|
|
178
|
+
params.dateEnd = dateEnd;
|
|
179
|
+
}
|
|
180
|
+
const whereClause = whereClauses.length > 0
|
|
181
|
+
? 'WHERE ' + whereClauses.join(' AND ')
|
|
182
|
+
: '';
|
|
183
|
+
// Get total count
|
|
184
|
+
const countQuery = `SELECT COUNT(*) as total FROM session_metadata ${whereClause}`;
|
|
185
|
+
const countResult = db.prepare(countQuery).get(params);
|
|
186
|
+
const total = countResult.total;
|
|
187
|
+
// Get paginated sessions
|
|
188
|
+
const sessionsQuery = `
|
|
189
|
+
SELECT *
|
|
190
|
+
FROM session_metadata
|
|
191
|
+
${whereClause}
|
|
192
|
+
ORDER BY start_time DESC
|
|
193
|
+
LIMIT @limit OFFSET @offset
|
|
194
|
+
`;
|
|
195
|
+
const rows = db.prepare(sessionsQuery).all({
|
|
196
|
+
...params,
|
|
197
|
+
limit,
|
|
198
|
+
offset,
|
|
199
|
+
});
|
|
200
|
+
// Convert rows to SessionMetadata
|
|
201
|
+
const sessions = rows.map(row => ({
|
|
202
|
+
sessionId: row.session_id,
|
|
203
|
+
slug: row.slug,
|
|
204
|
+
project: row.project,
|
|
205
|
+
agent: (row.agent_type || 'claude'),
|
|
206
|
+
startTime: row.start_time,
|
|
207
|
+
endTime: row.end_time || undefined,
|
|
208
|
+
duration: row.duration_seconds || undefined,
|
|
209
|
+
eventCount: row.event_count,
|
|
210
|
+
cwd: row.cwd,
|
|
211
|
+
firstUserMessage: row.first_user_message || undefined,
|
|
212
|
+
}));
|
|
213
|
+
return { sessions, total };
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get a single session by ID
|
|
217
|
+
*
|
|
218
|
+
* @param {string} sessionId - Session UUID
|
|
219
|
+
* @returns {SessionMetadata | null} Session or null if not found
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* const session = getTranscriptSessionById('abc-123');
|
|
223
|
+
* if (session) {
|
|
224
|
+
* console.log(`Project: ${session.project}`);
|
|
225
|
+
* }
|
|
226
|
+
*/
|
|
227
|
+
function getTranscriptSessionById(sessionId) {
|
|
228
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
229
|
+
const row = db.prepare(`
|
|
230
|
+
SELECT *
|
|
231
|
+
FROM session_metadata
|
|
232
|
+
WHERE session_id = ?
|
|
233
|
+
`).get(sessionId);
|
|
234
|
+
if (!row) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
sessionId: row.session_id,
|
|
239
|
+
slug: row.slug,
|
|
240
|
+
project: row.project,
|
|
241
|
+
agent: (row.agent_type || 'claude'),
|
|
242
|
+
startTime: row.start_time,
|
|
243
|
+
endTime: row.end_time || undefined,
|
|
244
|
+
duration: row.duration_seconds || undefined,
|
|
245
|
+
eventCount: row.event_count,
|
|
246
|
+
cwd: row.cwd,
|
|
247
|
+
firstUserMessage: row.first_user_message || undefined,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get playback frames for a session
|
|
252
|
+
*
|
|
253
|
+
* @param {string} sessionId - Session UUID
|
|
254
|
+
* @param {TranscriptFramesQuery} query - Filter and pagination options
|
|
255
|
+
* @returns {{ frames: PlaybackFrame[]; total: number }} Frames and total count
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* const result = getTranscriptFrames('abc-123', { limit: 100, offset: 0 });
|
|
259
|
+
* console.log(`Total frames: ${result.total}`);
|
|
260
|
+
*/
|
|
261
|
+
function getTranscriptFrames(sessionId, query) {
|
|
262
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
263
|
+
const { offset = 0, limit = 100, afterTimestamp, frameTypes, } = query;
|
|
264
|
+
// Build WHERE clauses
|
|
265
|
+
const whereClauses = ['session_id = @sessionId'];
|
|
266
|
+
const params = { sessionId };
|
|
267
|
+
if (afterTimestamp) {
|
|
268
|
+
whereClauses.push('timestamp_ms > @afterTimestamp');
|
|
269
|
+
params.afterTimestamp = afterTimestamp;
|
|
270
|
+
}
|
|
271
|
+
if (frameTypes) {
|
|
272
|
+
const types = frameTypes.split(',').map(t => t.trim());
|
|
273
|
+
const placeholders = types.map((_, i) => `@type${i}`).join(',');
|
|
274
|
+
whereClauses.push(`frame_type IN (${placeholders})`);
|
|
275
|
+
types.forEach((type, i) => {
|
|
276
|
+
params[`type${i}`] = type;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const whereClause = 'WHERE ' + whereClauses.join(' AND ');
|
|
280
|
+
// Get total count
|
|
281
|
+
const countQuery = `SELECT COUNT(*) as total FROM playback_frames ${whereClause}`;
|
|
282
|
+
const countResult = db.prepare(countQuery).get(params);
|
|
283
|
+
const total = countResult.total;
|
|
284
|
+
// Get paginated frames
|
|
285
|
+
const framesQuery = `
|
|
286
|
+
SELECT *
|
|
287
|
+
FROM playback_frames
|
|
288
|
+
${whereClause}
|
|
289
|
+
ORDER BY timestamp_ms ASC
|
|
290
|
+
LIMIT @limit OFFSET @offset
|
|
291
|
+
`;
|
|
292
|
+
const rows = db.prepare(framesQuery).all({
|
|
293
|
+
...params,
|
|
294
|
+
limit,
|
|
295
|
+
offset,
|
|
296
|
+
});
|
|
297
|
+
// Convert rows to PlaybackFrame objects
|
|
298
|
+
const frames = rows.map(row => convertRowToFrame(row));
|
|
299
|
+
return { frames, total };
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get tool execution for a frame
|
|
303
|
+
*
|
|
304
|
+
* @param {string} frameId - Frame ID
|
|
305
|
+
* @returns {ToolExecution | null} Tool execution or null
|
|
306
|
+
*/
|
|
307
|
+
function getToolExecution(frameId) {
|
|
308
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
309
|
+
const row = db.prepare(`
|
|
310
|
+
SELECT *
|
|
311
|
+
FROM tool_executions
|
|
312
|
+
WHERE frame_id = ?
|
|
313
|
+
`).get(frameId);
|
|
314
|
+
if (!row) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
// Get file diff if it exists
|
|
318
|
+
const diffRow = db.prepare(`
|
|
319
|
+
SELECT *
|
|
320
|
+
FROM file_diffs
|
|
321
|
+
WHERE tool_execution_id = ?
|
|
322
|
+
`).get(row.id);
|
|
323
|
+
return {
|
|
324
|
+
tool: row.tool_name,
|
|
325
|
+
input: JSON.parse(row.tool_input),
|
|
326
|
+
output: {
|
|
327
|
+
content: row.output_content,
|
|
328
|
+
isError: row.is_error === 1,
|
|
329
|
+
exitCode: row.exit_code || undefined,
|
|
330
|
+
},
|
|
331
|
+
fileDiff: diffRow ? {
|
|
332
|
+
filePath: diffRow.file_path,
|
|
333
|
+
oldContent: diffRow.old_content || undefined,
|
|
334
|
+
newContent: diffRow.new_content,
|
|
335
|
+
language: diffRow.language,
|
|
336
|
+
} : undefined,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Insert a session into the database
|
|
341
|
+
*
|
|
342
|
+
* @param {SessionMetadata} session - Session metadata
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* insertSession({
|
|
346
|
+
* sessionId: 'abc-123',
|
|
347
|
+
* slug: 'my-session',
|
|
348
|
+
* project: 'MyProject',
|
|
349
|
+
* startTime: '2026-02-02T12:00:00Z',
|
|
350
|
+
* eventCount: 0,
|
|
351
|
+
* cwd: '/home/user/project'
|
|
352
|
+
* });
|
|
353
|
+
*/
|
|
354
|
+
function insertSession(session) {
|
|
355
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
356
|
+
db.prepare(`
|
|
357
|
+
INSERT OR REPLACE INTO session_metadata (
|
|
358
|
+
session_id,
|
|
359
|
+
slug,
|
|
360
|
+
project,
|
|
361
|
+
agent_type,
|
|
362
|
+
start_time,
|
|
363
|
+
end_time,
|
|
364
|
+
duration_seconds,
|
|
365
|
+
event_count,
|
|
366
|
+
frame_count,
|
|
367
|
+
cwd,
|
|
368
|
+
first_user_message,
|
|
369
|
+
parsed_at
|
|
370
|
+
) VALUES (
|
|
371
|
+
@sessionId,
|
|
372
|
+
@slug,
|
|
373
|
+
@project,
|
|
374
|
+
@agentType,
|
|
375
|
+
@startTime,
|
|
376
|
+
@endTime,
|
|
377
|
+
@duration,
|
|
378
|
+
@eventCount,
|
|
379
|
+
@frameCount,
|
|
380
|
+
@cwd,
|
|
381
|
+
@firstUserMessage,
|
|
382
|
+
@parsedAt
|
|
383
|
+
)
|
|
384
|
+
`).run({
|
|
385
|
+
sessionId: session.sessionId,
|
|
386
|
+
slug: session.slug,
|
|
387
|
+
project: session.project,
|
|
388
|
+
agentType: session.agent || 'claude',
|
|
389
|
+
startTime: session.startTime,
|
|
390
|
+
endTime: session.endTime || null,
|
|
391
|
+
duration: session.duration || null,
|
|
392
|
+
eventCount: session.eventCount,
|
|
393
|
+
frameCount: 0, // Will be updated as frames are inserted
|
|
394
|
+
cwd: session.cwd,
|
|
395
|
+
firstUserMessage: session.firstUserMessage || null,
|
|
396
|
+
parsedAt: new Date().toISOString(),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Insert a playback frame into the database
|
|
401
|
+
*
|
|
402
|
+
* @param {string} sessionId - Session UUID
|
|
403
|
+
* @param {PlaybackFrame} frame - Playback frame
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* insertFrame('abc-123', frame);
|
|
407
|
+
*/
|
|
408
|
+
function insertFrame(sessionId, frame) {
|
|
409
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
410
|
+
db.prepare(`
|
|
411
|
+
INSERT OR REPLACE INTO playback_frames (
|
|
412
|
+
id,
|
|
413
|
+
session_id,
|
|
414
|
+
frame_type,
|
|
415
|
+
timestamp_ms,
|
|
416
|
+
duration_ms,
|
|
417
|
+
user_message_text,
|
|
418
|
+
thinking_text,
|
|
419
|
+
thinking_signature,
|
|
420
|
+
response_text,
|
|
421
|
+
cwd,
|
|
422
|
+
files_read,
|
|
423
|
+
files_modified,
|
|
424
|
+
agent_type
|
|
425
|
+
) VALUES (
|
|
426
|
+
@id,
|
|
427
|
+
@sessionId,
|
|
428
|
+
@frameType,
|
|
429
|
+
@timestampMs,
|
|
430
|
+
@durationMs,
|
|
431
|
+
@userMessageText,
|
|
432
|
+
@thinkingText,
|
|
433
|
+
@thinkingSignature,
|
|
434
|
+
@responseText,
|
|
435
|
+
@cwd,
|
|
436
|
+
@filesRead,
|
|
437
|
+
@filesModified,
|
|
438
|
+
@agentType
|
|
439
|
+
)
|
|
440
|
+
`).run({
|
|
441
|
+
id: frame.id,
|
|
442
|
+
sessionId,
|
|
443
|
+
frameType: frame.type,
|
|
444
|
+
timestampMs: frame.timestamp,
|
|
445
|
+
durationMs: frame.duration || null,
|
|
446
|
+
userMessageText: frame.userMessage?.text || null,
|
|
447
|
+
thinkingText: frame.thinking?.text || null,
|
|
448
|
+
thinkingSignature: frame.thinking?.signature || null,
|
|
449
|
+
responseText: frame.claudeResponse?.text || null,
|
|
450
|
+
cwd: frame.context.cwd,
|
|
451
|
+
filesRead: frame.context.filesRead ? JSON.stringify(frame.context.filesRead) : null,
|
|
452
|
+
filesModified: frame.context.filesModified ? JSON.stringify(frame.context.filesModified) : null,
|
|
453
|
+
agentType: frame.agent || 'claude',
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Insert a tool execution into the database
|
|
458
|
+
*
|
|
459
|
+
* @param {string} frameId - Frame ID
|
|
460
|
+
* @param {ToolExecution} toolExecution - Tool execution data
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* insertToolExecution('frame-123', toolExecution);
|
|
464
|
+
*/
|
|
465
|
+
function insertToolExecution(frameId, toolExecution) {
|
|
466
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
467
|
+
const toolId = `${frameId}-tool`;
|
|
468
|
+
db.prepare(`
|
|
469
|
+
INSERT OR REPLACE INTO tool_executions (
|
|
470
|
+
id,
|
|
471
|
+
frame_id,
|
|
472
|
+
tool_name,
|
|
473
|
+
tool_input,
|
|
474
|
+
output_content,
|
|
475
|
+
is_error,
|
|
476
|
+
exit_code
|
|
477
|
+
) VALUES (
|
|
478
|
+
@id,
|
|
479
|
+
@frameId,
|
|
480
|
+
@toolName,
|
|
481
|
+
@toolInput,
|
|
482
|
+
@outputContent,
|
|
483
|
+
@isError,
|
|
484
|
+
@exitCode
|
|
485
|
+
)
|
|
486
|
+
`).run({
|
|
487
|
+
id: toolId,
|
|
488
|
+
frameId,
|
|
489
|
+
toolName: toolExecution.tool,
|
|
490
|
+
toolInput: JSON.stringify(toolExecution.input),
|
|
491
|
+
outputContent: toolExecution.output.content,
|
|
492
|
+
isError: toolExecution.output.isError ? 1 : 0,
|
|
493
|
+
exitCode: toolExecution.output.exitCode || null,
|
|
494
|
+
});
|
|
495
|
+
// Insert file diff if present
|
|
496
|
+
if (toolExecution.fileDiff) {
|
|
497
|
+
insertFileDiff(toolId, toolExecution.fileDiff);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Insert a file diff into the database
|
|
502
|
+
*
|
|
503
|
+
* @param {string} toolExecutionId - Tool execution ID
|
|
504
|
+
* @param {FileDiff} fileDiff - File diff data
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* insertFileDiff('tool-123', fileDiff);
|
|
508
|
+
*/
|
|
509
|
+
function insertFileDiff(toolExecutionId, fileDiff) {
|
|
510
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
511
|
+
db.prepare(`
|
|
512
|
+
INSERT OR REPLACE INTO file_diffs (
|
|
513
|
+
id,
|
|
514
|
+
tool_execution_id,
|
|
515
|
+
file_path,
|
|
516
|
+
old_content,
|
|
517
|
+
new_content,
|
|
518
|
+
language
|
|
519
|
+
) VALUES (
|
|
520
|
+
@id,
|
|
521
|
+
@toolExecutionId,
|
|
522
|
+
@filePath,
|
|
523
|
+
@oldContent,
|
|
524
|
+
@newContent,
|
|
525
|
+
@language
|
|
526
|
+
)
|
|
527
|
+
`).run({
|
|
528
|
+
id: `${toolExecutionId}-diff`,
|
|
529
|
+
toolExecutionId,
|
|
530
|
+
filePath: fileDiff.filePath,
|
|
531
|
+
oldContent: fileDiff.oldContent || null,
|
|
532
|
+
newContent: fileDiff.newContent,
|
|
533
|
+
language: fileDiff.language,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Update frame count for a session
|
|
538
|
+
*
|
|
539
|
+
* @param {string} sessionId - Session UUID
|
|
540
|
+
*
|
|
541
|
+
* @example
|
|
542
|
+
* updateSessionFrameCount('abc-123');
|
|
543
|
+
*/
|
|
544
|
+
function updateSessionFrameCount(sessionId) {
|
|
545
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
546
|
+
db.prepare(`
|
|
547
|
+
UPDATE session_metadata
|
|
548
|
+
SET frame_count = (
|
|
549
|
+
SELECT COUNT(*) FROM playback_frames WHERE session_id = @sessionId
|
|
550
|
+
)
|
|
551
|
+
WHERE session_id = @sessionId
|
|
552
|
+
`).run({ sessionId });
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Create or update parsing status
|
|
556
|
+
*
|
|
557
|
+
* @param {string} sessionId - Session UUID
|
|
558
|
+
* @param {Partial<ParsingStatusRow>} status - Status fields to update
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* updateParsingStatus('abc-123', {
|
|
562
|
+
* status: 'completed',
|
|
563
|
+
* completed_at: new Date().toISOString()
|
|
564
|
+
* });
|
|
565
|
+
*/
|
|
566
|
+
function updateParsingStatus(sessionId, status) {
|
|
567
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
568
|
+
// Get existing status if any
|
|
569
|
+
const existing = db.prepare(`
|
|
570
|
+
SELECT * FROM parsing_status WHERE session_id = ?
|
|
571
|
+
`).get(sessionId);
|
|
572
|
+
if (existing) {
|
|
573
|
+
// Update existing
|
|
574
|
+
const updates = [];
|
|
575
|
+
const params = { sessionId };
|
|
576
|
+
if (status.status !== undefined) {
|
|
577
|
+
updates.push('status = @status');
|
|
578
|
+
params.status = status.status;
|
|
579
|
+
}
|
|
580
|
+
if (status.frames_created !== undefined) {
|
|
581
|
+
updates.push('frames_created = @frames_created');
|
|
582
|
+
params.frames_created = status.frames_created;
|
|
583
|
+
}
|
|
584
|
+
if (status.completed_at !== undefined) {
|
|
585
|
+
updates.push('completed_at = @completed_at');
|
|
586
|
+
params.completed_at = status.completed_at;
|
|
587
|
+
}
|
|
588
|
+
if (status.error_message !== undefined) {
|
|
589
|
+
updates.push('error_message = @error_message');
|
|
590
|
+
params.error_message = status.error_message;
|
|
591
|
+
}
|
|
592
|
+
if (updates.length > 0) {
|
|
593
|
+
db.prepare(`
|
|
594
|
+
UPDATE parsing_status
|
|
595
|
+
SET ${updates.join(', ')}
|
|
596
|
+
WHERE session_id = @sessionId
|
|
597
|
+
`).run(params);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
// Insert new
|
|
602
|
+
db.prepare(`
|
|
603
|
+
INSERT INTO parsing_status (
|
|
604
|
+
session_id,
|
|
605
|
+
transcript_file_path,
|
|
606
|
+
total_entries,
|
|
607
|
+
frames_created,
|
|
608
|
+
status,
|
|
609
|
+
started_at,
|
|
610
|
+
completed_at,
|
|
611
|
+
error_message
|
|
612
|
+
) VALUES (
|
|
613
|
+
@sessionId,
|
|
614
|
+
@transcript_file_path,
|
|
615
|
+
@total_entries,
|
|
616
|
+
@frames_created,
|
|
617
|
+
@status,
|
|
618
|
+
@started_at,
|
|
619
|
+
@completed_at,
|
|
620
|
+
@error_message
|
|
621
|
+
)
|
|
622
|
+
`).run({
|
|
623
|
+
sessionId,
|
|
624
|
+
transcript_file_path: status.transcript_file_path || '',
|
|
625
|
+
total_entries: status.total_entries || 0,
|
|
626
|
+
frames_created: status.frames_created || 0,
|
|
627
|
+
status: status.status || 'pending',
|
|
628
|
+
started_at: status.started_at || new Date().toISOString(),
|
|
629
|
+
completed_at: status.completed_at || null,
|
|
630
|
+
error_message: status.error_message || null,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Get all unique project names from transcript database
|
|
636
|
+
*
|
|
637
|
+
* @returns {string[]} Array of project names
|
|
638
|
+
*
|
|
639
|
+
* @example
|
|
640
|
+
* const projects = getTranscriptProjects();
|
|
641
|
+
* console.log(`Found ${projects.length} projects`);
|
|
642
|
+
*/
|
|
643
|
+
function getTranscriptProjects() {
|
|
644
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
645
|
+
const results = db.prepare(`
|
|
646
|
+
SELECT DISTINCT project
|
|
647
|
+
FROM session_metadata
|
|
648
|
+
ORDER BY project ASC
|
|
649
|
+
`).all();
|
|
650
|
+
return results.map(r => r.project);
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Helper: Convert database row to PlaybackFrame
|
|
654
|
+
*/
|
|
655
|
+
function convertRowToFrame(row) {
|
|
656
|
+
const frame = {
|
|
657
|
+
id: row.id,
|
|
658
|
+
type: row.frame_type,
|
|
659
|
+
timestamp: row.timestamp_ms,
|
|
660
|
+
duration: row.duration_ms || undefined,
|
|
661
|
+
agent: (row.agent_type || 'claude'),
|
|
662
|
+
context: {
|
|
663
|
+
cwd: row.cwd,
|
|
664
|
+
filesRead: row.files_read ? JSON.parse(row.files_read) : undefined,
|
|
665
|
+
filesModified: row.files_modified ? JSON.parse(row.files_modified) : undefined,
|
|
666
|
+
},
|
|
667
|
+
};
|
|
668
|
+
if (row.user_message_text) {
|
|
669
|
+
frame.userMessage = { text: row.user_message_text };
|
|
670
|
+
}
|
|
671
|
+
if (row.thinking_text) {
|
|
672
|
+
frame.thinking = {
|
|
673
|
+
text: row.thinking_text,
|
|
674
|
+
signature: row.thinking_signature || undefined,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
if (row.response_text) {
|
|
678
|
+
frame.claudeResponse = { text: row.response_text };
|
|
679
|
+
}
|
|
680
|
+
// Load tool execution if this is a tool frame
|
|
681
|
+
if (row.frame_type === 'tool_execution') {
|
|
682
|
+
const toolExecution = getToolExecution(row.id);
|
|
683
|
+
if (toolExecution) {
|
|
684
|
+
frame.toolExecution = toolExecution;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return frame;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Helper: Get import statistics
|
|
691
|
+
*
|
|
692
|
+
* @returns {{ total: number; pending: number; completed: number; failed: number }}
|
|
693
|
+
*/
|
|
694
|
+
function getImportStats() {
|
|
695
|
+
const db = (0, transcript_connection_1.getTranscriptDbInstance)();
|
|
696
|
+
const stats = db.prepare(`
|
|
697
|
+
SELECT
|
|
698
|
+
COUNT(*) as total,
|
|
699
|
+
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
|
|
700
|
+
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
|
|
701
|
+
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed
|
|
702
|
+
FROM parsing_status
|
|
703
|
+
`).get();
|
|
704
|
+
return stats;
|
|
705
|
+
}
|
|
706
|
+
//# sourceMappingURL=transcript-queries.js.map
|