vibestats 1.3.4 → 1.3.6

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 +833 -321
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,121 +3,6 @@
3
3
  // src/index.ts
4
4
  import { defineCommand, runMain } from "citty";
5
5
 
6
- // src/usage/loader.ts
7
- import { promises as fs } from "fs";
8
- import { homedir } from "os";
9
- import { join, basename } from "path";
10
-
11
- // src/pricing.ts
12
- var MODEL_PRICING = {
13
- // Opus 4.6 (same pricing as Opus 4.5)
14
- "claude-opus-4-6-20260101": {
15
- input: 5,
16
- output: 25,
17
- cacheWrite: 6.25,
18
- cacheRead: 0.5
19
- },
20
- // Opus 4.5 (cheaper than Opus 4.1)
21
- "claude-opus-4-5-20251101": {
22
- input: 5,
23
- output: 25,
24
- cacheWrite: 6.25,
25
- cacheRead: 0.5
26
- },
27
- // Sonnet 4.6 (same pricing as Sonnet 4.5)
28
- "claude-sonnet-4-6-20260101": {
29
- input: 3,
30
- output: 15,
31
- cacheWrite: 3.75,
32
- cacheRead: 0.3
33
- },
34
- // Sonnet 4.5
35
- "claude-sonnet-4-5-20250929": {
36
- input: 3,
37
- output: 15,
38
- cacheWrite: 3.75,
39
- cacheRead: 0.3
40
- },
41
- // Opus 4.1 (MORE expensive than Opus 4.5)
42
- "claude-opus-4-1-20250805": {
43
- input: 15,
44
- output: 75,
45
- cacheWrite: 18.75,
46
- cacheRead: 1.5
47
- },
48
- // Haiku 4.5
49
- "claude-haiku-4-5-20251001": {
50
- input: 1,
51
- output: 5,
52
- cacheWrite: 1.25,
53
- cacheRead: 0.1
54
- },
55
- // Sonnet 3.5 (legacy - same as Sonnet 4.5)
56
- "claude-3-5-sonnet-20241022": {
57
- input: 3,
58
- output: 15,
59
- cacheWrite: 3.75,
60
- cacheRead: 0.3
61
- },
62
- "claude-3-5-sonnet-20240620": {
63
- input: 3,
64
- output: 15,
65
- cacheWrite: 3.75,
66
- cacheRead: 0.3
67
- },
68
- // Haiku 3.5 (legacy - same as Haiku 4.5)
69
- "claude-3-5-haiku-20241022": {
70
- input: 1,
71
- output: 5,
72
- cacheWrite: 1.25,
73
- cacheRead: 0.1
74
- }
75
- };
76
- function getModelPricing(modelName) {
77
- if (MODEL_PRICING[modelName]) {
78
- return MODEL_PRICING[modelName];
79
- }
80
- if (modelName.includes("opus-4-6") || modelName.includes("opus-4.6")) {
81
- return MODEL_PRICING["claude-opus-4-6-20260101"];
82
- }
83
- if (modelName.includes("opus-4-5") || modelName.includes("opus-4.5")) {
84
- return MODEL_PRICING["claude-opus-4-5-20251101"];
85
- }
86
- if (modelName.includes("opus-4-1") || modelName.includes("opus-4.1") || modelName.includes("opus-4")) {
87
- return MODEL_PRICING["claude-opus-4-1-20250805"];
88
- }
89
- if (modelName.includes("sonnet-4-6") || modelName.includes("sonnet-4.6")) {
90
- return MODEL_PRICING["claude-sonnet-4-6-20260101"];
91
- }
92
- if (modelName.includes("sonnet-4-5") || modelName.includes("sonnet-4.5")) {
93
- return MODEL_PRICING["claude-sonnet-4-5-20250929"];
94
- }
95
- if (modelName.includes("sonnet")) {
96
- return MODEL_PRICING["claude-3-5-sonnet-20241022"];
97
- }
98
- if (modelName.includes("haiku-4-5") || modelName.includes("haiku-4.5")) {
99
- return MODEL_PRICING["claude-haiku-4-5-20251001"];
100
- }
101
- if (modelName.includes("haiku")) {
102
- return MODEL_PRICING["claude-3-5-haiku-20241022"];
103
- }
104
- return MODEL_PRICING["claude-sonnet-4-5-20250929"];
105
- }
106
- function getModelDisplayName(modelName) {
107
- if (modelName.includes("opus-4-6") || modelName.includes("opus-4.6")) return "Opus 4.6";
108
- if (modelName.includes("opus-4-5") || modelName.includes("opus-4.5")) return "Opus 4.5";
109
- if (modelName.includes("opus-4-1") || modelName.includes("opus-4.1")) return "Opus 4.1";
110
- if (modelName.includes("opus")) return "Opus";
111
- if (modelName.includes("sonnet-4-6") || modelName.includes("sonnet-4.6")) return "Sonnet 4.6";
112
- if (modelName.includes("sonnet-4-5") || modelName.includes("sonnet-4.5")) return "Sonnet 4.5";
113
- if (modelName.includes("sonnet-3-5") || modelName.includes("sonnet-3.5")) return "Sonnet 3.5";
114
- if (modelName.includes("sonnet")) return "Sonnet";
115
- if (modelName.includes("haiku-4-5") || modelName.includes("haiku-4.5")) return "Haiku 4.5";
116
- if (modelName.includes("haiku-3-5") || modelName.includes("haiku-3.5")) return "Haiku 3.5";
117
- if (modelName.includes("haiku")) return "Haiku";
118
- return modelName;
119
- }
120
-
121
6
  // src/codex-pricing.ts
122
7
  var GPT_54_PRICING = { input: 2.5, output: 15, cachedInput: 0.25 };
123
8
  var GPT_53_PRICING = { input: 1.75, output: 14, cachedInput: 0.175 };
@@ -395,6 +280,501 @@ function calculateCodexCost(modelName, inputTokens, outputTokens, cachedInputTok
395
280
  return inputCost + cachedCost + outputCost;
396
281
  }
397
282
 
