vibestats 1.0.7 → 1.0.9

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 (2) hide show
  1. package/dist/index.js +81 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -365,6 +365,46 @@ function parseCodexJsonl() {
365
365
  }
366
366
  return entries;
367
367
  }
368
+ function parseStatsCacheJson() {
369
+ const cachePath = join(getClaudeDir(), "stats-cache.json");
370
+ if (!existsSync(cachePath)) return [];
371
+ try {
372
+ const cache = JSON.parse(readFileSync(cachePath, "utf-8"));
373
+ const entries = [];
374
+ if (!cache.dailyModelTokens || !cache.modelUsage) return [];
375
+ for (const day of cache.dailyModelTokens) {
376
+ for (const [modelId, totalTokens] of Object.entries(day.tokensByModel)) {
377
+ const usage = cache.modelUsage[modelId];
378
+ if (!usage) continue;
379
+ const allTimeTotal = usage.inputTokens + usage.outputTokens + usage.cacheReadInputTokens + usage.cacheCreationInputTokens;
380
+ if (allTimeTotal === 0) continue;
381
+ const inputRatio = usage.inputTokens / allTimeTotal;
382
+ const outputRatio = usage.outputTokens / allTimeTotal;
383
+ const cacheReadRatio = usage.cacheReadInputTokens / allTimeTotal;
384
+ const cacheWriteRatio = usage.cacheCreationInputTokens / allTimeTotal;
385
+ const inputTokens = Math.round(totalTokens * inputRatio);
386
+ const outputTokens = Math.round(totalTokens * outputRatio);
387
+ const cacheReadTokens = Math.round(totalTokens * cacheReadRatio);
388
+ const cacheWriteTokens = Math.round(totalTokens * cacheWriteRatio);
389
+ const pricing = getModelPricing(modelId);
390
+ const cost = inputTokens * pricing.input / 1e6 + outputTokens * pricing.output / 1e6 + cacheWriteTokens * pricing.cacheWrite / 1e6 + cacheReadTokens * pricing.cacheRead / 1e6;
391
+ entries.push({
392
+ date: day.date,
393
+ model: getModelDisplayName(modelId),
394
+ inputTokens,
395
+ outputTokens,
396
+ cacheWriteTokens,
397
+ cacheReadTokens,
398
+ cost,
399
+ source: "claude"
400
+ });
401
+ }
402
+ }
403
+ return entries;
404
+ } catch {
405
+ return [];
406
+ }
407
+ }
368
408
  function filterByDateRange(entries, since, until) {
369
409
  return entries.filter((e) => {
370
410
  if (since && e.date < since) return false;
@@ -495,7 +535,12 @@ function loadUsageStats(options) {
495
535
  const { aggregation, since, until, codexOnly, combined } = options;
496
536
  let entries = [];
497
537
  if (!codexOnly) {
498
- entries = entries.concat(parseClaudeJsonl());
538
+ const jsonlEntries = parseClaudeJsonl();
539
+ const cacheEntries = parseStatsCacheJson();
540
+ const jsonlDates = new Set(jsonlEntries.map((e) => e.date));
541
+ const supplementalEntries = cacheEntries.filter((e) => !jsonlDates.has(e.date));
542
+ entries = entries.concat(jsonlEntries);
543
+ entries = entries.concat(supplementalEntries);
499
544
  }
500
545
  if (codexOnly || combined) {
501
546
  entries = entries.concat(parseCodexJsonl());
@@ -1447,21 +1492,49 @@ function getModelAbbrevFromDisplayName(displayName) {
1447
1492
  };
1448
1493
  return map[displayName] || displayName.slice(0, 5).toLowerCase();
1449
1494
  }
1495
+ function aggregateRowsToMonthly(rows) {
1496
+ const monthMap = /* @__PURE__ */ new Map();
1497
+ for (const row of rows) {
1498
+ const month = row.key.slice(0, 7);
1499
+ const existing = monthMap.get(month);
1500
+ if (existing) {
1501
+ existing.inputTokens += row.inputTokens;
1502
+ existing.outputTokens += row.outputTokens;
1503
+ existing.cacheWriteTokens += row.cacheWriteTokens;
1504
+ existing.cacheReadTokens += row.cacheReadTokens;
1505
+ existing.totalTokens += row.totalTokens;
1506
+ existing.cost += row.cost;
1507
+ } else {
1508
+ monthMap.set(month, { ...row, key: month });
1509
+ }
1510
+ }
1511
+ return Array.from(monthMap.values()).sort((a, b) => a.key.localeCompare(b.key));
1512
+ }
1450
1513
  function encodeUsageToUrl(stats, baseUrl = "https://vibestats.wolfai.dev") {
1451
1514
  const params = new URLSearchParams();
1452
- const aggMap = { daily: "d", monthly: "m", model: "mo", total: "t" };
1453
- params.set("agg", aggMap[stats.aggregation] || "d");
1454
1515
  if (stats.source !== "claude") {
1455
1516
  params.set("src", stats.source);
1456
1517
  }
1457
1518
  const formatDateCompact = (d) => d.replace(/-/g, "");
1458
- const limitedRows = stats.rows.slice(-31);
1459
- const startDate = limitedRows[0]?.key || stats.dateRange.start;
1460
- const endDate = limitedRows[limitedRows.length - 1]?.key || stats.dateRange.end;
1519
+ const startMs = new Date(stats.dateRange.start).getTime();
1520
+ const endMs = new Date(stats.dateRange.end).getTime();
1521
+ const daySpan = Math.ceil((endMs - startMs) / (1e3 * 60 * 60 * 24));
1522
+ const useMonthly = stats.aggregation === "daily" && daySpan > 31;
1523
+ const aggMap = { daily: "d", monthly: "m", model: "mo", total: "t" };
1524
+ const effectiveAgg = useMonthly ? "monthly" : stats.aggregation;
1525
+ params.set("agg", aggMap[effectiveAgg] || "d");
1526
+ let rowsToEncode;
1527
+ if (useMonthly) {
1528
+ rowsToEncode = aggregateRowsToMonthly(stats.rows);
1529
+ } else {
1530
+ rowsToEncode = stats.rows.slice(-31);
1531
+ }
1532
+ const startDate = useMonthly ? stats.dateRange.start : rowsToEncode[0]?.key || stats.dateRange.start;
1533
+ const endDate = useMonthly ? stats.dateRange.end : rowsToEncode[rowsToEncode.length - 1]?.key || stats.dateRange.end;
1461
1534
  params.set("dr", `${formatDateCompact(startDate)}-${formatDateCompact(endDate)}`);
1462
- const rows = limitedRows.map((row) => {
1535
+ const rows = rowsToEncode.map((row) => {
1463
1536
  let key = row.key;
1464
- if (stats.aggregation === "daily" && row.key.length === 10) {
1537
+ if (effectiveAgg === "daily" && row.key.length === 10) {
1465
1538
  key = row.key.slice(5).replace("-", "");
1466
1539
  }
1467
1540
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibestats",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "AI coding stats - usage tracking and annual wrapped for Claude Code & Codex",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",