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.
Files changed (123) hide show
  1. package/.claude/settings.local.json +12 -6
  2. package/README.md +14 -0
  3. package/analyze.js +187 -42
  4. package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-35-54_uu_app_aicoding_conv55.txt +49 -0
  5. package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-36-35_uu_app_aicoding_conv54.txt +241 -0
  6. package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-47-45_uu_app_aicoding_conv56.txt +122 -0
  7. package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-56-31_uu_app_aicoding_conv40.txt +80 -0
  8. package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-58-09__unmatched__conv108.txt +26 -0
  9. package/cursor-logs-export/chats/2026-02-05_2026-02-10_08-59-08_uu_app_aicoding_conv57.txt +306 -0
  10. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-00-49_uu_app_aicoding_conv41.txt +149 -0
  11. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-04-15_uu_app_aicoding_conv58.txt +143 -0
  12. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-06-29_uu_app_aicoding_conv59.txt +119 -0
  13. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-17-49_uu_app_aicoding_conv60.txt +227 -0
  14. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-18-36_uu_app_aicoding_conv70.txt +193 -0
  15. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-26-21_uu_app_aicoding_conv42.txt +111 -0
  16. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-31-34_uu_app_aicoding_conv71.txt +232 -0
  17. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-40-01_uu_app_aicoding_conv72.txt +125 -0
  18. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-49-58_uu_app_aicoding_conv73.txt +64 -0
  19. package/cursor-logs-export/chats/2026-02-05_2026-02-10_09-57-27_uu_entitymanage_conv43.txt +157 -0
  20. package/cursor-logs-export/chats/2026-02-05_2026-02-10_10-02-36_uu_app_aicoding_conv44.txt +294 -0
  21. package/cursor-logs-export/chats/2026-02-05_2026-02-10_10-48-21_uu_app_aicoding_conv79.txt +181 -0
  22. package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-13-29_uu_app_aicoding_conv45.txt +160 -0
  23. package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-19-00_uu_app_aicoding_conv46.txt +82 -0
  24. package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-21-15_uu_app_aicoding_conv74.txt +103 -0
  25. package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-25-21_uu_app_aicoding_conv75.txt +119 -0
  26. package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-26-01_uu_app_aicoding_conv47.txt +266 -0
  27. package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-31-42_uu_entitymanage_conv48.txt +130 -0
  28. package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-33-00_uu_app_aicoding_conv1.txt +260 -0
  29. package/cursor-logs-export/chats/2026-02-05_2026-02-10_11-51-10_uu_app_aicoding_conv80.txt +68 -0
  30. package/cursor-logs-export/chats/2026-02-05_2026-02-10_12-24-42_cursor_usage_an_conv106.txt +769 -0
  31. package/cursor-logs-export/chats/2026-02-05_2026-02-10_12-37-27_uu_app_aicoding_conv2.txt +897 -0
  32. package/cursor-logs-export/chats/2026-02-05_2026-02-10_12-48-53__unmatched__conv109.txt +26 -0
  33. package/cursor-logs-export/chats/2026-02-05_2026-02-10_12-51-19_uu_app_aicoding_conv3.txt +72 -0
  34. package/cursor-logs-export/chats/2026-02-05_2026-02-10_13-01-28_uu_app_aicoding_conv4.txt +112 -0
  35. package/cursor-logs-export/chats/2026-02-05_2026-02-10_13-21-29_uu_app_aicoding_conv5.txt +286 -0
  36. package/cursor-logs-export/chats/2026-02-05_2026-02-10_14-14-37_uu_app_aicoding_conv76.txt +765 -0
  37. package/cursor-logs-export/chats/2026-02-05_2026-02-10_14-25-53_uu_app_aicoding_conv7.txt +134 -0
  38. package/cursor-logs-export/chats/2026-02-05_2026-02-10_14-31-19_uu_app_aicoding_conv8.txt +118 -0
  39. package/cursor-logs-export/chats/2026-02-05_2026-02-10_15-15-16_uu_app_aicoding_conv9.txt +4644 -0
  40. package/cursor-logs-export/chats/2026-02-05_2026-02-10_15-20-50_uu_app_aicoding_conv6.txt +945 -0
  41. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-00-41_cursor_usage_an_conv107.txt +85 -0
  42. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-25-01_uu_app_aicoding_conv11.txt +274 -0
  43. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-29-52_uu_app_aicoding_conv10.txt +1603 -0
  44. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-38-00_uu_app_aicoding_conv12.txt +96 -0
  45. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-43-55_uu_app_aicoding_conv13.txt +74 -0
  46. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-47-13_uu_app_aicoding_conv14.txt +172 -0
  47. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-48-38_uu_cloud_univer_conv82.txt +253 -0
  48. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-51-54_uu_app_aicoding_conv16.txt +189 -0
  49. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-51-54_uu_app_aicoding_conv17.txt +57 -0
  50. package/cursor-logs-export/chats/2026-02-05_2026-02-10_16-59-13_uu_app_aicoding_conv15.txt +36 -0
  51. package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-03-28_uu_app_aicoding_conv18.txt +212 -0
  52. package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-05-14_uu_app_aicoding_conv19.txt +87 -0
  53. package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-13-17_uu_app_aicoding_conv20.txt +77 -0
  54. package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-25-15_uu_app_aicoding_conv21.txt +131 -0
  55. package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-31-30_uu_app_aicoding_conv23.txt +108 -0
  56. package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-38-46_uu_app_aicoding_conv81.txt +428 -0
  57. package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-43-08_uu_app_aicoding_conv24.txt +15297 -0
  58. package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-51-39_uu_app_aicoding_conv22.txt +60 -0
  59. package/cursor-logs-export/chats/2026-02-05_2026-02-10_17-59-43_uu_app_aicoding_conv25.txt +189 -0
  60. package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-03-50_uu_app_aicoding_conv26.txt +120 -0
  61. package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-30-45_uu_app_aicoding_conv83.txt +523 -0
  62. package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-32-40_uu_app_aicoding_conv27.txt +3941 -0
  63. package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-39-32_uu_app_aicoding_conv84.txt +133 -0
  64. package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-41-01_uu_app_aicoding_conv28.txt +136 -0
  65. package/cursor-logs-export/chats/2026-02-05_2026-02-10_18-56-27_uu_app_aicoding_conv85.txt +211 -0
  66. package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-10-56_uu_app_aicoding_conv86.txt +319 -0
  67. package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-22-42_uu_app_aicoding_conv87.txt +193 -0
  68. package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-27-57_uu_app_aicoding_conv88.txt +272 -0
  69. package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-32-27_uu_app_aicoding_conv89.txt +50 -0
  70. package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-42-59_uu_app_aicoding_conv90.txt +125 -0
  71. package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-47-01_uu_app_aicoding_conv91.txt +102 -0
  72. package/cursor-logs-export/chats/2026-02-05_2026-02-10_19-58-26_uu_app_aicoding_conv92.txt +145 -0
  73. package/cursor-logs-export/chats/2026-02-05_2026-02-10_20-43-25_uu_app_aicoding_conv93.txt +553 -0
  74. package/cursor-logs-export/chats/2026-02-05_2026-02-10_20-56-36_uu_app_aicoding_conv95.txt +195 -0
  75. package/cursor-logs-export/chats/2026-02-05_2026-02-10_20-58-23_uu_app_aicoding_conv96.txt +86 -0
  76. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-01-26_uu_app_aicoding_conv94.txt +116 -0
  77. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-03-46_uu_app_aicoding_conv61.txt +1743 -0
  78. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-06-54_uu_app_aicoding_conv97.txt +102 -0
  79. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-07-32_uu_app_aicoding_conv29.txt +9930 -0
  80. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-09-02_uu_app_aicoding_conv98.txt +111 -0
  81. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-11-07_uu_app_aicoding_conv49.txt +170 -0
  82. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-16-16_uu_app_aicoding_conv62.txt +200 -0
  83. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-17-18_uu_app_aicoding_conv31.txt +351 -0
  84. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-26-32_uu_app_aicoding_conv99.txt +219 -0
  85. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-29-18_uu_app_aicoding_conv100.txt +121 -0
  86. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-33-35_uu_app_aicoding_conv30.txt +204 -0
  87. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-38-37_uu_app_aicoding_conv63.txt +251 -0
  88. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-42-10_uu_entitymanage_conv33.txt +163 -0
  89. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-43-41_uu_app_aicoding_conv64.txt +139 -0
  90. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-43-53_uu_app_aicoding_conv101.txt +221 -0
  91. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-44-55_uu_app_aicoding_conv50.txt +156 -0
  92. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-47-10_uu_app_aicoding_conv65.txt +136 -0
  93. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-48-40_uu_app_aicoding_conv51.txt +130 -0
  94. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-49-31_uu_app_aicoding_conv102.txt +153 -0
  95. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-49-44_uu_app_aicoding_conv66.txt +54 -0
  96. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-51-05_uu_app_aicoding_conv67.txt +55 -0
  97. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-51-26_uu_app_aicoding_conv32.txt +6172 -0
  98. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-56-08_uu_app_aicoding_conv103.txt +102 -0
  99. package/cursor-logs-export/chats/2026-02-05_2026-02-10_21-59-00_uu_app_aicoding_conv52.txt +244 -0
  100. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-10-16_uu_app_aicoding_conv77.txt +61 -0
  101. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-11-24_uu_app_aicoding_conv68.txt +142 -0
  102. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-12-31_uu_app_aicoding_conv104.txt +66 -0
  103. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-16-03_uu_app_aicoding_conv53.txt +439 -0
  104. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-23-41_uu_entitymanage_conv34.txt +2251 -0
  105. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-25-56_uu_app_aicoding_conv69.txt +169 -0
  106. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-26-54_uu_app_aicoding_conv105.txt +70 -0
  107. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-33-45_uu_entitymanage_conv35.txt +144 -0
  108. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-39-23_uu_app_aicoding_conv37.txt +104 -0
  109. package/cursor-logs-export/chats/2026-02-05_2026-02-10_22-45-30_uu_app_aicoding_conv78.txt +187 -0
  110. package/cursor-logs-export/chats/2026-02-05_2026-02-10_23-04-38_uu_app_aicoding_conv36.txt +2292 -0
  111. package/cursor-logs-export/chats/2026-02-05_2026-02-10_23-08-50_uu_entitymanage_conv38.txt +109 -0
  112. package/cursor-logs-export/chats/2026-02-05_2026-02-10_23-14-01_uu_entitymanage_conv39.txt +112 -0
  113. package/cursor-logs-export/report.html +3071 -0
  114. package/html-template.js +610 -18
  115. package/package.json +18 -7
  116. package/.idea/copilot.data.migration.agent.xml +0 -6
  117. package/.idea/cursor-usage-analyzer.iml +0 -12
  118. package/.idea/modules.xml +0 -8
  119. package/.idea/vcs.xml +0 -11
  120. package/cursor-usage-analyzer-0.1.0.tgz +0 -0
  121. package/cursor-usage-analyzer-0.2.0.tgz +0 -0
  122. package/cursor-usage-analyzer-0.2.1.tgz +0 -0
  123. package/team-usage-events-10287858-2025-12-18.csv +0 -600