283
+ // src/url-encoder.ts
284
+ function formatCompactNumber(num) {
285
+ if (num >= 1e9) {
286
+ return `${(num / 1e9).toFixed(1)}B`;
287
+ }
288
+ if (num >= 1e6) {
289
+ return `${(num / 1e6).toFixed(1)}M`;
290
+ }
291
+ if (num >= 1e3) {
292
+ return `${(num / 1e3).toFixed(1)}K`;
293
+ }
294
+ return num.toString();
295
+ }
296
+ var dayToNumber = {
297
+ Sunday: 0,
298
+ Monday: 1,
299
+ Tuesday: 2,
300
+ Wednesday: 3,
301
+ Thursday: 4,
302
+ Friday: 5,
303
+ Saturday: 6
304
+ };
305
+ function encodeStatsToUrl(stats, baseUrl = "https://vibestats.wolfai.dev") {
306
+ const params = new URLSearchParams();
307
+ params.set("s", stats.sessions.toString());
308
+ params.set("t", formatCompactNumber(stats.totalTokens));
309
+ params.set("c", stats.totalCost.toFixed(2));
310
+ params.set("d", stats.daysActive.toString());
311
+ params.set("ls", stats.longestStreak.toString());
312
+ params.set("cs", stats.currentStreak.toString());
313
+ params.set("ph", stats.peakHour.toString());
314
+ params.set("pd", (dayToNumber[stats.peakDay] ?? 0).toString());
315
+ params.set("fm", getModelTokenFromDisplayName(stats.favoriteModel));
316
+ const mbParts = stats.modelBreakdown.slice(0, 3).map((m) => {
317
+ const abbr = getModelTokenFromDisplayName(m.model);
318
+ return `${abbr}:${m.percentage}`;
319
+ });
320
+ params.set("mb", mbParts.join(","));
321
+ if (stats.topTools && stats.topTools.length > 0) {
322
+ params.set("tt", stats.topTools.slice(0, 5).join(","));
323
+ }
324
+ if (stats.developerStyle) {
325
+ const styleMap = {
326
+ reader: "r",
327
+ writer: "w",
328
+ executor: "e",
329
+ balanced: "b"
330
+ };
331
+ params.set("st", styleMap[stats.developerStyle] || "b");
332
+ }
333
+ if (stats.topProject) {
334
+ params.set("tp", stats.topProject);
335
+ }
336
+ if (stats.projectCount) {
337
+ params.set("pc", stats.projectCount.toString());
338
+ }
339
+ params.set("wg", formatCompactNumber(stats.wordsGenerated));
340
+ const firstDate = new Date(stats.firstSessionDate);
341
+ params.set("fad", firstDate.toISOString().split("T")[0].replace(/-/g, ""));
342
+ if (stats.source && stats.source !== "claude") {
343
+ params.set("src", stats.source);
344
+ }
345
+ if (stats.activity) {
346
+ params.set("act", encodePayload(stats.activity));
347
+ }
348
+ return `${baseUrl}/wrapped/?${params.toString()}`;
349
+ }
350
+ function encodeActivityToUrl(payload, baseUrl = "https://vibestats.wolfai.dev") {
351
+ const params = new URLSearchParams();
352
+ params.set("activity", encodePayload(payload));
353
+ if (payload.source !== "claude") {
354
+ params.set("src", payload.source);
355
+ }
356
+ return `${baseUrl}/activity?${params.toString()}`;
357
+ }
358
+ var CLAUDE_DISPLAY_TO_TOKEN = {
359
+ "Opus 4.6": "o46",
360
+ "Opus 4.5": "o45",
361
+ "Opus 4.1": "o41",
362
+ Opus: "opus",
363
+ "Sonnet 4.6": "s46",
364
+ "Sonnet 4.5": "s45",
365
+ "Sonnet 3.5": "s35",
366
+ Sonnet: "sonnet",
367
+ "Haiku 4.5": "h45",
368
+ "Haiku 3.5": "h35",
369
+ Haiku: "haiku"
370
+ };
371
+ function getModelTokenFromDisplayName(displayName) {
372
+ if (CLAUDE_DISPLAY_TO_TOKEN[displayName]) {
373
+ return CLAUDE_DISPLAY_TO_TOKEN[displayName];
374
+ }
375
+ const normalized = displayName.toLowerCase();
376
+ if (normalized.includes("opus") && normalized.includes("4.6")) return "o46";
377
+ if (normalized.includes("opus") && normalized.includes("4.5")) return "o45";
378
+ if (normalized.includes("opus") && normalized.includes("4.1")) return "o41";
379
+ if (normalized.includes("opus")) return "opus";
380
+ if (normalized.includes("sonnet") && normalized.includes("4.6")) return "s46";
381
+ if (normalized.includes("sonnet") && normalized.includes("4.5")) return "s45";
382
+ if (normalized.includes("sonnet") && normalized.includes("3.5")) return "s35";
383
+ if (normalized.includes("sonnet")) return "sonnet";
384
+ if (normalized.includes("haiku") && normalized.includes("4.5")) return "h45";
385
+ if (normalized.includes("haiku") && normalized.includes("3.5")) return "h35";
386
+ if (normalized.includes("haiku")) return "haiku";
387
+ return getCodexModelAbbreviation(displayName);
388
+ }
389
+ function aggregateRowsToMonthly(rows) {
390
+ const monthMap = /* @__PURE__ */ new Map();
391
+ for (const row of rows) {
392
+ const month = row.key.slice(0, 7);
393
+ const existing = monthMap.get(month);
394
+ if (existing) {
395
+ existing.inputTokens += row.inputTokens;
396
+ existing.outputTokens += row.outputTokens;
397
+ existing.cacheWriteTokens += row.cacheWriteTokens;
398
+ existing.cacheReadTokens += row.cacheReadTokens;
399
+ existing.totalTokens += row.totalTokens;
400
+ existing.cost += row.cost;
401
+ } else {
402
+ monthMap.set(month, { ...row, key: month });
403
+ }
404
+ }
405
+ return Array.from(monthMap.values()).sort((a, b) => a.key.localeCompare(b.key));
406
+ }
407
+ function encodeUsageToUrl(stats, baseUrl = "https://vibestats.wolfai.dev") {
408
+ const params = new URLSearchParams();
409
+ if (stats.source !== "claude") {
410
+ params.set("src", stats.source);
411
+ }
412
+ const formatDateCompact = (d) => d.replace(/-/g, "");
413
+ const startMs = new Date(stats.dateRange.start).getTime();
414
+ const endMs = new Date(stats.dateRange.end).getTime();
415
+ const daySpan = Math.ceil((endMs - startMs) / (1e3 * 60 * 60 * 24));
416
+ const useMonthly = stats.aggregation === "daily" && daySpan > 31;
417
+ const aggMap = { daily: "d", monthly: "m", model: "mo", total: "t" };
418
+ const effectiveAgg = useMonthly ? "monthly" : stats.aggregation;
419
+ params.set("agg", aggMap[effectiveAgg] || "d");
420
+ let rowsToEncode;
421
+ if (useMonthly) {
422
+ rowsToEncode = aggregateRowsToMonthly(stats.rows);
423
+ } else {
424
+ rowsToEncode = stats.rows.slice(-31);
425
+ }
426
+ const startDate = useMonthly ? stats.dateRange.start : rowsToEncode[0]?.key || stats.dateRange.start;
427
+ const endDate = useMonthly ? stats.dateRange.end : rowsToEncode[rowsToEncode.length - 1]?.key || stats.dateRange.end;
428
+ params.set("dr", `${formatDateCompact(startDate)}-${formatDateCompact(endDate)}`);
429
+ const rows = rowsToEncode.map((row) => {
430
+ let key = row.key;
431
+ if (effectiveAgg === "daily" && row.key.length === 10) {
432
+ key = row.key.slice(5).replace("-", "");
433
+ }
434
+ return [
435
+ key,
436
+ formatCompactNumber(row.inputTokens),
437
+ formatCompactNumber(row.outputTokens),
438
+ formatCompactNumber(row.cacheWriteTokens),
439
+ formatCompactNumber(row.cacheReadTokens),
440
+ formatCompactNumber(row.totalTokens),
441
+ row.cost.toFixed(2)
442
+ ].join(":");
443
+ });
444
+ params.set("rows", rows.join("|"));
445
+ const t = stats.totals;
446
+ params.set("tot", [
447
+ formatCompactNumber(t.inputTokens),
448
+ formatCompactNumber(t.outputTokens),
449
+ formatCompactNumber(t.cacheWriteTokens),
450
+ formatCompactNumber(t.cacheReadTokens),
451
+ formatCompactNumber(t.totalTokens),
452
+ t.cost.toFixed(2)
453
+ ].join(":"));
454
+ if (stats.modelBreakdown.length > 0) {
455
+ const mb = stats.modelBreakdown.slice(0, 5).map((m) => {
456
+ const abbr = getModelTokenFromDisplayName(m.model);
457
+ return `${abbr}:${m.percentage}:${m.cost.toFixed(2)}`;
458
+ });
459
+ params.set("mb", mb.join(","));
460
+ }
461
+ return `${baseUrl}?${params.toString()}`;
462
+ }
463
+ function encodePayload(payload) {
464
+ return Buffer.from(JSON.stringify(payload), "utf-8").toString("base64url");
465
+ }
466
+
467
+ // src/activity.ts
468
+ var WEEKDAY_LABELS = ["Mon", "", "", "", "", "", "Sun"];
469
+ var MS_PER_DAY = 24 * 60 * 60 * 1e3;
470
+ function toDateKey(date) {
471
+ const year = date.getFullYear();
472
+ const month = String(date.getMonth() + 1).padStart(2, "0");
473
+ const day = String(date.getDate()).padStart(2, "0");
474
+ return `${year}-${month}-${day}`;
475
+ }
476
+ function startOfDay(date) {
477
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0, 0);
478
+ }
479
+ function addDays(date, days) {
480
+ return new Date(date.getTime() + days * MS_PER_DAY);
481
+ }
482
+ function startOfWeekMonday(date) {
483
+ const current = startOfDay(date);
484
+ const day = current.getDay();
485
+ const delta = day === 0 ? -6 : 1 - day;
486
+ return addDays(current, delta);
487
+ }
488
+ function endOfWeekSunday(date) {
489
+ const monday = startOfWeekMonday(date);
490
+ return addDays(monday, 6);
491
+ }
492
+ function quantile(sortedValues, ratio) {
493
+ if (sortedValues.length === 0) return 0;
494
+ const index = Math.min(
495
+ sortedValues.length - 1,
496
+ Math.max(0, Math.floor((sortedValues.length - 1) * ratio))
497
+ );
498
+ return sortedValues[index] ?? 0;
499
+ }
500
+ function getMetricValue(day, metric) {
501
+ switch (metric) {
502
+ case "messages":
503
+ return day.messageCount;
504
+ case "sessions":
505
+ return day.sessionCount;
506
+ case "tokens":
507
+ default:
508
+ return day.totalTokens;
509
+ }
510
+ }
511
+ function buildLegend(thresholds) {
512
+ return [
513
+ { intensity: 0, min: 0, max: 0, label: "No activity" },
514
+ { intensity: 1, min: 1, max: thresholds[0], label: "Low" },
515
+ { intensity: 2, min: thresholds[0] + 1, max: thresholds[1], label: "Steady" },
516
+ { intensity: 3, min: thresholds[1] + 1, max: thresholds[2], label: "Strong" },
517
+ { intensity: 4, min: thresholds[2] + 1, max: Number.MAX_SAFE_INTEGER, label: "Peak" }
518
+ ];
519
+ }
520
+ function getIntensity(value, thresholds) {
521
+ if (value <= 0) return 0;
522
+ if (value <= thresholds[0]) return 1;
523
+ if (value <= thresholds[1]) return 2;
524
+ if (value <= thresholds[2]) return 3;
525
+ return 4;
526
+ }
527
+ function computeStreaks(days) {
528
+ let current = 0;
529
+ let longest = 0;
530
+ let streak = 0;
531
+ let activeDays = 0;
532
+ for (const day of days) {
533
+ if (day.value > 0) {
534
+ streak += 1;
535
+ activeDays += 1;
536
+ longest = Math.max(longest, streak);
537
+ } else {
538
+ streak = 0;
539
+ }
540
+ }
541
+ for (let index = days.length - 1; index >= 0; index -= 1) {
542
+ if ((days[index]?.value ?? 0) > 0) {
543
+ current += 1;
544
+ } else if (current > 0) {
545
+ break;
546
+ }
547
+ }
548
+ return { current, longest, activeDays };
549
+ }
550
+ function buildActivityGraph(stats, metric, requestedDays = 365) {
551
+ const today = startOfDay(/* @__PURE__ */ new Date());
552
+ const endDate = stats.dateRange.end ? startOfDay(new Date(stats.dateRange.end)) : today;
553
+ const effectiveEnd = endDate > today ? today : endDate;
554
+ const effectiveStart = addDays(effectiveEnd, -(requestedDays - 1));
555
+ const paddedStart = startOfWeekMonday(effectiveStart);
556
+ const paddedEnd = endOfWeekSunday(effectiveEnd);
557
+ const dayMap = new Map(stats.days.map((day) => [day.date, day]));
558
+ const orderedDays = [];
559
+ const positiveValues = [];
560
+ for (let cursor = new Date(paddedStart); cursor <= paddedEnd; cursor = addDays(cursor, 1)) {
561
+ const key = toDateKey(cursor);
562
+ const sourceDay = dayMap.get(key);
563
+ const value = sourceDay ? getMetricValue(sourceDay, metric) : 0;
564
+ if (cursor >= effectiveStart && cursor <= effectiveEnd && value > 0) {
565
+ positiveValues.push(value);
566
+ }
567
+ orderedDays.push({
568
+ date: key,
569
+ value,
570
+ intensity: 0,
571
+ inputTokens: sourceDay?.inputTokens ?? 0,
572
+ outputTokens: sourceDay?.outputTokens ?? 0,
573
+ cacheWriteTokens: sourceDay?.cacheWriteTokens ?? 0,
574
+ cacheReadTokens: sourceDay?.cacheReadTokens ?? 0,
575
+ totalTokens: sourceDay?.totalTokens ?? 0,
576
+ sessionCount: sourceDay?.sessionCount ?? 0,
577
+ messageCount: sourceDay?.messageCount ?? 0
578
+ });
579
+ }
580
+ positiveValues.sort((a, b) => a - b);
581
+ const thresholds = [
582
+ Math.max(1, quantile(positiveValues, 0.25)),
583
+ Math.max(1, quantile(positiveValues, 0.5)),
584
+ Math.max(1, quantile(positiveValues, 0.75))
585
+ ];
586
+ for (const day of orderedDays) {
587
+ day.intensity = getIntensity(day.value, thresholds);
588
+ }
589
+ const weeks = [];
590
+ for (let index = 0; index < orderedDays.length; index += 7) {
591
+ weeks.push(orderedDays.slice(index, index + 7));
592
+ }
593
+ const monthLabels = [];
594
+ const seenMonths = /* @__PURE__ */ new Set();
595
+ for (let weekIndex = 0; weekIndex < weeks.length; weekIndex += 1) {
596
+ const week = weeks[weekIndex];
597
+ const firstRealDay = week?.find((day) => day.date >= toDateKey(effectiveStart) && day.date <= toDateKey(effectiveEnd));
598
+ if (!firstRealDay) continue;
599
+ const monthKey = firstRealDay.date.slice(0, 7);
600
+ if (seenMonths.has(monthKey)) continue;
601
+ seenMonths.add(monthKey);
602
+ const monthDate = /* @__PURE__ */ new Date(`${monthKey}-01T12:00:00`);
603
+ monthLabels.push({
604
+ weekIndex,
605
+ label: monthDate.toLocaleString("en-US", { month: "short" })
606
+ });
607
+ }
608
+ const visibleDays = orderedDays.filter((day) => day.date >= toDateKey(effectiveStart) && day.date <= toDateKey(effectiveEnd));
609
+ const streaks = computeStreaks(visibleDays);
610
+ const recent30Total = visibleDays.slice(-30).reduce((sum, day) => sum + day.value, 0);
611
+ const favoriteModel = stats.modelBreakdown[0];
612
+ return {
613
+ metric,
614
+ startDate: toDateKey(effectiveStart),
615
+ endDate: toDateKey(effectiveEnd),
616
+ weeks,
617
+ monthLabels,
618
+ weekdayLabels: WEEKDAY_LABELS,
619
+ legend: buildLegend(thresholds),
620
+ summary: {
621
+ activeDays: streaks.activeDays,
622
+ currentStreak: streaks.current,
623
+ longestStreak: streaks.longest,
624
+ recent30DayTotal: recent30Total,
625
+ favoriteModel: favoriteModel?.model || "Unknown",
626
+ favoriteModelTokens: favoriteModel?.tokens || 0,
627
+ inputTokens: stats.totals.inputTokens,
628
+ outputTokens: stats.totals.outputTokens,
629
+ cacheWriteTokens: stats.totals.cacheWriteTokens,
630
+ cacheReadTokens: stats.totals.cacheReadTokens,
631
+ totalTokens: stats.totals.totalTokens,
632
+ totalMessages: stats.totals.messageCount,
633
+ totalSessions: stats.totals.sessionCount
634
+ }
635
+ };
636
+ }
637
+ function buildActivityTitle(source, metric) {
638
+ const sourceLabel = source === "codex" ? "Codex" : source === "combined" ? "AI Coding" : "Claude";
639
+ const metricLabel = metric === "tokens" ? "Activity" : metric === "sessions" ? "Session Activity" : "Message Activity";
640
+ return `${sourceLabel} ${metricLabel}`;
641
+ }
642
+ function buildActivityArtifactPayload(stats, metric, requestedDays = 365) {
643
+ return {
644
+ title: buildActivityTitle(stats.source, metric),
645
+ source: stats.source,
646
+ activity: buildActivityGraph(stats, metric, requestedDays)
647
+ };
648
+ }
649
+ function buildActivityArtifact(stats, metric, requestedDays = 365) {
650
+ return {
651
+ type: "activity",
652
+ schemaVersion: 1,
653
+ source: stats.source,
654
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
655
+ renderOptions: {
656
+ canonicalPath: "activity",
657
+ theme: "light"
658
+ },
659
+ payload: buildActivityArtifactPayload(stats, metric, requestedDays)
660
+ };
661
+ }
662
+
663
+ // src/usage/loader.ts
664
+ import { promises as fs } from "fs";
665
+ import { homedir } from "os";
666
+ import { join, basename } from "path";
667
+
668
+ // src/pricing.ts
669
+ var MODEL_PRICING = {
670
+ // Opus 4.6 (same pricing as Opus 4.5)
671
+ "claude-opus-4-6-20260101": {
672
+ input: 5,
673
+ output: 25,
674
+ cacheWrite: 6.25,
675
+ cacheRead: 0.5
676
+ },
677
+ // Opus 4.5 (cheaper than Opus 4.1)
678
+ "claude-opus-4-5-20251101": {
679
+ input: 5,
680
+ output: 25,
681
+ cacheWrite: 6.25,
682
+ cacheRead: 0.5
683
+ },
684
+ // Sonnet 4.6 (same pricing as Sonnet 4.5)
685
+ "claude-sonnet-4-6-20260101": {
686
+ input: 3,
687
+ output: 15,
688
+ cacheWrite: 3.75,
689
+ cacheRead: 0.3
690
+ },
691
+ // Sonnet 4.5
692
+ "claude-sonnet-4-5-20250929": {
693
+ input: 3,
694
+ output: 15,
695
+ cacheWrite: 3.75,
696
+ cacheRead: 0.3
697
+ },
698
+ // Opus 4.1 (MORE expensive than Opus 4.5)
699
+ "claude-opus-4-1-20250805": {
700
+ input: 15,
701
+ output: 75,
702
+ cacheWrite: 18.75,
703
+ cacheRead: 1.5
704
+ },
705
+ // Haiku 4.5
706
+ "claude-haiku-4-5-20251001": {
707
+ input: 1,
708
+ output: 5,
709
+ cacheWrite: 1.25,
710
+ cacheRead: 0.1
711
+ },
712
+ // Sonnet 3.5 (legacy - same as Sonnet 4.5)
713
+ "claude-3-5-sonnet-20241022": {
714
+ input: 3,
715
+ output: 15,
716
+ cacheWrite: 3.75,
717
+ cacheRead: 0.3
718
+ },
719
+ "claude-3-5-sonnet-20240620": {
720
+ input: 3,
721
+ output: 15,
722
+ cacheWrite: 3.75,
723
+ cacheRead: 0.3
724
+ },
725
+ // Haiku 3.5 (legacy - same as Haiku 4.5)
726
+ "claude-3-5-haiku-20241022": {
727
+ input: 1,
728
+ output: 5,
729
+ cacheWrite: 1.25,
730
+ cacheRead: 0.1
731
+ }
732
+ };
733
+ function getModelPricing(modelName) {
734
+ if (MODEL_PRICING[modelName]) {
735
+ return MODEL_PRICING[modelName];
736
+ }
737
+ if (modelName.includes("opus-4-6") || modelName.includes("opus-4.6")) {
738
+ return MODEL_PRICING["claude-opus-4-6-20260101"];
739
+ }
740
+ if (modelName.includes("opus-4-5") || modelName.includes("opus-4.5")) {
741
+ return MODEL_PRICING["claude-opus-4-5-20251101"];
742
+ }
743
+ if (modelName.includes("opus-4-1") || modelName.includes("opus-4.1") || modelName.includes("opus-4")) {
744
+ return MODEL_PRICING["claude-opus-4-1-20250805"];
745
+ }
746
+ if (modelName.includes("sonnet-4-6") || modelName.includes("sonnet-4.6")) {
747
+ return MODEL_PRICING["claude-sonnet-4-6-20260101"];
748
+ }
749
+ if (modelName.includes("sonnet-4-5") || modelName.includes("sonnet-4.5")) {
750
+ return MODEL_PRICING["claude-sonnet-4-5-20250929"];
751
+ }
752
+ if (modelName.includes("sonnet")) {
753
+ return MODEL_PRICING["claude-3-5-sonnet-20241022"];
754
+ }
755
+ if (modelName.includes("haiku-4-5") || modelName.includes("haiku-4.5")) {
756
+ return MODEL_PRICING["claude-haiku-4-5-20251001"];
757
+ }
758
+ if (modelName.includes("haiku")) {
759
+ return MODEL_PRICING["claude-3-5-haiku-20241022"];
760
+ }
761
+ return MODEL_PRICING["claude-sonnet-4-5-20250929"];
762
+ }
763
+ function getModelDisplayName(modelName) {
764
+ if (modelName.includes("opus-4-6") || modelName.includes("opus-4.6")) return "Opus 4.6";
765
+ if (modelName.includes("opus-4-5") || modelName.includes("opus-4.5")) return "Opus 4.5";
766
+ if (modelName.includes("opus-4-1") || modelName.includes("opus-4.1")) return "Opus 4.1";
767
+ if (modelName.includes("opus")) return "Opus";
768
+ if (modelName.includes("sonnet-4-6") || modelName.includes("sonnet-4.6")) return "Sonnet 4.6";
769
+ if (modelName.includes("sonnet-4-5") || modelName.includes("sonnet-4.5")) return "Sonnet 4.5";
770
+ if (modelName.includes("sonnet-3-5") || modelName.includes("sonnet-3.5")) return "Sonnet 3.5";
771
+ if (modelName.includes("sonnet")) return "Sonnet";
772
+ if (modelName.includes("haiku-4-5") || modelName.includes("haiku-4.5")) return "Haiku 4.5";
773
+ if (modelName.includes("haiku-3-5") || modelName.includes("haiku-3.5")) return "Haiku 3.5";
774
+ if (modelName.includes("haiku")) return "Haiku";
775
+ return modelName;
776
+ }
777
+
398
778
  // src/usage/loader.ts
