cursor-usage-analyzer 0.3.0 → 0.3.1
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/.claude/settings.local.json +12 -6
- package/README.md +14 -0
- package/analyze.js +187 -42
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-35-54_uu_app_aicoding_conv55.txt +49 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-36-35_uu_app_aicoding_conv54.txt +241 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-47-45_uu_app_aicoding_conv56.txt +122 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-56-31_uu_app_aicoding_conv40.txt +80 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-58-09__unmatched__conv108.txt +26 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-59-08_uu_app_aicoding_conv57.txt +306 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-00-49_uu_app_aicoding_conv41.txt +149 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-04-15_uu_app_aicoding_conv58.txt +143 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-06-29_uu_app_aicoding_conv59.txt +119 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-17-49_uu_app_aicoding_conv60.txt +227 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-18-36_uu_app_aicoding_conv70.txt +193 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-26-21_uu_app_aicoding_conv42.txt +111 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-31-34_uu_app_aicoding_conv71.txt +232 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-40-01_uu_app_aicoding_conv72.txt +125 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-49-58_uu_app_aicoding_conv73.txt +64 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-57-27_uu_entitymanage_conv43.txt +157 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_10-02-36_uu_app_aicoding_conv44.txt +294 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_10-48-21_uu_app_aicoding_conv79.txt +181 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-13-29_uu_app_aicoding_conv45.txt +160 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-19-00_uu_app_aicoding_conv46.txt +82 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-21-15_uu_app_aicoding_conv74.txt +103 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-25-21_uu_app_aicoding_conv75.txt +119 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-26-01_uu_app_aicoding_conv47.txt +266 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-31-42_uu_entitymanage_conv48.txt +130 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-33-00_uu_app_aicoding_conv1.txt +260 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-51-10_uu_app_aicoding_conv80.txt +68 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_12-24-42_cursor_usage_an_conv106.txt +769 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_12-37-27_uu_app_aicoding_conv2.txt +897 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_12-48-53__unmatched__conv109.txt +26 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_12-51-19_uu_app_aicoding_conv3.txt +72 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_13-01-28_uu_app_aicoding_conv4.txt +112 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_13-21-29_uu_app_aicoding_conv5.txt +286 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_14-14-37_uu_app_aicoding_conv76.txt +765 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_14-25-53_uu_app_aicoding_conv7.txt +134 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_14-31-19_uu_app_aicoding_conv8.txt +118 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_15-15-16_uu_app_aicoding_conv9.txt +4644 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_15-20-50_uu_app_aicoding_conv6.txt +945 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-00-41_cursor_usage_an_conv107.txt +85 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-25-01_uu_app_aicoding_conv11.txt +274 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-29-52_uu_app_aicoding_conv10.txt +1603 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-38-00_uu_app_aicoding_conv12.txt +96 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-43-55_uu_app_aicoding_conv13.txt +74 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-47-13_uu_app_aicoding_conv14.txt +172 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-48-38_uu_cloud_univer_conv82.txt +253 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-51-54_uu_app_aicoding_conv16.txt +189 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-51-54_uu_app_aicoding_conv17.txt +57 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-59-13_uu_app_aicoding_conv15.txt +36 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-03-28_uu_app_aicoding_conv18.txt +212 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-05-14_uu_app_aicoding_conv19.txt +87 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-13-17_uu_app_aicoding_conv20.txt +77 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-25-15_uu_app_aicoding_conv21.txt +131 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-31-30_uu_app_aicoding_conv23.txt +108 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-38-46_uu_app_aicoding_conv81.txt +428 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-43-08_uu_app_aicoding_conv24.txt +15297 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-51-39_uu_app_aicoding_conv22.txt +60 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-59-43_uu_app_aicoding_conv25.txt +189 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-03-50_uu_app_aicoding_conv26.txt +120 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-30-45_uu_app_aicoding_conv83.txt +523 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-32-40_uu_app_aicoding_conv27.txt +3941 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-39-32_uu_app_aicoding_conv84.txt +133 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-41-01_uu_app_aicoding_conv28.txt +136 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-56-27_uu_app_aicoding_conv85.txt +211 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-10-56_uu_app_aicoding_conv86.txt +319 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-22-42_uu_app_aicoding_conv87.txt +193 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-27-57_uu_app_aicoding_conv88.txt +272 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-32-27_uu_app_aicoding_conv89.txt +50 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-42-59_uu_app_aicoding_conv90.txt +125 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-47-01_uu_app_aicoding_conv91.txt +102 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-58-26_uu_app_aicoding_conv92.txt +145 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_20-43-25_uu_app_aicoding_conv93.txt +553 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_20-56-36_uu_app_aicoding_conv95.txt +195 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_20-58-23_uu_app_aicoding_conv96.txt +86 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-01-26_uu_app_aicoding_conv94.txt +116 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-03-46_uu_app_aicoding_conv61.txt +1743 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-06-54_uu_app_aicoding_conv97.txt +102 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-07-32_uu_app_aicoding_conv29.txt +9930 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-09-02_uu_app_aicoding_conv98.txt +111 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-11-07_uu_app_aicoding_conv49.txt +170 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-16-16_uu_app_aicoding_conv62.txt +200 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-17-18_uu_app_aicoding_conv31.txt +351 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-26-32_uu_app_aicoding_conv99.txt +219 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-29-18_uu_app_aicoding_conv100.txt +121 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-33-35_uu_app_aicoding_conv30.txt +204 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-38-37_uu_app_aicoding_conv63.txt +251 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-42-10_uu_entitymanage_conv33.txt +163 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-43-41_uu_app_aicoding_conv64.txt +139 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-43-53_uu_app_aicoding_conv101.txt +221 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-44-55_uu_app_aicoding_conv50.txt +156 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-47-10_uu_app_aicoding_conv65.txt +136 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-48-40_uu_app_aicoding_conv51.txt +130 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-49-31_uu_app_aicoding_conv102.txt +153 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-49-44_uu_app_aicoding_conv66.txt +54 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-51-05_uu_app_aicoding_conv67.txt +55 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-51-26_uu_app_aicoding_conv32.txt +6172 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-56-08_uu_app_aicoding_conv103.txt +102 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-59-00_uu_app_aicoding_conv52.txt +244 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-10-16_uu_app_aicoding_conv77.txt +61 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-11-24_uu_app_aicoding_conv68.txt +142 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-12-31_uu_app_aicoding_conv104.txt +66 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-16-03_uu_app_aicoding_conv53.txt +439 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-23-41_uu_entitymanage_conv34.txt +2251 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-25-56_uu_app_aicoding_conv69.txt +169 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-26-54_uu_app_aicoding_conv105.txt +70 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-33-45_uu_entitymanage_conv35.txt +144 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-39-23_uu_app_aicoding_conv37.txt +104 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-45-30_uu_app_aicoding_conv78.txt +187 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_23-04-38_uu_app_aicoding_conv36.txt +2292 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_23-08-50_uu_entitymanage_conv38.txt +109 -0
- package/cursor-logs-export/chats/2026-02-05_2026-02-10_23-14-01_uu_entitymanage_conv39.txt +112 -0
- package/cursor-logs-export/report.html +3071 -0
- package/html-template.js +610 -18
- package/package.json +18 -7
- package/.idea/copilot.data.migration.agent.xml +0 -6
- package/.idea/cursor-usage-analyzer.iml +0 -12
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -11
- package/cursor-usage-analyzer-0.1.0.tgz +0 -0
- package/cursor-usage-analyzer-0.2.0.tgz +0 -0
- package/cursor-usage-analyzer-0.2.1.tgz +0 -0
- package/team-usage-events-10287858-2025-12-18.csv +0 -600
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"Bash(
|
|
4
|
+
"mcp__plus4u-mcp__document_read",
|
|
5
|
+
"mcp__plus4u-mcp__book_list_structure",
|
|
6
|
+
"Bash(npm install:*)",
|
|
7
7
|
"Bash(node -e:*)",
|
|
8
|
-
"Bash(
|
|
9
|
-
"
|
|
10
|
-
"Bash(
|
|
8
|
+
"Bash(node analyze.js:*)",
|
|
9
|
+
"mcp__skilled-plus4u-mcp__login",
|
|
10
|
+
"Bash(npm rebuild:*)",
|
|
11
|
+
"mcp__skilled-plus4u-mcp__executeSkill",
|
|
12
|
+
"Bash(node-gyp rebuild:*)",
|
|
13
|
+
"Bash(npx node-gyp rebuild:*)",
|
|
14
|
+
"Bash(npm run install:*)",
|
|
15
|
+
"Bash(npm run build:*)",
|
|
16
|
+
"Bash(node validate.js:*)"
|
|
11
17
|
]
|
|
12
18
|
}
|
|
13
19
|
}
|
package/README.md
CHANGED
|
@@ -34,8 +34,13 @@ cd cursor-usage-analyzer
|
|
|
34
34
|
|
|
35
35
|
# Install dependencies
|
|
36
36
|
npm install
|
|
37
|
+
|
|
38
|
+
# Build native bindings (runs automatically via postinstall, but run manually if needed)
|
|
39
|
+
npm run build
|
|
37
40
|
```
|
|
38
41
|
|
|
42
|
+
**Note**: The `postinstall` script automatically builds `better-sqlite3` native bindings. If you see SQLite errors, run `npm run build` manually.
|
|
43
|
+
|
|
39
44
|
## Quick Start
|
|
40
45
|
|
|
41
46
|
### Using npx
|
|
@@ -307,6 +312,15 @@ Ensure Cursor has been used and conversations exist. Check the database path for
|
|
|
307
312
|
- Verify you have conversations in Cursor from that period
|
|
308
313
|
- Ensure Cursor isn't currently running (may lock database)
|
|
309
314
|
|
|
315
|
+
### SQLite errors or "Database exceeds 2GB limit"
|
|
316
|
+
This means `better-sqlite3` native bindings weren't built properly. Fix it by:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
npm run build
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
This rebuilds the native SQLite bindings. The tool will fall back to `sql.js` (pure JavaScript) if native bindings fail, but `sql.js` has a 2GB database size limit.
|
|
323
|
+
|
|
310
324
|
### Project shows as "unknown"
|
|
311
325
|
This happens when the analyzer can't determine the workspace from file paths. The conversation is still exported with all content intact.
|
|
312
326
|
|
package/analyze.js
CHANGED
|
@@ -208,13 +208,14 @@ function parseCSVUsage(csvPath) {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// Match API calls to a conversation based on timestamp and model
|
|
211
|
-
function matchAPICallsToConversation(conv, apiCalls, timeWindow =
|
|
211
|
+
function matchAPICallsToConversation(conv, apiCalls, timeWindow = 10 * 60 * 1000) {
|
|
212
212
|
if (!apiCalls || apiCalls.length === 0) return [];
|
|
213
213
|
|
|
214
|
-
const convStart = conv.timestamp;
|
|
215
|
-
const convEnd =
|
|
216
|
-
|
|
217
|
-
:
|
|
214
|
+
const convStart = conv.createdAt || conv.timestamp;
|
|
215
|
+
const convEnd = Math.max(
|
|
216
|
+
conv.timestamp,
|
|
217
|
+
conv.messages.length > 0 ? Math.max(...conv.messages.map(m => m.timestamp)) : 0
|
|
218
|
+
);
|
|
218
219
|
|
|
219
220
|
const normalizeModel = (model) => {
|
|
220
221
|
if (!model) return 'unknown';
|
|
@@ -222,20 +223,21 @@ function matchAPICallsToConversation(conv, apiCalls, timeWindow = 5 * 60 * 1000)
|
|
|
222
223
|
if (m.includes('sonnet')) return 'sonnet';
|
|
223
224
|
if (m.includes('opus')) return 'opus';
|
|
224
225
|
if (m.includes('composer')) return 'composer';
|
|
225
|
-
if (m
|
|
226
|
-
return
|
|
226
|
+
if (m.includes('gpt')) return 'gpt';
|
|
227
|
+
if (m.includes('gemini')) return 'gemini';
|
|
228
|
+
return m;
|
|
227
229
|
};
|
|
228
230
|
|
|
229
231
|
const convModel = normalizeModel(conv.model);
|
|
232
|
+
// For 'auto', 'default', 'unknown' models - match by time only
|
|
233
|
+
const isFlexibleModel = ['auto', 'default', 'unknown'].includes(convModel);
|
|
230
234
|
|
|
231
235
|
return apiCalls.filter(call => {
|
|
232
236
|
const callModel = normalizeModel(call.model);
|
|
233
237
|
const timeMatch = call.timestamp >= (convStart - timeWindow) &&
|
|
234
238
|
call.timestamp <= (convEnd + timeWindow);
|
|
235
239
|
|
|
236
|
-
const modelMatch = convModel ===
|
|
237
|
-
callModel === 'any' ||
|
|
238
|
-
convModel === callModel;
|
|
240
|
+
const modelMatch = isFlexibleModel || convModel === callModel;
|
|
239
241
|
|
|
240
242
|
return timeMatch && modelMatch;
|
|
241
243
|
});
|
|
@@ -288,6 +290,37 @@ function readWorkspaceJson(workspaceId) {
|
|
|
288
290
|
return null;
|
|
289
291
|
}
|
|
290
292
|
|
|
293
|
+
// Build composerId -> workspaceName map from workspace databases
|
|
294
|
+
function buildComposerWorkspaceMap() {
|
|
295
|
+
const map = {};
|
|
296
|
+
try {
|
|
297
|
+
const entries = fs.readdirSync(CURSOR_WORKSPACE_STORAGE);
|
|
298
|
+
for (const entry of entries) {
|
|
299
|
+
const wsName = readWorkspaceJson(entry);
|
|
300
|
+
if (!wsName) continue;
|
|
301
|
+
|
|
302
|
+
const dbFile = path.join(CURSOR_WORKSPACE_STORAGE, entry, 'state.vscdb');
|
|
303
|
+
if (!fs.existsSync(dbFile)) continue;
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const db = openDatabase(dbFile);
|
|
307
|
+
const row = dbQueryOne(db, "SELECT value FROM ItemTable WHERE key = 'composer.composerData'");
|
|
308
|
+
if (row) {
|
|
309
|
+
const data = JSON.parse(row.value);
|
|
310
|
+
const composers = data.allComposers || [];
|
|
311
|
+
for (const c of composers) {
|
|
312
|
+
if (c.composerId) {
|
|
313
|
+
map[c.composerId] = wsName;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
dbClose(db);
|
|
318
|
+
} catch (e) {}
|
|
319
|
+
}
|
|
320
|
+
} catch (e) {}
|
|
321
|
+
return map;
|
|
322
|
+
}
|
|
323
|
+
|
|
291
324
|
// Find workspace name from file path
|
|
292
325
|
function findWorkspaceFromPath(filePath) {
|
|
293
326
|
try {
|
|
@@ -318,23 +351,69 @@ function findWorkspaceFromPath(filePath) {
|
|
|
318
351
|
return parts[parts.length - 2] || 'unknown';
|
|
319
352
|
}
|
|
320
353
|
|
|
354
|
+
// Extract file paths from various composer data sources
|
|
355
|
+
function extractFilePathsFromComposer(composerData) {
|
|
356
|
+
const paths = [];
|
|
357
|
+
|
|
358
|
+
// newlyCreatedFiles - array of objects with uri.path
|
|
359
|
+
if (Array.isArray(composerData.newlyCreatedFiles)) {
|
|
360
|
+
for (const f of composerData.newlyCreatedFiles) {
|
|
361
|
+
const p = f?.uri?.path || f?.uri?.fsPath;
|
|
362
|
+
if (p) paths.push(p);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// codeBlockData - can be array of file URI strings or object with file URI keys
|
|
367
|
+
if (Array.isArray(composerData.codeBlockData)) {
|
|
368
|
+
for (const item of composerData.codeBlockData) {
|
|
369
|
+
if (typeof item === 'string') paths.push(item.replace('file://', ''));
|
|
370
|
+
}
|
|
371
|
+
} else if (composerData.codeBlockData && typeof composerData.codeBlockData === 'object') {
|
|
372
|
+
for (const key of Object.keys(composerData.codeBlockData)) {
|
|
373
|
+
paths.push(key.replace('file://', ''));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// originalFileStates - array of file URI strings
|
|
378
|
+
if (Array.isArray(composerData.originalFileStates)) {
|
|
379
|
+
for (const item of composerData.originalFileStates) {
|
|
380
|
+
if (typeof item === 'string') paths.push(item.replace('file://', ''));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// allAttachedFileCodeChunksUris - array of file URI strings
|
|
385
|
+
if (Array.isArray(composerData.allAttachedFileCodeChunksUris)) {
|
|
386
|
+
for (const item of composerData.allAttachedFileCodeChunksUris) {
|
|
387
|
+
if (typeof item === 'string') paths.push(item.replace('file://', ''));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// addedFiles - can be array of file URI strings (sometimes it's a number)
|
|
392
|
+
if (Array.isArray(composerData.addedFiles)) {
|
|
393
|
+
for (const item of composerData.addedFiles) {
|
|
394
|
+
if (typeof item === 'string') paths.push(item.replace('file://', ''));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return paths;
|
|
399
|
+
}
|
|
400
|
+
|
|
321
401
|
// Determine workspace name from multiple sources
|
|
322
|
-
function resolveWorkspace(composerData, headers, composerId) {
|
|
323
|
-
// Try
|
|
402
|
+
function resolveWorkspace(composerData, headers, composerId, composerWorkspaceMap) {
|
|
403
|
+
// Try composer->workspace map first (most reliable)
|
|
404
|
+
if (composerWorkspaceMap && composerId && composerWorkspaceMap[composerId]) {
|
|
405
|
+
return composerWorkspaceMap[composerId];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Try workspaceId field
|
|
324
409
|
if (composerData.workspaceId) {
|
|
325
410
|
const name = readWorkspaceJson(composerData.workspaceId);
|
|
326
411
|
if (name) return name;
|
|
327
412
|
}
|
|
328
413
|
|
|
329
|
-
// Try file paths from
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
Object.keys(composerData.codeBlockData || {})[0]?.replace('file://', ''),
|
|
333
|
-
composerData.addedFiles?.[0]?.replace('file://', ''),
|
|
334
|
-
composerData.allAttachedFileCodeChunksUris?.[0]?.replace('file://', '')
|
|
335
|
-
];
|
|
336
|
-
|
|
337
|
-
for (const filePath of fileSources) {
|
|
414
|
+
// Try file paths from all available sources
|
|
415
|
+
const filePaths = extractFilePathsFromComposer(composerData);
|
|
416
|
+
for (const filePath of filePaths) {
|
|
338
417
|
if (filePath) {
|
|
339
418
|
const name = findWorkspaceFromPath(filePath);
|
|
340
419
|
if (name !== 'unknown') return name;
|
|
@@ -345,9 +424,9 @@ function resolveWorkspace(composerData, headers, composerId) {
|
|
|
345
424
|
}
|
|
346
425
|
|
|
347
426
|
// Determine workspace name with database access for messageRequestContext
|
|
348
|
-
function resolveWorkspaceWithDb(composerData, headers, db, composerId) {
|
|
427
|
+
function resolveWorkspaceWithDb(composerData, headers, db, composerId, composerWorkspaceMap) {
|
|
349
428
|
// Try basic resolution first
|
|
350
|
-
const basicResult = resolveWorkspace(composerData, headers, composerId);
|
|
429
|
+
const basicResult = resolveWorkspace(composerData, headers, composerId, composerWorkspaceMap);
|
|
351
430
|
if (basicResult !== 'unknown') return basicResult;
|
|
352
431
|
|
|
353
432
|
// Last resort: messageRequestContext
|
|
@@ -379,6 +458,10 @@ async function extractConversations(startTime, endTime, apiCalls = []) {
|
|
|
379
458
|
}
|
|
380
459
|
|
|
381
460
|
try {
|
|
461
|
+
console.log('Building workspace map...');
|
|
462
|
+
const composerWorkspaceMap = buildComposerWorkspaceMap();
|
|
463
|
+
console.log(`Mapped ${Object.keys(composerWorkspaceMap).length} composer IDs to workspaces`);
|
|
464
|
+
|
|
382
465
|
const db = openDatabase(dbPath);
|
|
383
466
|
|
|
384
467
|
// Load all bubbles
|
|
@@ -404,9 +487,12 @@ async function extractConversations(startTime, endTime, apiCalls = []) {
|
|
|
404
487
|
try {
|
|
405
488
|
const composerId = row.key.split(':')[1];
|
|
406
489
|
const composer = JSON.parse(row.value);
|
|
407
|
-
const
|
|
490
|
+
const createdAt = composer.createdAt || 0;
|
|
491
|
+
const lastUpdatedAt = composer.lastUpdatedAt || createdAt;
|
|
492
|
+
const timestamp = lastUpdatedAt || Date.now();
|
|
408
493
|
|
|
409
|
-
if
|
|
494
|
+
// Include conversation if its time range overlaps with the query range
|
|
495
|
+
if (lastUpdatedAt < startTime || createdAt > endTime) continue;
|
|
410
496
|
|
|
411
497
|
const headers = composer.fullConversationHeadersOnly || [];
|
|
412
498
|
if (headers.length === 0) continue;
|
|
@@ -423,7 +509,7 @@ async function extractConversations(startTime, endTime, apiCalls = []) {
|
|
|
423
509
|
return {
|
|
424
510
|
role: h.type === 1 ? 'user' : 'assistant',
|
|
425
511
|
text: text.trim(),
|
|
426
|
-
timestamp: bubble.timestamp ||
|
|
512
|
+
timestamp: bubble.timestamp || timestamp
|
|
427
513
|
};
|
|
428
514
|
})
|
|
429
515
|
.filter(Boolean);
|
|
@@ -434,9 +520,10 @@ async function extractConversations(startTime, endTime, apiCalls = []) {
|
|
|
434
520
|
composerId,
|
|
435
521
|
name: composer.name || 'Untitled Chat',
|
|
436
522
|
timestamp,
|
|
523
|
+
createdAt,
|
|
437
524
|
messages,
|
|
438
525
|
messageCount: messages.length,
|
|
439
|
-
workspace: resolveWorkspaceWithDb(composer, headers, db, composerId),
|
|
526
|
+
workspace: resolveWorkspaceWithDb(composer, headers, db, composerId, composerWorkspaceMap),
|
|
440
527
|
model: composer.modelConfig?.modelName || 'unknown',
|
|
441
528
|
contextTokensUsed: composer.contextTokensUsed || 0,
|
|
442
529
|
contextTokenLimit: composer.contextTokenLimit || 0,
|
|
@@ -454,22 +541,20 @@ async function extractConversations(startTime, endTime, apiCalls = []) {
|
|
|
454
541
|
// Match API calls to conversations WITHOUT double-counting
|
|
455
542
|
// Each API call should only be matched to ONE conversation
|
|
456
543
|
if (apiCalls.length > 0) {
|
|
544
|
+
// Tag each API call with its original index for tracking
|
|
545
|
+
const indexedApiCalls = apiCalls.map((call, idx) => ({ ...call, _origIdx: idx }));
|
|
457
546
|
const usedApiCallIndices = new Set();
|
|
458
547
|
|
|
459
|
-
|
|
460
|
-
|
|
548
|
+
// Sort conversations by timestamp so matching is deterministic
|
|
549
|
+
const sortedConvs = [...conversations].sort((a, b) => a.timestamp - b.timestamp);
|
|
550
|
+
|
|
551
|
+
for (const conv of sortedConvs) {
|
|
552
|
+
const availableApiCalls = indexedApiCalls.filter(c => !usedApiCallIndices.has(c._origIdx));
|
|
461
553
|
const matchedCalls = matchAPICallsToConversation(conv, availableApiCalls);
|
|
462
554
|
|
|
463
|
-
// Mark these API calls as used
|
|
555
|
+
// Mark these API calls as used by their original index
|
|
464
556
|
matchedCalls.forEach(call => {
|
|
465
|
-
|
|
466
|
-
c.timestamp === call.timestamp &&
|
|
467
|
-
c.model === call.model &&
|
|
468
|
-
c.totalTokens === call.totalTokens
|
|
469
|
-
);
|
|
470
|
-
if (originalIndex !== -1) {
|
|
471
|
-
usedApiCallIndices.add(originalIndex);
|
|
472
|
-
}
|
|
557
|
+
usedApiCallIndices.add(call._origIdx);
|
|
473
558
|
});
|
|
474
559
|
|
|
475
560
|
conv.apiCalls = matchedCalls;
|
|
@@ -485,6 +570,48 @@ async function extractConversations(startTime, endTime, apiCalls = []) {
|
|
|
485
570
|
cost: matchedCalls.reduce((sum, c) => sum + c.cost, 0)
|
|
486
571
|
};
|
|
487
572
|
}
|
|
573
|
+
|
|
574
|
+
// Group unmatched API calls by day and add as synthetic entries
|
|
575
|
+
const unmatchedCalls = indexedApiCalls.filter(c => !usedApiCallIndices.has(c._origIdx));
|
|
576
|
+
if (unmatchedCalls.length > 0) {
|
|
577
|
+
const byDay = {};
|
|
578
|
+
for (const call of unmatchedCalls) {
|
|
579
|
+
const day = new Date(call.timestamp).toLocaleDateString('en-US');
|
|
580
|
+
if (!byDay[day]) byDay[day] = [];
|
|
581
|
+
byDay[day].push(call);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
for (const [day, calls] of Object.entries(byDay)) {
|
|
585
|
+
const firstTs = Math.min(...calls.map(c => c.timestamp));
|
|
586
|
+
const models = [...new Set(calls.map(c => c.model))];
|
|
587
|
+
const modelStr = models.length <= 3 ? models.join(', ') : `${models.slice(0, 2).join(', ')} +${models.length - 2}`;
|
|
588
|
+
conversations.push({
|
|
589
|
+
composerId: `unmatched-${day}`,
|
|
590
|
+
name: `Unmatched API calls (${day})`,
|
|
591
|
+
timestamp: firstTs,
|
|
592
|
+
messages: [],
|
|
593
|
+
messageCount: 0,
|
|
594
|
+
workspace: '(unmatched)',
|
|
595
|
+
model: modelStr,
|
|
596
|
+
contextTokensUsed: 0,
|
|
597
|
+
contextTokenLimit: 0,
|
|
598
|
+
totalLinesAdded: 0,
|
|
599
|
+
totalLinesRemoved: 0,
|
|
600
|
+
filesChangedCount: 0,
|
|
601
|
+
isUnmatched: true,
|
|
602
|
+
apiCalls: calls,
|
|
603
|
+
apiCallCount: calls.length,
|
|
604
|
+
apiTokens: {
|
|
605
|
+
inputWithCache: calls.reduce((s, c) => s + c.inputWithCache, 0),
|
|
606
|
+
inputWithoutCache: calls.reduce((s, c) => s + c.inputWithoutCache, 0),
|
|
607
|
+
cacheRead: calls.reduce((s, c) => s + c.cacheRead, 0),
|
|
608
|
+
outputTokens: calls.reduce((s, c) => s + c.outputTokens, 0),
|
|
609
|
+
totalTokens: calls.reduce((s, c) => s + c.totalTokens, 0),
|
|
610
|
+
cost: calls.reduce((s, c) => s + c.cost, 0)
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
488
615
|
} else {
|
|
489
616
|
// No API calls to match
|
|
490
617
|
conversations.forEach(conv => {
|
|
@@ -569,7 +696,7 @@ function exportConversation(conv, index, dateStr) {
|
|
|
569
696
|
}
|
|
570
697
|
|
|
571
698
|
// Generate statistics
|
|
572
|
-
function generateStats(conversations, startOfDay, endOfDay) {
|
|
699
|
+
function generateStats(conversations, startOfDay, endOfDay, apiCalls = []) {
|
|
573
700
|
const daysDiff = Math.ceil((endOfDay - startOfDay) / (1000 * 60 * 60 * 24));
|
|
574
701
|
|
|
575
702
|
const stats = {
|
|
@@ -642,12 +769,30 @@ function generateStats(conversations, startOfDay, endOfDay) {
|
|
|
642
769
|
apiTokens: conv.apiTokens,
|
|
643
770
|
linesChanged: `+${conv.totalLinesAdded}/-${conv.totalLinesRemoved}`,
|
|
644
771
|
files: conv.filesChangedCount,
|
|
645
|
-
preview
|
|
772
|
+
preview,
|
|
773
|
+
exportedFile: conv.exportedFile || null,
|
|
774
|
+
isUnmatched: conv.isUnmatched || false
|
|
646
775
|
});
|
|
647
776
|
}
|
|
648
777
|
|
|
649
778
|
stats.conversations.sort((a, b) => a.timestamp - b.timestamp);
|
|
650
779
|
|
|
780
|
+
// Compute CSV totals (all API calls in range, regardless of matching)
|
|
781
|
+
if (apiCalls.length > 0) {
|
|
782
|
+
const csvTotals = { total: 0, onDemand: 0, included: 0, errored: 0, callCount: apiCalls.length };
|
|
783
|
+
for (const call of apiCalls) {
|
|
784
|
+
csvTotals.total += call.cost;
|
|
785
|
+
const kind = (call.kind || '').toLowerCase();
|
|
786
|
+
if (kind.includes('on-demand')) csvTotals.onDemand += call.cost;
|
|
787
|
+
else if (kind.includes('included')) csvTotals.included += call.cost;
|
|
788
|
+
else if (kind.includes('error')) csvTotals.errored += call.cost;
|
|
789
|
+
else csvTotals.total += 0; // already counted
|
|
790
|
+
}
|
|
791
|
+
stats.csvTotals = csvTotals;
|
|
792
|
+
stats.unmatchedApiCalls = csvTotals.callCount - stats.totalApiCalls;
|
|
793
|
+
stats.unmatchedCost = csvTotals.total - stats.totalApiTokens.cost;
|
|
794
|
+
}
|
|
795
|
+
|
|
651
796
|
return stats;
|
|
652
797
|
}
|
|
653
798
|
|
|
@@ -706,11 +851,11 @@ async function main() {
|
|
|
706
851
|
|
|
707
852
|
console.log('Exporting conversations...');
|
|
708
853
|
conversations.forEach((conv, i) => {
|
|
709
|
-
exportConversation(conv, i + 1, dateStr);
|
|
854
|
+
conv.exportedFile = exportConversation(conv, i + 1, dateStr);
|
|
710
855
|
});
|
|
711
856
|
|
|
712
857
|
console.log('Generating statistics...');
|
|
713
|
-
const stats = generateStats(conversations, startOfDay, endOfDay);
|
|
858
|
+
const stats = generateStats(conversations, startOfDay, endOfDay, apiCalls);
|
|
714
859
|
|
|
715
860
|
console.log('Generating HTML report...');
|
|
716
861
|
try {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
================================================================================
|
|
2
|
+
CONVERSATION #55
|
|
3
|
+
Name: Skills reload
|
|
4
|
+
Workspace: uu_app_aicodingg01
|
|
5
|
+
Time: 2/7/2026, 8:35:54 AM
|
|
6
|
+
Model: claude-4.5-opus-high-thinking
|
|
7
|
+
Context Tokens: 25,471 / 164,000 (15.5%)
|
|
8
|
+
Changes: +0 -0 lines in 0 files
|
|
9
|
+
Messages: 2
|
|
10
|
+
Composer ID: 21f2687c-5206-41ff-8d02-bcfe24037482
|
|
11
|
+
|
|
12
|
+
API TOKEN USAGE (from dashboard export):
|
|
13
|
+
API Calls: 4
|
|
14
|
+
Input (w/ Cache Write): 105,264
|
|
15
|
+
Input (w/o Cache Write): 429
|
|
16
|
+
Cache Read: 1,429,552
|
|
17
|
+
Output Tokens: 8,366
|
|
18
|
+
Total API Tokens: 1,543,611
|
|
19
|
+
Cost: $1.97
|
|
20
|
+
|
|
21
|
+
================================================================================
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
--------------------------------------------------------------------------------
|
|
25
|
+
[USER] 8:35:54 AM
|
|
26
|
+
--------------------------------------------------------------------------------
|
|
27
|
+
reload skills
|
|
28
|
+
|
|
29
|
+
--------------------------------------------------------------------------------
|
|
30
|
+
[ASSISTANT] 8:35:54 AM
|
|
31
|
+
--------------------------------------------------------------------------------
|
|
32
|
+
Skills reloaded successfully. 12 skills are now available:
|
|
33
|
+
|
|
34
|
+
- `bookkit-add-page`
|
|
35
|
+
- `bookkit-list-pages`
|
|
36
|
+
- `bookkit-read`
|
|
37
|
+
- `bookkit-update`
|
|
38
|
+
- `business-chat`
|
|
39
|
+
- `mngkit-read`
|
|
40
|
+
- `mngkit-update`
|
|
41
|
+
- `sls-read`
|
|
42
|
+
- `validate`
|
|
43
|
+
- `convert`
|
|
44
|
+
- `wtm-worklog`
|
|
45
|
+
- `skill`
|
|
46
|
+
|
|
47
|
+
================================================================================
|
|
48
|
+
End of conversation
|
|
49
|
+
================================================================================
|