@@ -1,13 +1,19 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(npm install)",
5
- "Bash(sqlite3:*)",
6
- "Bash(python3:*)",
4
+ "mcp__plus4u-mcp__document_read",
5
+ "mcp__plus4u-mcp__book_list_structure",
6
+ "Bash(npm install:*)",
7
7
  "Bash(node -e:*)",
8
- "Bash(for file in cursor-logs-export/chats/2025-12-17*.txt)",
9
- "Bash(grep:*)",
10
- "Bash(node analyze.js:*)"
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 = 5 * 60 * 1000) {
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 = conv.messages.length > 0
216
- ? Math.max(...conv.messages.map(m => m.timestamp))
217
- : convStart;
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 === 'auto' || m === 'default' || m === 'unknown') return 'any';
226
- return model;
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 === 'any' ||
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 workspaceId first
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 various sources
330
- const fileSources = [
331
- composerData.newlyCreatedFiles?.[0]?.uri?.path,
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 timestamp = composer.lastUpdatedAt || composer.createdAt || Date.now();
490
+ const createdAt = composer.createdAt || 0;
491
+ const lastUpdatedAt = composer.lastUpdatedAt || createdAt;
492
+ const timestamp = lastUpdatedAt || Date.now();
408
493
 
409
- if (timestamp < startTime || timestamp > endTime) continue;
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 || Date.now()
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
- for (const conv of conversations) {
460
- const availableApiCalls = apiCalls.filter((_, idx) => !usedApiCallIndices.has(idx));
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
- const originalIndex = apiCalls.findIndex(c =>
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
+ ================================================================================