399
779
  var MAX_RECURSION_DEPTH = 10;
400
780
  function toLocalDateString(isoTimestamp) {
@@ -515,6 +895,7 @@ async function parseClaudeJsonl(projectFilter) {
515
895
  cacheWriteTokens,
516
896
  cacheReadTokens,
517
897
  cost,
898
+ messageCount: 1,
518
899
  source: "claude",
519
900
  sessionId,
520
901
  timestamp
@@ -571,7 +952,10 @@ async function parseCodexJsonl() {
571
952
  cacheWriteTokens: 0,
572
953
  cacheReadTokens: cachedInputTokens,
573
954
  cost,
574
- source: "codex"
955
+ messageCount: 1,
956
+ source: "codex",
957
+ sessionId: basename(filePath, ".jsonl"),
958
+ timestamp
575
959
  });
576
960
  }
577
961
  } catch {
@@ -772,6 +1156,76 @@ function computeModelBreakdown(entries) {
772
1156
  percentage: totalTokens > 0 ? Math.round(data.tokens / totalTokens * 100) : 0
773
1157
  })).sort((a, b) => b.tokens - a.tokens);
774
1158
  }
1159
+ function computeActivityStats(entries, source) {
1160
+ const dayMap = /* @__PURE__ */ new Map();
1161
+ for (const entry of entries) {
1162
+ const existing = dayMap.get(entry.date);
1163
+ if (existing) {
1164
+ existing.inputTokens += entry.inputTokens;
1165
+ existing.outputTokens += entry.outputTokens;
1166
+ existing.cacheWriteTokens += entry.cacheWriteTokens;
1167
+ existing.cacheReadTokens += entry.cacheReadTokens;
1168
+ existing.totalTokens += entry.inputTokens + entry.outputTokens + entry.cacheWriteTokens + entry.cacheReadTokens;
1169
+ existing.messageCount += entry.messageCount;
1170
+ if (entry.sessionId) {
1171
+ existing.sessionCount += 0;
1172
+ }
1173
+ } else {
1174
+ dayMap.set(entry.date, {
1175
+ date: entry.date,
1176
+ inputTokens: entry.inputTokens,
1177
+ outputTokens: entry.outputTokens,
1178
+ cacheWriteTokens: entry.cacheWriteTokens,
1179
+ cacheReadTokens: entry.cacheReadTokens,
1180
+ totalTokens: entry.inputTokens + entry.outputTokens + entry.cacheWriteTokens + entry.cacheReadTokens,
1181
+ sessionCount: 0,
1182
+ messageCount: entry.messageCount
1183
+ });
1184
+ }
1185
+ }
1186
+ const sessionMap = /* @__PURE__ */ new Map();
1187
+ for (const entry of entries) {
1188
+ if (!entry.sessionId) continue;
1189
+ const sessions = sessionMap.get(entry.date) || /* @__PURE__ */ new Set();
1190
+ sessions.add(entry.sessionId);
1191
+ sessionMap.set(entry.date, sessions);
1192
+ }
1193
+ const days = Array.from(dayMap.values()).map((day) => ({
1194
+ ...day,
1195
+ sessionCount: sessionMap.get(day.date)?.size || 0
1196
+ })).sort((a, b) => a.date.localeCompare(b.date));
1197
+ const totals = days.reduce(
1198
+ (acc, day) => ({
1199
+ inputTokens: acc.inputTokens + day.inputTokens,
1200
+ outputTokens: acc.outputTokens + day.outputTokens,
1201
+ cacheWriteTokens: acc.cacheWriteTokens + day.cacheWriteTokens,
1202
+ cacheReadTokens: acc.cacheReadTokens + day.cacheReadTokens,
1203
+ totalTokens: acc.totalTokens + day.totalTokens,
1204
+ sessionCount: acc.sessionCount + day.sessionCount,
1205
+ messageCount: acc.messageCount + day.messageCount
1206
+ }),
1207
+ {
1208
+ inputTokens: 0,
1209
+ outputTokens: 0,
1210
+ cacheWriteTokens: 0,
1211
+ cacheReadTokens: 0,
1212
+ totalTokens: 0,
1213
+ sessionCount: 0,
1214
+ messageCount: 0
1215
+ }
1216
+ );
1217
+ const dates = days.map((entry) => entry.date);
1218
+ return {
1219
+ source,
1220
+ dateRange: {
1221
+ start: dates[0] || "",
1222
+ end: dates[dates.length - 1] || ""
1223
+ },
1224
+ days,
1225
+ totals,
1226
+ modelBreakdown: computeModelBreakdown(entries)
1227
+ };
1228
+ }
775
1229
  async function loadUsageStats(options) {
776
1230
  const { aggregation, since, until, codexOnly, combined, projectFilter } = options;
777
1231
  let entries = [];
@@ -813,19 +1267,41 @@ async function loadUsageStats(options) {
813
1267
  rows = aggregateByDay(entries);
814
1268
  break;
815
1269
  }
816
- const totals = aggregation === "total" ? computeTotals(aggregateByDay(entries)) : computeTotals(rows);
817
- const modelBreakdown = computeModelBreakdown(entries);
1270
+ const totals = aggregation === "total" ? computeTotals(aggregateByDay(entries)) : computeTotals(rows);
1271
+ const modelBreakdown = computeModelBreakdown(entries);
1272
+ let source = "claude";
1273
+ if (codexOnly) source = "codex";
1274
+ else if (combined) source = "combined";
1275
+ return {
1276
+ rows,
1277
+ totals,
1278
+ source,
1279
+ aggregation,
1280
+ dateRange,
1281
+ modelBreakdown
1282
+ };
1283
+ }
1284
+ async function loadActivityStats(options) {
1285
+ const { since, until, codexOnly, combined, projectFilter } = options;
1286
+ let entries = [];
1287
+ if (!codexOnly) {
1288
+ entries = entries.concat(await parseClaudeJsonl(projectFilter));
1289
+ }
1290
+ if (codexOnly || combined) {
1291
+ entries = entries.concat(await parseCodexJsonl());
1292
+ }
1293
+ if (entries.length === 0) {
1294
+ return null;
1295
+ }
1296
+ entries = filterByDateRange(entries, since, until);
1297
+ entries = entries.filter((entry) => !entry.model.toLowerCase().includes("synthetic"));
1298
+ if (entries.length === 0) {
1299
+ return null;
1300
+ }
818
1301
  let source = "claude";
819
1302
  if (codexOnly) source = "codex";
820
1303
  else if (combined) source = "combined";
821
- return {
822
- rows,
823
- totals,
824
- source,
825
- aggregation,
826
- dateRange,
827
- modelBreakdown
828
- };
1304
+ return computeActivityStats(entries, source);
829
1305
  }
