cursor-usage-analyzer 0.2.0 → 0.2.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.
@@ -3,7 +3,11 @@
3
3
  "allow": [
4
4
  "Bash(npm install)",
5
5
  "Bash(sqlite3:*)",
6
- "Bash(python3:*)"
6
+ "Bash(python3:*)",
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:*)"
7
11
  ]
8
12
  }
9
13
  }
package/analyze.js CHANGED
@@ -363,8 +363,33 @@ function extractConversations(startTime, endTime, apiCalls = []) {
363
363
  filesChangedCount: composer.filesChangedCount || 0
364
364
  };
365
365
 
366
- // Match API calls if available
367
- const matchedCalls = matchAPICallsToConversation(conv, apiCalls);
366
+ conversations.push(conv);
367
+ } catch (e) {
368
+ // Silently skip invalid composers
369
+ }
370
+ }
371
+
372
+ // Match API calls to conversations WITHOUT double-counting
373
+ // Each API call should only be matched to ONE conversation
374
+ if (apiCalls.length > 0) {
375
+ const usedApiCallIndices = new Set();
376
+
377
+ for (const conv of conversations) {
378
+ const availableApiCalls = apiCalls.filter((_, idx) => !usedApiCallIndices.has(idx));
379
+ const matchedCalls = matchAPICallsToConversation(conv, availableApiCalls);
380
+
381
+ // Mark these API calls as used
382
+ matchedCalls.forEach(call => {
383
+ const originalIndex = apiCalls.findIndex(c =>
384
+ c.timestamp === call.timestamp &&
385
+ c.model === call.model &&
386
+ c.totalTokens === call.totalTokens
387
+ );
388
+ if (originalIndex !== -1) {
389
+ usedApiCallIndices.add(originalIndex);
390
+ }
391
+ });
392
+
368
393
  conv.apiCalls = matchedCalls;
369
394
  conv.apiCallCount = matchedCalls.length;
370
395
 
@@ -377,11 +402,21 @@ function extractConversations(startTime, endTime, apiCalls = []) {
377
402
  totalTokens: matchedCalls.reduce((sum, c) => sum + c.totalTokens, 0),
378
403
  cost: matchedCalls.reduce((sum, c) => sum + c.cost, 0)
379
404
  };
380
-
381
- conversations.push(conv);
382
- } catch (e) {
383
- // Silently skip invalid composers
384
405
  }
406
+ } else {
407
+ // No API calls to match
408
+ conversations.forEach(conv => {
409
+ conv.apiCalls = [];
410
+ conv.apiCallCount = 0;
411
+ conv.apiTokens = {
412
+ inputWithCache: 0,
413
+ inputWithoutCache: 0,
414
+ cacheRead: 0,
415
+ outputTokens: 0,
416
+ totalTokens: 0,
417
+ cost: 0
418
+ };
419
+ });
385
420
  }
386
421
 
387
422
  db.close();
@@ -560,8 +595,13 @@ async function main() {
560
595
  let apiCalls = [];
561
596
  if (csvPath) {
562
597
  console.log(`CSV file: ${csvPath}`);
563
- apiCalls = parseCSVUsage(csvPath);
564
- console.log(`Parsed ${apiCalls.length} API calls from CSV\n`);
598
+ const allApiCalls = parseCSVUsage(csvPath);
599
+ // Filter API calls to only those within the date range
600
+ apiCalls = allApiCalls.filter(call =>
601
+ call.timestamp >= startOfDay && call.timestamp <= endOfDay
602
+ );
603
+ console.log(`Parsed ${allApiCalls.length} API calls from CSV`);
604
+ console.log(`Filtered to ${apiCalls.length} API calls within date range\n`);
565
605
  } else {
566
606
  console.log('No CSV file provided (use --csv path/to/file.csv to include API token data)\n');
567
607
  }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-usage-analyzer",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Analyze and visualize your Cursor AI editor usage with interactive reports including API token tracking and costs",
5
5
  "main": "analyze.js",
6
6
  "type": "module",