devday 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/bin/devday.js +2 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +89 -0
- package/dist/config.js.map +1 -0
- package/dist/cost.d.ts +12 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +47 -0
- package/dist/cost.js.map +1 -0
- package/dist/git.d.ts +6 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +95 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +231 -0
- package/dist/index.js.map +1 -0
- package/dist/merge.d.ts +6 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +80 -0
- package/dist/merge.js.map +1 -0
- package/dist/parsers/claude-code.d.ts +18 -0
- package/dist/parsers/claude-code.d.ts.map +1 -0
- package/dist/parsers/claude-code.js +385 -0
- package/dist/parsers/claude-code.js.map +1 -0
- package/dist/parsers/cursor.d.ts +15 -0
- package/dist/parsers/cursor.d.ts.map +1 -0
- package/dist/parsers/cursor.js +302 -0
- package/dist/parsers/cursor.js.map +1 -0
- package/dist/parsers/opencode.d.ts +21 -0
- package/dist/parsers/opencode.d.ts.map +1 -0
- package/dist/parsers/opencode.js +301 -0
- package/dist/parsers/opencode.js.map +1 -0
- package/dist/render.d.ts +6 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +161 -0
- package/dist/render.js.map +1 -0
- package/dist/summarize.d.ts +8 -0
- package/dist/summarize.d.ts.map +1 -0
- package/dist/summarize.js +157 -0
- package/dist/summarize.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import Database from 'better-sqlite3';
|
|
5
|
+
import { estimateCost, emptyTokenUsage } from '../cost.js';
|
|
6
|
+
// ── Cursor model name → known model ID mapping ──────────────────
|
|
7
|
+
const CURSOR_MODEL_MAP = {
|
|
8
|
+
'composer-1': 'gpt-4o', // Cursor's default agent model
|
|
9
|
+
'cheetah': 'gpt-4o-mini', // Cursor's fast model
|
|
10
|
+
'default': 'gpt-4o',
|
|
11
|
+
'gpt-5': 'gpt-4o', // closest proxy
|
|
12
|
+
'gpt-5-codex': 'gpt-4o',
|
|
13
|
+
'claude-4.5-sonnet-thinking': 'claude-3-5-sonnet-20241022',
|
|
14
|
+
'claude-4-sonnet-thinking': 'claude-3-5-sonnet-20241022',
|
|
15
|
+
'claude-4.5-opus-high-thinking': 'claude-3-opus-20240229',
|
|
16
|
+
};
|
|
17
|
+
function mapCursorModel(cursorModel) {
|
|
18
|
+
return CURSOR_MODEL_MAP[cursorModel] ?? cursorModel;
|
|
19
|
+
}
|
|
20
|
+
// ── Parser ───────────────────────────────────────────────────────
|
|
21
|
+
export class CursorParser {
|
|
22
|
+
name = 'cursor';
|
|
23
|
+
dbPath;
|
|
24
|
+
constructor(dbPath) {
|
|
25
|
+
this.dbPath = dbPath;
|
|
26
|
+
}
|
|
27
|
+
async isAvailable() {
|
|
28
|
+
return existsSync(this.dbPath);
|
|
29
|
+
}
|
|
30
|
+
async getSessions(date) {
|
|
31
|
+
// Build day boundaries in local time
|
|
32
|
+
const [year, month, day] = date.split('-').map(Number);
|
|
33
|
+
const dayStart = new Date(year, month - 1, day, 0, 0, 0, 0);
|
|
34
|
+
const dayEnd = new Date(year, month - 1, day, 23, 59, 59, 999);
|
|
35
|
+
const dayStartMs = dayStart.getTime();
|
|
36
|
+
const dayEndMs = dayEnd.getTime();
|
|
37
|
+
let db;
|
|
38
|
+
try {
|
|
39
|
+
db = new Database(this.dbPath, { readonly: true });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const sessions = [];
|
|
45
|
+
try {
|
|
46
|
+
// 1. Load all composerData entries
|
|
47
|
+
const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'").all();
|
|
48
|
+
for (const row of rows) {
|
|
49
|
+
let composer;
|
|
50
|
+
try {
|
|
51
|
+
const val = typeof row.value === 'string' ? row.value : row.value.toString('utf-8');
|
|
52
|
+
composer = JSON.parse(val);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!composer.composerId)
|
|
58
|
+
continue;
|
|
59
|
+
// 2. Date filter: session must overlap with target day
|
|
60
|
+
const createdMs = composer.createdAt ?? 0;
|
|
61
|
+
const updatedMs = composer.lastUpdatedAt ?? createdMs;
|
|
62
|
+
const overlapsDay = (createdMs >= dayStartMs && createdMs <= dayEndMs) ||
|
|
63
|
+
(updatedMs >= dayStartMs && updatedMs <= dayEndMs) ||
|
|
64
|
+
(createdMs <= dayStartMs && updatedMs >= dayEndMs);
|
|
65
|
+
if (!overlapsDay)
|
|
66
|
+
continue;
|
|
67
|
+
// 3. Load bubbles for this session
|
|
68
|
+
const bubbles = this.loadBubbles(db, composer, dayStartMs, dayEndMs);
|
|
69
|
+
if (bubbles.length === 0)
|
|
70
|
+
continue;
|
|
71
|
+
// 4. Build session
|
|
72
|
+
const session = this.buildSession(composer, bubbles, dayStartMs, dayEndMs);
|
|
73
|
+
if (session)
|
|
74
|
+
sessions.push(session);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
db.close();
|
|
79
|
+
}
|
|
80
|
+
return sessions;
|
|
81
|
+
}
|
|
82
|
+
// ── Load bubbles ────────────────────────────────────────────────
|
|
83
|
+
loadBubbles(db, composer, dayStartMs, dayEndMs) {
|
|
84
|
+
const bubbles = [];
|
|
85
|
+
// Strategy 1: v1 inline conversation array
|
|
86
|
+
if (composer.conversation && Array.isArray(composer.conversation)) {
|
|
87
|
+
for (const bubble of composer.conversation) {
|
|
88
|
+
if (this.bubbleInDay(bubble, composer, dayStartMs, dayEndMs)) {
|
|
89
|
+
bubbles.push(bubble);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (bubbles.length > 0)
|
|
93
|
+
return bubbles;
|
|
94
|
+
}
|
|
95
|
+
// Strategy 2: v3+ — load from separate KV entries
|
|
96
|
+
const headers = composer.fullConversationHeadersOnly;
|
|
97
|
+
if (headers && headers.length > 0) {
|
|
98
|
+
// Batch query: get all bubbles for this composer at once
|
|
99
|
+
const composerId = composer.composerId;
|
|
100
|
+
const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE ?").all(`bubbleId:${composerId}:%`);
|
|
101
|
+
const bubbleMap = new Map();
|
|
102
|
+
for (const row of rows) {
|
|
103
|
+
try {
|
|
104
|
+
const val = typeof row.value === 'string' ? row.value : row.value.toString('utf-8');
|
|
105
|
+
const bubble = JSON.parse(val);
|
|
106
|
+
// Extract bubbleId from key: "bubbleId:{composerId}:{bubbleId}"
|
|
107
|
+
const parts = row.key.split(':');
|
|
108
|
+
const bubbleId = parts.slice(2).join(':'); // rejoin in case bubbleId contains ':'
|
|
109
|
+
if (bubble && bubbleId) {
|
|
110
|
+
bubble.bubbleId = bubble.bubbleId ?? bubbleId;
|
|
111
|
+
bubbleMap.set(bubbleId, bubble);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// skip
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Also check conversationMap (sometimes data is inline)
|
|
119
|
+
if (composer.conversationMap) {
|
|
120
|
+
for (const [id, bubble] of Object.entries(composer.conversationMap)) {
|
|
121
|
+
if (bubble && typeof bubble === 'object' && !bubbleMap.has(id)) {
|
|
122
|
+
bubble.bubbleId = bubble.bubbleId ?? id;
|
|
123
|
+
bubbleMap.set(id, bubble);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Return in conversation order, filtered by day
|
|
128
|
+
for (const header of headers) {
|
|
129
|
+
const bubble = bubbleMap.get(header.bubbleId);
|
|
130
|
+
if (bubble && bubble.type && this.bubbleInDay(bubble, composer, dayStartMs, dayEndMs)) {
|
|
131
|
+
bubbles.push(bubble);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return bubbles;
|
|
136
|
+
}
|
|
137
|
+
/** Check if a bubble's timestamp falls within the target day */
|
|
138
|
+
bubbleInDay(bubble, composer, dayStartMs, dayEndMs) {
|
|
139
|
+
const ts = this.getBubbleTimestamp(bubble, composer);
|
|
140
|
+
if (ts === null)
|
|
141
|
+
return true; // no timestamp — include by default (session already filtered)
|
|
142
|
+
return ts >= dayStartMs && ts <= dayEndMs;
|
|
143
|
+
}
|
|
144
|
+
/** Extract the best available timestamp from a bubble (ms epoch) */
|
|
145
|
+
getBubbleTimestamp(bubble, composer) {
|
|
146
|
+
// Prefer timingInfo (most reliable Unix ms timestamp)
|
|
147
|
+
if (bubble.timingInfo?.clientRpcSendTime) {
|
|
148
|
+
return bubble.timingInfo.clientRpcSendTime;
|
|
149
|
+
}
|
|
150
|
+
// v3 createdAt is ISO string
|
|
151
|
+
if (bubble.createdAt) {
|
|
152
|
+
const ts = new Date(bubble.createdAt).getTime();
|
|
153
|
+
if (!isNaN(ts))
|
|
154
|
+
return ts;
|
|
155
|
+
}
|
|
156
|
+
// Fall back to composer-level timestamps
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
// ── Build session from bubbles ──────────────────────────────────
|
|
160
|
+
buildSession(composer, bubbles, dayStartMs, dayEndMs) {
|
|
161
|
+
const MAX_MSG_DURATION_MS = 5 * 60 * 1000;
|
|
162
|
+
const home = homedir();
|
|
163
|
+
const userBubbles = bubbles.filter((b) => b.type === 1);
|
|
164
|
+
const aiBubbles = bubbles.filter((b) => b.type === 2);
|
|
165
|
+
if (userBubbles.length === 0 && aiBubbles.length === 0)
|
|
166
|
+
return null;
|
|
167
|
+
// ── Model detection ───────────────────────────────────────
|
|
168
|
+
const models = new Set();
|
|
169
|
+
for (const b of aiBubbles) {
|
|
170
|
+
const raw = b.modelInfo?.modelName ?? composer.modelConfig?.modelName;
|
|
171
|
+
if (raw)
|
|
172
|
+
models.add(raw);
|
|
173
|
+
}
|
|
174
|
+
if (models.size === 0 && composer.modelConfig?.modelName) {
|
|
175
|
+
models.add(composer.modelConfig.modelName);
|
|
176
|
+
}
|
|
177
|
+
// ── Token aggregation ─────────────────────────────────────
|
|
178
|
+
const totalTokens = emptyTokenUsage();
|
|
179
|
+
for (const b of aiBubbles) {
|
|
180
|
+
if (b.tokenCount) {
|
|
181
|
+
totalTokens.input += b.tokenCount.inputTokens ?? 0;
|
|
182
|
+
totalTokens.output += b.tokenCount.outputTokens ?? 0;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
totalTokens.total = totalTokens.input + totalTokens.output;
|
|
186
|
+
// ── Cost estimation ───────────────────────────────────────
|
|
187
|
+
let totalCost = 0;
|
|
188
|
+
if (totalTokens.total > 0 && models.size > 0) {
|
|
189
|
+
const mappedModel = mapCursorModel([...models][0]);
|
|
190
|
+
totalCost = estimateCost(mappedModel, totalTokens);
|
|
191
|
+
}
|
|
192
|
+
// ── Duration ──────────────────────────────────────────────
|
|
193
|
+
let totalDurationMs = 0;
|
|
194
|
+
for (const b of aiBubbles) {
|
|
195
|
+
const ti = b.timingInfo;
|
|
196
|
+
if (ti?.clientRpcSendTime && ti?.clientEndTime) {
|
|
197
|
+
const dur = ti.clientEndTime - ti.clientRpcSendTime;
|
|
198
|
+
if (dur > 0) {
|
|
199
|
+
totalDurationMs += Math.min(dur, MAX_MSG_DURATION_MS);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Fallback: if no timingInfo, estimate from timestamp gaps
|
|
204
|
+
if (totalDurationMs === 0 && bubbles.length > 1) {
|
|
205
|
+
const timestamps = bubbles
|
|
206
|
+
.map((b) => this.getBubbleTimestamp(b, composer))
|
|
207
|
+
.filter((t) => t !== null)
|
|
208
|
+
.sort((a, b) => a - b);
|
|
209
|
+
for (let i = 1; i < timestamps.length; i++) {
|
|
210
|
+
const gap = timestamps[i] - timestamps[i - 1];
|
|
211
|
+
if (gap > 0) {
|
|
212
|
+
totalDurationMs += Math.min(gap, MAX_MSG_DURATION_MS);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// ── Project path ──────────────────────────────────────────
|
|
217
|
+
// Extract from first bubble's cwd, or from any bubble that has it
|
|
218
|
+
let projectPath = null;
|
|
219
|
+
for (const b of bubbles) {
|
|
220
|
+
if (b.cwd) {
|
|
221
|
+
projectPath = b.cwd;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ── Timestamps ────────────────────────────────────────────
|
|
226
|
+
const allTimestamps = bubbles
|
|
227
|
+
.map((b) => this.getBubbleTimestamp(b, composer))
|
|
228
|
+
.filter((t) => t !== null);
|
|
229
|
+
const earliest = allTimestamps.length > 0
|
|
230
|
+
? Math.max(dayStartMs, Math.min(...allTimestamps))
|
|
231
|
+
: composer.createdAt ?? dayStartMs;
|
|
232
|
+
const latest = allTimestamps.length > 0
|
|
233
|
+
? Math.min(dayEndMs, Math.max(...allTimestamps))
|
|
234
|
+
: composer.lastUpdatedAt ?? dayEndMs;
|
|
235
|
+
// ── Content extraction ────────────────────────────────────
|
|
236
|
+
const digestParts = [];
|
|
237
|
+
const toolSummaries = [];
|
|
238
|
+
const files = new Set();
|
|
239
|
+
for (const b of bubbles) {
|
|
240
|
+
if (!b.text?.trim())
|
|
241
|
+
continue;
|
|
242
|
+
if (b.type === 1) {
|
|
243
|
+
const truncated = b.text.length > 500 ? b.text.slice(0, 500) + '...' : b.text;
|
|
244
|
+
digestParts.push(`[User]: ${truncated}`);
|
|
245
|
+
}
|
|
246
|
+
else if (b.type === 2) {
|
|
247
|
+
const truncated = b.text.length > 500 ? b.text.slice(0, 500) + '...' : b.text;
|
|
248
|
+
digestParts.push(`[Assistant]: ${truncated}`);
|
|
249
|
+
}
|
|
250
|
+
// Extract files from code blocks
|
|
251
|
+
if (b.codeBlocks) {
|
|
252
|
+
for (const cb of b.codeBlocks) {
|
|
253
|
+
const filePath = cb.uri?.fsPath ?? cb.uri?.path;
|
|
254
|
+
if (filePath) {
|
|
255
|
+
files.add(filePath);
|
|
256
|
+
const short = filePath.startsWith(home) ? '~' + filePath.slice(home.length) : filePath;
|
|
257
|
+
toolSummaries.push(`edit ${short}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Cap digest
|
|
263
|
+
let conversationDigest = digestParts.join('\n\n');
|
|
264
|
+
if (conversationDigest.length > 4000) {
|
|
265
|
+
conversationDigest = conversationDigest.slice(0, 4000) + '\n\n[...truncated]';
|
|
266
|
+
}
|
|
267
|
+
// Dedup tool summaries
|
|
268
|
+
const uniqueToolSummaries = [...new Set(toolSummaries)];
|
|
269
|
+
// ── Title ─────────────────────────────────────────────────
|
|
270
|
+
const title = composer.name ?? null;
|
|
271
|
+
// ── Topics ────────────────────────────────────────────────
|
|
272
|
+
const topics = [];
|
|
273
|
+
if (composer.name)
|
|
274
|
+
topics.push(composer.name);
|
|
275
|
+
if (composer.subtitle)
|
|
276
|
+
topics.push(composer.subtitle);
|
|
277
|
+
// Map model names for display
|
|
278
|
+
const displayModels = [...models].map(mapCursorModel);
|
|
279
|
+
return {
|
|
280
|
+
id: composer.composerId,
|
|
281
|
+
tool: 'cursor',
|
|
282
|
+
projectPath,
|
|
283
|
+
projectName: projectPath ? basename(projectPath) : null,
|
|
284
|
+
title,
|
|
285
|
+
startedAt: new Date(earliest),
|
|
286
|
+
endedAt: new Date(latest),
|
|
287
|
+
durationMs: totalDurationMs,
|
|
288
|
+
messageCount: bubbles.length,
|
|
289
|
+
userMessageCount: userBubbles.length,
|
|
290
|
+
assistantMessageCount: aiBubbles.length,
|
|
291
|
+
summary: composer.name ?? null,
|
|
292
|
+
topics,
|
|
293
|
+
tokens: totalTokens,
|
|
294
|
+
costUsd: totalCost,
|
|
295
|
+
models: displayModels,
|
|
296
|
+
filesTouched: [...files],
|
|
297
|
+
conversationDigest,
|
|
298
|
+
toolCallSummaries: uniqueToolSummaries,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/parsers/cursor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Parser, Session } from '../types.js';
|
|
2
|
+
export declare class OpenCodeParser implements Parser {
|
|
3
|
+
readonly name: "opencode";
|
|
4
|
+
private storagePath;
|
|
5
|
+
constructor(storagePath: string);
|
|
6
|
+
isAvailable(): Promise<boolean>;
|
|
7
|
+
getSessions(date: string): Promise<Session[]>;
|
|
8
|
+
private loadProjects;
|
|
9
|
+
private loadSessionsForProject;
|
|
10
|
+
private loadMessagesForSession;
|
|
11
|
+
private loadPartsForMessage;
|
|
12
|
+
/**
|
|
13
|
+
* Single-pass extraction of all session content:
|
|
14
|
+
* - conversationDigest: user prompts + assistant text (for LLM summarization)
|
|
15
|
+
* - toolCallSummaries: human-readable tool call descriptions
|
|
16
|
+
* - filesTouched: unique file paths from tool calls and patches
|
|
17
|
+
*/
|
|
18
|
+
private extractSessionContent;
|
|
19
|
+
private messageToTokens;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=opencode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../src/parsers/opencode.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAc,MAAM,aAAa,CAAC;AA6E/D,qBAAa,cAAe,YAAW,MAAM;IAC3C,QAAQ,CAAC,IAAI,EAAG,UAAU,CAAU;IACpC,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM;IAIzB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IA2InD,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,sBAAsB;IAgB9B,OAAO,CAAC,sBAAsB;IAgB9B,OAAO,CAAC,mBAAmB;IAgB3B;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IAyF7B,OAAO,CAAC,eAAe;CAkBxB"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, basename } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { estimateCost, emptyTokenUsage, sumTokens } from '../cost.js';
|
|
5
|
+
// ── Parser ───────────────────────────────────────────────────────
|
|
6
|
+
export class OpenCodeParser {
|
|
7
|
+
name = 'opencode';
|
|
8
|
+
storagePath;
|
|
9
|
+
constructor(storagePath) {
|
|
10
|
+
this.storagePath = storagePath;
|
|
11
|
+
}
|
|
12
|
+
async isAvailable() {
|
|
13
|
+
return existsSync(this.storagePath) && existsSync(join(this.storagePath, 'project'));
|
|
14
|
+
}
|
|
15
|
+
async getSessions(date) {
|
|
16
|
+
// Parse YYYY-MM-DD and build day boundaries in local time manually.
|
|
17
|
+
// Avoids date-fns startOfDay/endOfDay which can mishandle timezones.
|
|
18
|
+
const [year, month, day] = date.split('-').map(Number);
|
|
19
|
+
const dayStart = new Date(year, month - 1, day, 0, 0, 0, 0);
|
|
20
|
+
const dayEnd = new Date(year, month - 1, day, 23, 59, 59, 999);
|
|
21
|
+
const dayStartMs = dayStart.getTime();
|
|
22
|
+
const dayEndMs = dayEnd.getTime();
|
|
23
|
+
// 1. Load all projects
|
|
24
|
+
const projects = this.loadProjects();
|
|
25
|
+
const sessions = [];
|
|
26
|
+
for (const project of projects) {
|
|
27
|
+
const isGlobal = project.worktree === '/';
|
|
28
|
+
// 2. Load sessions for this project
|
|
29
|
+
const ocSessions = this.loadSessionsForProject(project.id);
|
|
30
|
+
for (const ocSession of ocSessions) {
|
|
31
|
+
// Filter by date: session must overlap with the target day
|
|
32
|
+
const sessionCreated = new Date(ocSession.time.created);
|
|
33
|
+
const sessionUpdated = new Date(ocSession.time.updated);
|
|
34
|
+
const overlapsDay = (sessionCreated >= dayStart && sessionCreated <= dayEnd) ||
|
|
35
|
+
(sessionUpdated >= dayStart && sessionUpdated <= dayEnd) ||
|
|
36
|
+
(sessionCreated <= dayStart && sessionUpdated >= dayEnd);
|
|
37
|
+
if (!overlapsDay)
|
|
38
|
+
continue;
|
|
39
|
+
// Skip sub-agent sessions (they have a parentID, their data is attributed to the parent)
|
|
40
|
+
if (ocSession.parentID)
|
|
41
|
+
continue;
|
|
42
|
+
// For global project sessions, use the session's directory as the project path.
|
|
43
|
+
// Skip if the directory is also "/" or missing (truly global, no project context).
|
|
44
|
+
if (isGlobal && (!ocSession.directory || ocSession.directory === '/'))
|
|
45
|
+
continue;
|
|
46
|
+
// 3. Load messages for this session (including sub-agent sessions)
|
|
47
|
+
const childSessionIds = ocSessions
|
|
48
|
+
.filter((s) => s.parentID === ocSession.id)
|
|
49
|
+
.map((s) => s.id);
|
|
50
|
+
const allSessionIds = [ocSession.id, ...childSessionIds];
|
|
51
|
+
const messages = allSessionIds.flatMap((sid) => this.loadMessagesForSession(sid));
|
|
52
|
+
// Filter messages to only those within the target day
|
|
53
|
+
const dayMessages = messages.filter((m) => {
|
|
54
|
+
const createdMs = m.time.created;
|
|
55
|
+
return createdMs >= dayStartMs && createdMs <= dayEndMs;
|
|
56
|
+
});
|
|
57
|
+
if (dayMessages.length === 0)
|
|
58
|
+
continue;
|
|
59
|
+
// 4. Compute aggregates from messages
|
|
60
|
+
const userMessages = dayMessages.filter((m) => m.role === 'user');
|
|
61
|
+
const assistantMessages = dayMessages.filter((m) => m.role === 'assistant');
|
|
62
|
+
const models = [...new Set(assistantMessages.map((m) => m.modelID).filter(Boolean))];
|
|
63
|
+
// Token aggregation
|
|
64
|
+
const tokenUsages = dayMessages.map((m) => this.messageToTokens(m));
|
|
65
|
+
const totalTokens = sumTokens(...tokenUsages);
|
|
66
|
+
// Cost estimation (OpenCode cost field is always 0)
|
|
67
|
+
let totalCost = 0;
|
|
68
|
+
for (const msg of assistantMessages) {
|
|
69
|
+
if (msg.modelID && msg.tokens) {
|
|
70
|
+
totalCost += estimateCost(msg.modelID, this.messageToTokens(msg));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// 5. Extract content: files touched, conversation text, tool call summaries
|
|
74
|
+
const contentExtraction = this.extractSessionContent(dayMessages);
|
|
75
|
+
// 6. Duration — sum actual message processing times, not wall-clock span
|
|
76
|
+
// Each message has created → completed timestamps representing active time.
|
|
77
|
+
// Clamp to target day boundaries to avoid cross-day inflation.
|
|
78
|
+
// Cap individual message duration at 5 minutes — no single LLM response
|
|
79
|
+
// should take longer; OpenCode sometimes writes bogus completed timestamps
|
|
80
|
+
// (e.g. reconciliation when session is loaded days later).
|
|
81
|
+
const MAX_MSG_DURATION_MS = 5 * 60 * 1000; // 5 minutes
|
|
82
|
+
let durationMs = 0;
|
|
83
|
+
let earliestTs = Infinity;
|
|
84
|
+
let latestTs = -Infinity;
|
|
85
|
+
for (const msg of dayMessages) {
|
|
86
|
+
const start = Math.max(dayStartMs, msg.time.created);
|
|
87
|
+
const end = msg.time.completed
|
|
88
|
+
? Math.min(dayEndMs, msg.time.completed)
|
|
89
|
+
: start; // no completion = instant
|
|
90
|
+
if (end > start) {
|
|
91
|
+
durationMs += Math.min(end - start, MAX_MSG_DURATION_MS);
|
|
92
|
+
}
|
|
93
|
+
if (msg.time.created < earliestTs)
|
|
94
|
+
earliestTs = msg.time.created;
|
|
95
|
+
const msgEnd = msg.time.completed ?? msg.time.created;
|
|
96
|
+
if (msgEnd > latestTs)
|
|
97
|
+
latestTs = msgEnd;
|
|
98
|
+
}
|
|
99
|
+
// Clamp start/end for display purposes
|
|
100
|
+
const earliest = Math.max(dayStartMs, earliestTs);
|
|
101
|
+
const latest = Math.min(dayEndMs, latestTs === -Infinity ? earliest : latestTs);
|
|
102
|
+
// 7. Build topics from session title + summary
|
|
103
|
+
const topics = [];
|
|
104
|
+
if (ocSession.title)
|
|
105
|
+
topics.push(ocSession.title);
|
|
106
|
+
const sessionProjectPath = isGlobal ? ocSession.directory : project.worktree;
|
|
107
|
+
sessions.push({
|
|
108
|
+
id: ocSession.id,
|
|
109
|
+
tool: 'opencode',
|
|
110
|
+
projectPath: sessionProjectPath,
|
|
111
|
+
projectName: basename(sessionProjectPath),
|
|
112
|
+
title: ocSession.title ?? ocSession.slug ?? null,
|
|
113
|
+
startedAt: new Date(earliest),
|
|
114
|
+
endedAt: new Date(latest),
|
|
115
|
+
durationMs,
|
|
116
|
+
messageCount: dayMessages.length,
|
|
117
|
+
userMessageCount: userMessages.length,
|
|
118
|
+
assistantMessageCount: assistantMessages.length,
|
|
119
|
+
summary: ocSession.title ?? null,
|
|
120
|
+
topics,
|
|
121
|
+
tokens: totalTokens,
|
|
122
|
+
costUsd: totalCost,
|
|
123
|
+
models,
|
|
124
|
+
filesTouched: contentExtraction.filesTouched,
|
|
125
|
+
conversationDigest: contentExtraction.conversationDigest,
|
|
126
|
+
toolCallSummaries: contentExtraction.toolCallSummaries,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return sessions;
|
|
131
|
+
}
|
|
132
|
+
// ── Internal helpers ─────────────────────────────────────────
|
|
133
|
+
loadProjects() {
|
|
134
|
+
const dir = join(this.storagePath, 'project');
|
|
135
|
+
if (!existsSync(dir))
|
|
136
|
+
return [];
|
|
137
|
+
return readdirSync(dir)
|
|
138
|
+
.filter((f) => f.endsWith('.json'))
|
|
139
|
+
.map((f) => {
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(readFileSync(join(dir, f), 'utf-8'));
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
.filter((p) => p !== null);
|
|
148
|
+
}
|
|
149
|
+
loadSessionsForProject(projectId) {
|
|
150
|
+
const dir = join(this.storagePath, 'session', projectId);
|
|
151
|
+
if (!existsSync(dir))
|
|
152
|
+
return [];
|
|
153
|
+
return readdirSync(dir)
|
|
154
|
+
.filter((f) => f.endsWith('.json'))
|
|
155
|
+
.map((f) => {
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(readFileSync(join(dir, f), 'utf-8'));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.filter((s) => s !== null);
|
|
164
|
+
}
|
|
165
|
+
loadMessagesForSession(sessionId) {
|
|
166
|
+
const dir = join(this.storagePath, 'message', sessionId);
|
|
167
|
+
if (!existsSync(dir))
|
|
168
|
+
return [];
|
|
169
|
+
return readdirSync(dir)
|
|
170
|
+
.filter((f) => f.endsWith('.json'))
|
|
171
|
+
.map((f) => {
|
|
172
|
+
try {
|
|
173
|
+
return JSON.parse(readFileSync(join(dir, f), 'utf-8'));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
.filter((m) => m !== null);
|
|
180
|
+
}
|
|
181
|
+
loadPartsForMessage(messageId) {
|
|
182
|
+
const dir = join(this.storagePath, 'part', messageId);
|
|
183
|
+
if (!existsSync(dir))
|
|
184
|
+
return [];
|
|
185
|
+
return readdirSync(dir)
|
|
186
|
+
.filter((f) => f.endsWith('.json'))
|
|
187
|
+
.map((f) => {
|
|
188
|
+
try {
|
|
189
|
+
return JSON.parse(readFileSync(join(dir, f), 'utf-8'));
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
.filter((p) => p !== null);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Single-pass extraction of all session content:
|
|
199
|
+
* - conversationDigest: user prompts + assistant text (for LLM summarization)
|
|
200
|
+
* - toolCallSummaries: human-readable tool call descriptions
|
|
201
|
+
* - filesTouched: unique file paths from tool calls and patches
|
|
202
|
+
*/
|
|
203
|
+
extractSessionContent(messages) {
|
|
204
|
+
const digestParts = [];
|
|
205
|
+
const toolSummaries = [];
|
|
206
|
+
const files = new Set();
|
|
207
|
+
// Sort messages by creation time
|
|
208
|
+
const sorted = [...messages].sort((a, b) => a.time.created - b.time.created);
|
|
209
|
+
for (const msg of sorted) {
|
|
210
|
+
const parts = this.loadPartsForMessage(msg.id);
|
|
211
|
+
// Sort parts by their time if available
|
|
212
|
+
const textParts = [];
|
|
213
|
+
for (const part of parts) {
|
|
214
|
+
if (part.type === 'text' && part.text) {
|
|
215
|
+
textParts.push(part.text);
|
|
216
|
+
}
|
|
217
|
+
if (part.type === 'tool') {
|
|
218
|
+
const toolPart = part;
|
|
219
|
+
const toolName = toolPart.tool;
|
|
220
|
+
const input = toolPart.state?.input;
|
|
221
|
+
const title = toolPart.state?.title;
|
|
222
|
+
// Build a human-readable tool call summary
|
|
223
|
+
let summary = toolName;
|
|
224
|
+
if (title) {
|
|
225
|
+
summary = `${toolName}: ${title}`;
|
|
226
|
+
}
|
|
227
|
+
else if (input) {
|
|
228
|
+
const filePath = (input.filePath ?? input.path ?? input.file);
|
|
229
|
+
const command = input.command;
|
|
230
|
+
const pattern = input.pattern;
|
|
231
|
+
if (filePath) {
|
|
232
|
+
// Shorten to relative path (cross-platform)
|
|
233
|
+
const home = homedir();
|
|
234
|
+
const short = filePath.startsWith(home) ? '~' + filePath.slice(home.length) : filePath;
|
|
235
|
+
summary = `${toolName} ${short}`;
|
|
236
|
+
}
|
|
237
|
+
else if (command) {
|
|
238
|
+
summary = `bash: ${String(command).slice(0, 80)}`;
|
|
239
|
+
}
|
|
240
|
+
else if (pattern) {
|
|
241
|
+
summary = `${toolName}: ${pattern}`;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
toolSummaries.push(summary);
|
|
245
|
+
// Extract file paths
|
|
246
|
+
if (input) {
|
|
247
|
+
if (typeof input.filePath === 'string')
|
|
248
|
+
files.add(input.filePath);
|
|
249
|
+
if (typeof input.path === 'string')
|
|
250
|
+
files.add(input.path);
|
|
251
|
+
if (typeof input.file === 'string')
|
|
252
|
+
files.add(input.file);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (part.type === 'patch') {
|
|
256
|
+
const patchPart = part;
|
|
257
|
+
if (patchPart.path) {
|
|
258
|
+
files.add(patchPart.path);
|
|
259
|
+
toolSummaries.push(`patch: ${patchPart.path}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Add to conversation digest
|
|
264
|
+
if (textParts.length > 0) {
|
|
265
|
+
const role = msg.role === 'user' ? 'User' : 'Assistant';
|
|
266
|
+
const text = textParts.join('\n');
|
|
267
|
+
// Truncate individual messages to keep digest manageable
|
|
268
|
+
const truncated = text.length > 500 ? text.slice(0, 500) + '...' : text;
|
|
269
|
+
digestParts.push(`[${role}]: ${truncated}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Cap total digest at ~4000 chars (fits in an LLM context easily)
|
|
273
|
+
let conversationDigest = digestParts.join('\n\n');
|
|
274
|
+
if (conversationDigest.length > 4000) {
|
|
275
|
+
conversationDigest = conversationDigest.slice(0, 4000) + '\n\n[...truncated]';
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
conversationDigest,
|
|
279
|
+
toolCallSummaries: toolSummaries,
|
|
280
|
+
filesTouched: [...files],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
messageToTokens(msg) {
|
|
284
|
+
if (!msg.tokens)
|
|
285
|
+
return emptyTokenUsage();
|
|
286
|
+
const input = msg.tokens.input ?? 0;
|
|
287
|
+
const output = msg.tokens.output ?? 0;
|
|
288
|
+
const reasoning = msg.tokens.reasoning ?? 0;
|
|
289
|
+
const cacheRead = msg.tokens.cache?.read ?? 0;
|
|
290
|
+
const cacheWrite = msg.tokens.cache?.write ?? 0;
|
|
291
|
+
return {
|
|
292
|
+
input,
|
|
293
|
+
output,
|
|
294
|
+
reasoning,
|
|
295
|
+
cacheRead,
|
|
296
|
+
cacheWrite,
|
|
297
|
+
total: input + output + reasoning + cacheRead + cacheWrite,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=opencode.js.map
|