830
1306
 
831
1307
  // src/usage/table.ts
@@ -1698,176 +2174,6 @@ function combineWrappedStats(claude, codex) {
1698
2174
  };
1699
2175
  }
1700
2176
 
1701
- // src/url-encoder.ts
1702
- function formatCompactNumber(num) {
1703
- if (num >= 1e9) {
1704
- return `${(num / 1e9).toFixed(1)}B`;
1705
- }
1706
- if (num >= 1e6) {
1707
- return `${(num / 1e6).toFixed(1)}M`;
1708
- }
1709
- if (num >= 1e3) {
1710
- return `${(num / 1e3).toFixed(1)}K`;
1711
- }
1712
- return num.toString();
1713
- }
1714
- var dayToNumber = {
1715
- Sunday: 0,
1716
- Monday: 1,
1717
- Tuesday: 2,
1718
- Wednesday: 3,
1719
- Thursday: 4,
1720
- Friday: 5,
1721
- Saturday: 6
1722
- };
1723
- function encodeStatsToUrl(stats, baseUrl = "https://vibestats.wolfai.dev") {
1724
- const params = new URLSearchParams();
1725
- params.set("s", stats.sessions.toString());
1726
- params.set("t", formatCompactNumber(stats.totalTokens));
1727
- params.set("c", stats.totalCost.toFixed(2));
1728
- params.set("d", stats.daysActive.toString());
1729
- params.set("ls", stats.longestStreak.toString());
1730
- params.set("cs", stats.currentStreak.toString());
1731
- params.set("ph", stats.peakHour.toString());
1732
- params.set("pd", (dayToNumber[stats.peakDay] ?? 0).toString());
1733
- params.set("fm", getModelTokenFromDisplayName(stats.favoriteModel));
1734
- const mbParts = stats.modelBreakdown.slice(0, 3).map((m) => {
1735
- const abbr = getModelTokenFromDisplayName(m.model);
1736
- return `${abbr}:${m.percentage}`;
1737
- });
1738
- params.set("mb", mbParts.join(","));
1739
- if (stats.topTools && stats.topTools.length > 0) {
1740
- params.set("tt", stats.topTools.slice(0, 5).join(","));
1741
- }
1742
- if (stats.developerStyle) {
1743
- const styleMap = {
1744
- reader: "r",
1745
- writer: "w",
1746
- executor: "e",
1747
- balanced: "b"
1748
- };
1749
- params.set("st", styleMap[stats.developerStyle] || "b");
1750
- }
1751
- if (stats.topProject) {
1752
- params.set("tp", stats.topProject);
1753
- }
1754
- if (stats.projectCount) {
1755
- params.set("pc", stats.projectCount.toString());
1756
- }
1757
- params.set("wg", formatCompactNumber(stats.wordsGenerated));
1758
- const firstDate = new Date(stats.firstSessionDate);
1759
- params.set("fad", firstDate.toISOString().split("T")[0].replace(/-/g, ""));
1760
- if (stats.source && stats.source !== "claude") {
1761
- params.set("src", stats.source);
1762
- }
1763
- return `${baseUrl}/wrapped/?${params.toString()}`;
1764
- }
1765
- var CLAUDE_DISPLAY_TO_TOKEN = {
1766
- "Opus 4.6": "o46",
1767
- "Opus 4.5": "o45",
1768
- "Opus 4.1": "o41",
1769
- Opus: "opus",
1770
- "Sonnet 4.6": "s46",
1771
- "Sonnet 4.5": "s45",
1772
- "Sonnet 3.5": "s35",
1773
- Sonnet: "sonnet",
1774
- "Haiku 4.5": "h45",
1775
- "Haiku 3.5": "h35",
1776
- Haiku: "haiku"
1777
- };
1778
- function getModelTokenFromDisplayName(displayName) {
1779
- if (CLAUDE_DISPLAY_TO_TOKEN[displayName]) {
1780
- return CLAUDE_DISPLAY_TO_TOKEN[displayName];
1781
- }
1782
- const normalized = displayName.toLowerCase();
1783
- if (normalized.includes("opus") && normalized.includes("4.6")) return "o46";
1784
- if (normalized.includes("opus") && normalized.includes("4.5")) return "o45";
1785
- if (normalized.includes("opus") && normalized.includes("4.1")) return "o41";
1786
- if (normalized.includes("opus")) return "opus";
1787
- if (normalized.includes("sonnet") && normalized.includes("4.6")) return "s46";
1788
- if (normalized.includes("sonnet") && normalized.includes("4.5")) return "s45";
1789
- if (normalized.includes("sonnet") && normalized.includes("3.5")) return "s35";
1790
- if (normalized.includes("sonnet")) return "sonnet";
1791
- if (normalized.includes("haiku") && normalized.includes("4.5")) return "h45";
1792
- if (normalized.includes("haiku") && normalized.includes("3.5")) return "h35";
1793
- if (normalized.includes("haiku")) return "haiku";
1794
- return getCodexModelAbbreviation(displayName);
1795
- }
1796
- function aggregateRowsToMonthly(rows) {
1797
- const monthMap = /* @__PURE__ */ new Map();
1798
- for (const row of rows) {
1799
- const month = row.key.slice(0, 7);
1800
- const existing = monthMap.get(month);
1801
- if (existing) {
1802
- existing.inputTokens += row.inputTokens;
1803
- existing.outputTokens += row.outputTokens;
1804
- existing.cacheWriteTokens += row.cacheWriteTokens;
1805
- existing.cacheReadTokens += row.cacheReadTokens;
1806
- existing.totalTokens += row.totalTokens;
1807
- existing.cost += row.cost;
1808
- } else {
1809
- monthMap.set(month, { ...row, key: month });
1810
- }
1811
- }
1812
- return Array.from(monthMap.values()).sort((a, b) => a.key.localeCompare(b.key));
1813
- }
1814
- function encodeUsageToUrl(stats, baseUrl = "https://vibestats.wolfai.dev") {
1815
- const params = new URLSearchParams();
1816
- if (stats.source !== "claude") {
1817
- params.set("src", stats.source);
1818
- }
1819
- const formatDateCompact = (d) => d.replace(/-/g, "");
1820
- const startMs = new Date(stats.dateRange.start).getTime();
1821
- const endMs = new Date(stats.dateRange.end).getTime();
1822
- const daySpan = Math.ceil((endMs - startMs) / (1e3 * 60 * 60 * 24));
1823
- const useMonthly = stats.aggregation === "daily" && daySpan > 31;
1824
- const aggMap = { daily: "d", monthly: "m", model: "mo", total: "t" };
1825
- const effectiveAgg = useMonthly ? "monthly" : stats.aggregation;
1826
- params.set("agg", aggMap[effectiveAgg] || "d");
1827
- let rowsToEncode;
1828
- if (useMonthly) {
1829
- rowsToEncode = aggregateRowsToMonthly(stats.rows);
1830
- } else {
1831
- rowsToEncode = stats.rows.slice(-31);
1832
- }
1833
- const startDate = useMonthly ? stats.dateRange.start : rowsToEncode[0]?.key || stats.dateRange.start;
1834
- const endDate = useMonthly ? stats.dateRange.end : rowsToEncode[rowsToEncode.length - 1]?.key || stats.dateRange.end;
1835
- params.set("dr", `${formatDateCompact(startDate)}-${formatDateCompact(endDate)}`);
1836
- const rows = rowsToEncode.map((row) => {
1837
- let key = row.key;
1838
- if (effectiveAgg === "daily" && row.key.length === 10) {
1839
- key = row.key.slice(5).replace("-", "");
1840
- }
1841
- return [
1842
- key,
1843
- formatCompactNumber(row.inputTokens),
1844
- formatCompactNumber(row.outputTokens),
1845
- formatCompactNumber(row.cacheWriteTokens),
1846
- formatCompactNumber(row.cacheReadTokens),
1847
- formatCompactNumber(row.totalTokens),
1848
- row.cost.toFixed(2)
1849
- ].join(":");
1850
- });
1851
- params.set("rows", rows.join("|"));
1852
- const t = stats.totals;
1853
- params.set("tot", [
1854
- formatCompactNumber(t.inputTokens),
1855
- formatCompactNumber(t.outputTokens),
1856
- formatCompactNumber(t.cacheWriteTokens),
1857
- formatCompactNumber(t.cacheReadTokens),
1858
- formatCompactNumber(t.totalTokens),
1859
- t.cost.toFixed(2)
1860
- ].join(":"));
1861
- if (stats.modelBreakdown.length > 0) {
1862
- const mb = stats.modelBreakdown.slice(0, 5).map((m) => {
1863
- const abbr = getModelTokenFromDisplayName(m.model);
1864
- return `${abbr}:${m.percentage}:${m.cost.toFixed(2)}`;
1865
- });
1866
- params.set("mb", mb.join(","));
1867
- }
1868
- return `${baseUrl}?${params.toString()}`;
1869
- }
1870
-
1871
2177
  // src/display.ts
1872
2178
  var colors2 = {
1873
2179
  reset: "\x1B[0m",
@@ -1966,27 +2272,85 @@ function formatHour(hour) {
1966
2272
  if (hour === 12) return "12pm";
1967
2273
  return `${hour - 12}pm`;
1968
2274
  }
2275
+ function displayActivityStats(artifact, url, options) {
2276
+ const c = getColors2(options?.theme);
2277
+ const summary = artifact.activity.summary;
2278
+ console.log();
2279
+ console.log(`${c.cyan}${c.bold}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${c.reset}`);
2280
+ console.log(`${c.cyan}${c.bold}\u2551${c.reset} ${c.white}${c.bold}${artifact.title}${c.reset} ${c.cyan}${c.bold}\u2551${c.reset}`);
2281
+ console.log(`${c.cyan}${c.bold}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${c.reset}`);
2282
+ console.log();
2283
+ console.log(`${c.bold}\u{1F4C8} Activity Heatmap${c.reset}`);
2284
+ console.log(`${c.gray}${"\u2500".repeat(60)}${c.reset}`);
2285
+ console.log(` Window: ${artifact.activity.startDate} \u2192 ${artifact.activity.endDate}`);
2286
+ console.log(` Metric: ${artifact.activity.metric}`);
2287
+ console.log(` Active days: ${c.cyan}${c.bold}${summary.activeDays}${c.reset}`);
2288
+ console.log();
2289
+ console.log(`${c.bold}\u26A1 Highlights${c.reset}`);
2290
+ console.log(`${c.gray}${"\u2500".repeat(60)}${c.reset}`);
2291
+ console.log(` Current streak: ${c.green}${summary.currentStreak} days${c.reset}`);
2292
+ console.log(` Longest streak: ${c.cyan}${summary.longestStreak} days${c.reset}`);
2293
+ console.log(` Recent 30 days: ${c.white}${c.bold}${formatCompactNumber(summary.recent30DayTotal)}${c.reset}`);
2294
+ console.log(` Favorite model: ${c.amber}${summary.favoriteModel}${c.reset}`);
2295
+ console.log();
2296
+ console.log(`${c.bold}\u{1F517} Share Activity${c.reset}`);
2297
+ console.log(`${c.gray}${"\u2500".repeat(60)}${c.reset}`);
2298
+ if (options?.shortUrl) {
2299
+ console.log(` ${c.cyan}${c.bold}${options.shortUrl}${c.reset}`);
2300
+ console.log(` ${c.dim}(Canonical URL: ${url})${c.reset}`);
2301
+ } else {
2302
+ console.log(` ${c.cyan}${url}${c.reset}`);
2303
+ }
2304
+ if (options?.imageUrl) {
2305
+ console.log(` Image: ${c.white}${options.imageUrl}${c.reset}`);
2306
+ }
2307
+ console.log();
2308
+ }
1969
2309
 
1970
- // src/shortener.ts
1971
- async function createShortlink(params, baseUrl) {
1972
- const queryString = params.includes("?") ? params.split("?")[1] : params;
2310
+ // src/share-client.ts
2311
+ var VIBESTATS_PUBLIC_HOST = "vibestats.wolfai.dev";
2312
+ var VIBESTATS_SHARE_API_ORIGIN = "https://api.wolfai.dev";
2313
+ function extractLegacyQuery(legacyUrl) {
2314
+ if (!legacyUrl) return null;
2315
+ const queryIndex = legacyUrl.indexOf("?");
2316
+ if (queryIndex === -1) return null;
2317
+ return legacyUrl.slice(queryIndex + 1) || null;
2318
+ }
2319
+ function resolveShareApiBaseUrl(baseUrl) {
2320
+ try {
2321
+ const parsed = new URL(baseUrl);
2322
+ if (parsed.hostname === VIBESTATS_PUBLIC_HOST) {
2323
+ return VIBESTATS_SHARE_API_ORIGIN;
2324
+ }
2325
+ return parsed.origin;
2326
+ } catch {
2327
+ return baseUrl;
2328
+ }
2329
+ }
2330
+ async function publishArtifact(artifact, baseUrl, legacyUrl) {
1973
2331
  try {
1974
- const apiUrl = new URL("/api/shorten", baseUrl);
2332
+ const apiUrl = new URL("/vibestats/shares", resolveShareApiBaseUrl(baseUrl));
1975
2333
  const response = await fetch(apiUrl.toString(), {
1976
2334
  method: "POST",
1977
2335
  headers: {
1978
2336
  "Content-Type": "application/json"
1979
2337
  },
1980
- body: JSON.stringify({ params: queryString })
2338
+ body: JSON.stringify({
2339
+ artifact,
2340
+ legacyQuery: extractLegacyQuery(legacyUrl)
2341
+ })
1981
2342
  });
1982
2343
  if (!response.ok) {
1983
2344
  return null;
1984
2345
  }
1985
2346
  const data = await response.json();
1986
- if (data.slug) {
1987
- return `${baseUrl}/s/${data.slug}`;
1988
- }
1989
- return null;
2347
+ return {
2348
+ id: data.id || data.slug,
2349
+ slug: data.slug,
2350
+ url: data.url,
2351
+ shortUrl: data.shortUrl,
2352
+ imageUrl: data.imageUrl || null
2353
+ };
1990
2354
  } catch {
1991
2355
  return null;
1992
2356
  }
@@ -2140,10 +2504,69 @@ function parseLastDaysFlag(args) {
2140
2504
  shorthandDays.sort((a, b) => a - b);
2141
2505
  return shorthandDays[0];
2142
2506
  }
2507
+ function parseActivityMetric(value) {
2508
+ if (value === "sessions" || value === "messages" || value === "tokens") {
2509
+ return value;
2510
+ }
2511
+ return "tokens";
2512
+ }
2513
+ function parseActivityDays(value) {
2514
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
2515
+ return Math.round(value);
2516
+ }
2517
+ if (typeof value === "string") {
2518
+ const parsed = Number.parseInt(value, 10);
2519
+ if (Number.isFinite(parsed) && parsed > 0) {
2520
+ return parsed;
2521
+ }
2522
+ }
2523
+ return 365;
2524
+ }
2525
+ function buildUsageArtifact(stats) {
2526
+ return {
2527
+ type: "usage",
2528
+ schemaVersion: 1,
2529
+ source: stats.source,
2530
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2531
+ renderOptions: {
2532
+ canonicalPath: "usage",
2533
+ theme: "light"
2534
+ },
2535
+ payload: stats
2536
+ };
2537
+ }
2538
+ function buildWrappedArtifact(stats) {
2539
+ return {
2540
+ type: "wrapped",
2541
+ schemaVersion: 1,
2542
+ source: stats.source || "claude",
2543
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2544
+ renderOptions: {
2545
+ canonicalPath: "wrapped",
2546
+ theme: "light"
2547
+ },
2548
+ payload: stats
2549
+ };
2550
+ }
2551
+ async function publishArtifactWithFallback(artifact, baseUrl, fallbackUrl, preferCanonical = false) {
2552
+ const published = await publishArtifact(artifact, baseUrl, fallbackUrl);
2553
+ if (!published) {
2554
+ return {
2555
+ canonicalUrl: fallbackUrl,
2556
+ shareUrl: fallbackUrl,
2557
+ imageUrl: null
2558
+ };
2559
+ }
2560
+ return {
2561
+ canonicalUrl: published.url,
2562
+ shareUrl: preferCanonical ? published.url : published.shortUrl,
2563
+ imageUrl: published.imageUrl || null
2564
+ };
2565
+ }
2143
2566
  var main = defineCommand({
2144
2567
  meta: {
2145
2568
  name: "vibestats",
2146
- version: "1.3.2",
2569
+ version: "1.3.6",
2147
2570
  description: "AI coding stats - usage tracking and annual wrapped for Claude Code & Codex"
2148
2571
  },
2149
2572
  args: {
@@ -2154,6 +2577,11 @@ var main = defineCommand({
2154
2577
  description: "Show annual wrapped summary instead of usage stats",
2155
2578
  default: false
2156
2579
  },
2580
+ activity: {
2581
+ type: "boolean",
2582
+ description: "Show the GitHub-style activity graph summary",
2583
+ default: false
2584
+ },
2157
2585
  // Data source
2158
2586
  codex: {
2159
2587
  type: "boolean",
@@ -2220,6 +2648,14 @@ var main = defineCommand({
2220
2648
  description: "Use compact table format (hide cache columns)",
2221
2649
  default: false
2222
2650
  },
2651
+ metric: {
2652
+ type: "string",
2653
+ description: "Activity metric: tokens, sessions, or messages"
2654
+ },
2655
+ days: {
2656
+ type: "string",
2657
+ description: "Number of days to include in the activity graph"
2658
+ },
2223
2659
  // Share option for usage mode
2224
2660
  share: {
2225
2661
  type: "boolean",
@@ -2272,7 +2708,9 @@ var main = defineCommand({
2272
2708
  console.log(JSON.stringify(config, null, 2));
2273
2709
  return;
2274
2710
  }
2275
- if (args.wrapped) {
2711
+ if (args.activity) {
2712
+ await runActivity(args, config);
2713
+ } else if (args.wrapped) {
2276
2714
  await runWrapped(args, config);
2277
2715
  } else {
2278
2716
  await runUsage(args, config);
@@ -2343,10 +2781,13 @@ async function runUsage(args, config) {
2343
2781
  let shareUrl = null;
2344
2782
  if (args.share) {
2345
2783
  const fullUrl = encodeUsageToUrl(stats, baseUrl);
2346
- if (!args["no-short"]) {
2347
- shareUrl = await createShortlink(fullUrl, baseUrl);
2348
- }
2349
- shareUrl = shareUrl || fullUrl;
2784
+ const shared = await publishArtifactWithFallback(
2785
+ buildUsageArtifact(stats),
2786
+ baseUrl,
2787
+ fullUrl,
2788
+ Boolean(args["no-short"])
2789
+ );
2790
+ shareUrl = shared.shareUrl;
2350
2791
  }
2351
2792
  if (args.quiet && args.share && shareUrl) {
2352
2793
  console.log(shareUrl);
@@ -2379,9 +2820,14 @@ async function runUsage(args, config) {
2379
2820
  }
2380
2821
  async function runWrapped(args, config) {
2381
2822
  const options = resolveOptions(args, config);
2823
+ const metric = parseActivityMetric(args.metric);
2824
+ const days = parseActivityDays(args.days);
2382
2825
  const spinner = createSpinner("Preparing wrapped...");
2383
- const data = await spinner.whilePromise(
2384
- loadData({ codexOnly: args.codex, combined: args.combined })
2826
+ const [data, activityStats] = await spinner.whilePromise(
2827
+ Promise.all([
2828
+ loadData({ codexOnly: args.codex, combined: args.combined }),
2829
+ loadActivityStats({ codexOnly: args.codex, combined: args.combined })
2830
+ ])
2385
2831
  );
2386
2832
  validateData(data, { codexOnly: args.codex, combined: args.combined });
2387
2833
  let claudeStats = null;
@@ -2400,23 +2846,89 @@ async function runWrapped(args, config) {
2400
2846
  } else {
2401
2847
  stats = claudeStats;
2402
2848
  }
2403
- const url = encodeStatsToUrl(stats, options.baseUrl);
2404
- let shortUrl = null;
2405
- if (!args["no-short"]) {
2406
- shortUrl = await createShortlink(url, options.baseUrl);
2849
+ if (activityStats) {
2850
+ stats.activity = buildActivityGraph(activityStats, metric, days);
2407
2851
  }
2852
+ const legacyUrl = encodeStatsToUrl(stats, options.baseUrl);
2853
+ const published = await publishArtifactWithFallback(
2854
+ buildWrappedArtifact(stats),
2855
+ options.baseUrl,
2856
+ legacyUrl,
2857
+ Boolean(args["no-short"])
2858
+ );
2408
2859
  if (options.outputFormat === "json") {
2409
- console.log(JSON.stringify({ ...stats, url, shortUrl }, null, 2));
2860
+ console.log(
2861
+ JSON.stringify(
2862
+ {
2863
+ ...stats,
2864
+ url: published.canonicalUrl,
2865
+ shortUrl: published.shareUrl === published.canonicalUrl ? null : published.shareUrl,
2866
+ imageUrl: published.imageUrl
2867
+ },
2868
+ null,
2869
+ 2
2870
+ )
2871
+ );
2410
2872
  } else if (options.outputFormat === "quiet") {
2411
- console.log(shortUrl || url);
2873
+ console.log(published.shareUrl);
2412
2874
  } else {
2413
- displayWrappedStats(stats, url, {
2875
+ displayWrappedStats(stats, published.canonicalUrl, {
2414
2876
  theme: options.theme,
2415
2877
  hideCost: options.hideCost,
2416
- shortUrl
2878
+ shortUrl: published.shareUrl === published.canonicalUrl ? null : published.shareUrl,
2879
+ imageUrl: published.imageUrl
2417
2880
  });
2418
2881
  }
2419
2882
  }
2883
+ async function runActivity(args, config) {
2884
+ const metric = parseActivityMetric(args.metric);
2885
+ const days = parseActivityDays(args.days);
2886
+ const spinner = createSpinner("Preparing activity graph...");
2887
+ const stats = await spinner.whilePromise(
2888
+ loadActivityStats({
2889
+ codexOnly: args.codex,
2890
+ combined: args.combined,
2891
+ projectFilter: args.project ? process.cwd() : void 0,
2892
+ since: args.since,
2893
+ until: args.until
2894
+ })
2895
+ );
2896
+ if (!stats) {
2897
+ console.error("Error: No activity data found.");
2898
+ process.exit(1);
2899
+ }
2900
+ const artifact = buildActivityArtifact(stats, metric, days);
2901
+ const baseUrl = args.url || config.baseUrl || "https://vibestats.wolfai.dev";
2902
+ const fallbackUrl = encodeActivityToUrl(artifact.payload, baseUrl);
2903
+ let shared = null;
2904
+ if (args.share) {
2905
+ shared = await publishArtifactWithFallback(artifact, baseUrl, fallbackUrl, Boolean(args["no-short"]));
2906
+ }
2907
+ if (args.json) {
2908
+ console.log(
2909
+ JSON.stringify(
2910
+ {
2911
+ ...artifact.payload,
2912
+ url: shared?.canonicalUrl || null,
2913
+ shortUrl: shared && shared.shareUrl !== shared.canonicalUrl ? shared.shareUrl : null,
2914
+ imageUrl: shared?.imageUrl || null
2915
+ },
2916
+ null,
2917
+ 2
2918
+ )
2919
+ );
2920
+ return;
2921
+ }
2922
+ if (args.quiet) {
2923
+ console.log(shared?.shareUrl || fallbackUrl);
2924
+ return;
2925
+ }
2926
+ displayActivityStats(artifact.payload, shared?.canonicalUrl || fallbackUrl, {
2927
+ theme: config.theme,
2928
+ shortUrl: shared && shared.shareUrl !== shared.canonicalUrl ? shared.shareUrl : null,
2929
+ imageUrl: shared?.imageUrl || null
2930
+ });
2931
+ }
2420
2932
  function formatNumber2(n) {
2421
2933
  if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
2422
2934
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibestats",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
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",