vibestats 1.3.4 → 1.3.5
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/dist/index.js +819 -320
- 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
|
-
|
|
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,72 @@ 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/
|
|
1971
|
-
|
|
1972
|
-
|
|
2310
|
+
// src/share-client.ts
|
|
2311
|
+
function extractLegacyQuery(legacyUrl) {
|
|
2312
|
+
if (!legacyUrl) return null;
|
|
2313
|
+
const queryIndex = legacyUrl.indexOf("?");
|
|
2314
|
+
if (queryIndex === -1) return null;
|
|
2315
|
+
return legacyUrl.slice(queryIndex + 1) || null;
|
|
2316
|
+
}
|
|
2317
|
+
async function publishArtifact(artifact, baseUrl, legacyUrl) {
|
|
1973
2318
|
try {
|
|
1974
|
-
const apiUrl = new URL("/
|
|
2319
|
+
const apiUrl = new URL("/vibestats/shares", baseUrl);
|
|
1975
2320
|
const response = await fetch(apiUrl.toString(), {
|
|
1976
2321
|
method: "POST",
|
|
1977
2322
|
headers: {
|
|
1978
2323
|
"Content-Type": "application/json"
|
|
1979
2324
|
},
|
|
1980
|
-
body: JSON.stringify({
|
|
2325
|
+
body: JSON.stringify({
|
|
2326
|
+
artifact,
|
|
2327
|
+
legacyQuery: extractLegacyQuery(legacyUrl)
|
|
2328
|
+
})
|
|
1981
2329
|
});
|
|
1982
2330
|
if (!response.ok) {
|
|
1983
2331
|
return null;
|
|
1984
2332
|
}
|
|
1985
2333
|
const data = await response.json();
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
2334
|
+
return {
|
|
2335
|
+
id: data.id || data.slug,
|
|
2336
|
+
slug: data.slug,
|
|
2337
|
+
url: data.url,
|
|
2338
|
+
shortUrl: data.shortUrl,
|
|
2339
|
+
imageUrl: data.imageUrl || null
|
|
2340
|
+
};
|
|
1990
2341
|
} catch {
|
|
1991
2342
|
return null;
|
|
1992
2343
|
}
|
|
@@ -2140,6 +2491,65 @@ function parseLastDaysFlag(args) {
|
|
|
2140
2491
|
shorthandDays.sort((a, b) => a - b);
|
|
2141
2492
|
return shorthandDays[0];
|
|
2142
2493
|
}
|
|
2494
|
+
function parseActivityMetric(value) {
|
|
2495
|
+
if (value === "sessions" || value === "messages" || value === "tokens") {
|
|
2496
|
+
return value;
|
|
2497
|
+
}
|
|
2498
|
+
return "tokens";
|
|
2499
|
+
}
|
|
2500
|
+
function parseActivityDays(value) {
|
|
2501
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
2502
|
+
return Math.round(value);
|
|
2503
|
+
}
|
|
2504
|
+
if (typeof value === "string") {
|
|
2505
|
+
const parsed = Number.parseInt(value, 10);
|
|
2506
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
2507
|
+
return parsed;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
return 365;
|
|
2511
|
+
}
|
|
2512
|
+
function buildUsageArtifact(stats) {
|
|
2513
|
+
return {
|
|
2514
|
+
type: "usage",
|
|
2515
|
+
schemaVersion: 1,
|
|
2516
|
+
source: stats.source,
|
|
2517
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2518
|
+
renderOptions: {
|
|
2519
|
+
canonicalPath: "usage",
|
|
2520
|
+
theme: "light"
|
|
2521
|
+
},
|
|
2522
|
+
payload: stats
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
function buildWrappedArtifact(stats) {
|
|
2526
|
+
return {
|
|
2527
|
+
type: "wrapped",
|
|
2528
|
+
schemaVersion: 1,
|
|
2529
|
+
source: stats.source || "claude",
|
|
2530
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2531
|
+
renderOptions: {
|
|
2532
|
+
canonicalPath: "wrapped",
|
|
2533
|
+
theme: "light"
|
|
2534
|
+
},
|
|
2535
|
+
payload: stats
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
async function publishArtifactWithFallback(artifact, baseUrl, fallbackUrl, preferCanonical = false) {
|
|
2539
|
+
const published = await publishArtifact(artifact, baseUrl, fallbackUrl);
|
|
2540
|
+
if (!published) {
|
|
2541
|
+
return {
|
|
2542
|
+
canonicalUrl: fallbackUrl,
|
|
2543
|
+
shareUrl: fallbackUrl,
|
|
2544
|
+
imageUrl: null
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
return {
|
|
2548
|
+
canonicalUrl: published.url,
|
|
2549
|
+
shareUrl: preferCanonical ? published.url : published.shortUrl,
|
|
2550
|
+
imageUrl: published.imageUrl || null
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2143
2553
|
var main = defineCommand({
|
|
2144
2554
|
meta: {
|
|
2145
2555
|
name: "vibestats",
|
|
@@ -2154,6 +2564,11 @@ var main = defineCommand({
|
|
|
2154
2564
|
description: "Show annual wrapped summary instead of usage stats",
|
|
2155
2565
|
default: false
|
|
2156
2566
|
},
|
|
2567
|
+
activity: {
|
|
2568
|
+
type: "boolean",
|
|
2569
|
+
description: "Show the GitHub-style activity graph summary",
|
|
2570
|
+
default: false
|
|
2571
|
+
},
|
|
2157
2572
|
// Data source
|
|
2158
2573
|
codex: {
|
|
2159
2574
|
type: "boolean",
|
|
@@ -2220,6 +2635,14 @@ var main = defineCommand({
|
|
|
2220
2635
|
description: "Use compact table format (hide cache columns)",
|
|
2221
2636
|
default: false
|
|
2222
2637
|
},
|
|
2638
|
+
metric: {
|
|
2639
|
+
type: "string",
|
|
2640
|
+
description: "Activity metric: tokens, sessions, or messages"
|
|
2641
|
+
},
|
|
2642
|
+
days: {
|
|
2643
|
+
type: "string",
|
|
2644
|
+
description: "Number of days to include in the activity graph"
|
|
2645
|
+
},
|
|
2223
2646
|
// Share option for usage mode
|
|
2224
2647
|
share: {
|
|
2225
2648
|
type: "boolean",
|
|
@@ -2272,7 +2695,9 @@ var main = defineCommand({
|
|
|
2272
2695
|
console.log(JSON.stringify(config, null, 2));
|
|
2273
2696
|
return;
|
|
2274
2697
|
}
|
|
2275
|
-
if (args.
|
|
2698
|
+
if (args.activity) {
|
|
2699
|
+
await runActivity(args, config);
|
|
2700
|
+
} else if (args.wrapped) {
|
|
2276
2701
|
await runWrapped(args, config);
|
|
2277
2702
|
} else {
|
|
2278
2703
|
await runUsage(args, config);
|
|
@@ -2343,10 +2768,13 @@ async function runUsage(args, config) {
|
|
|
2343
2768
|
let shareUrl = null;
|
|
2344
2769
|
if (args.share) {
|
|
2345
2770
|
const fullUrl = encodeUsageToUrl(stats, baseUrl);
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2771
|
+
const shared = await publishArtifactWithFallback(
|
|
2772
|
+
buildUsageArtifact(stats),
|
|
2773
|
+
baseUrl,
|
|
2774
|
+
fullUrl,
|
|
2775
|
+
Boolean(args["no-short"])
|
|
2776
|
+
);
|
|
2777
|
+
shareUrl = shared.shareUrl;
|
|
2350
2778
|
}
|
|
2351
2779
|
if (args.quiet && args.share && shareUrl) {
|
|
2352
2780
|
console.log(shareUrl);
|
|
@@ -2379,9 +2807,14 @@ async function runUsage(args, config) {
|
|
|
2379
2807
|
}
|
|
2380
2808
|
async function runWrapped(args, config) {
|
|
2381
2809
|
const options = resolveOptions(args, config);
|
|
2810
|
+
const metric = parseActivityMetric(args.metric);
|
|
2811
|
+
const days = parseActivityDays(args.days);
|
|
2382
2812
|
const spinner = createSpinner("Preparing wrapped...");
|
|
2383
|
-
const data = await spinner.whilePromise(
|
|
2384
|
-
|
|
2813
|
+
const [data, activityStats] = await spinner.whilePromise(
|
|
2814
|
+
Promise.all([
|
|
2815
|
+
loadData({ codexOnly: args.codex, combined: args.combined }),
|
|
2816
|
+
loadActivityStats({ codexOnly: args.codex, combined: args.combined })
|
|
2817
|
+
])
|
|
2385
2818
|
);
|
|
2386
2819
|
validateData(data, { codexOnly: args.codex, combined: args.combined });
|
|
2387
2820
|
let claudeStats = null;
|
|
@@ -2400,23 +2833,89 @@ async function runWrapped(args, config) {
|
|
|
2400
2833
|
} else {
|
|
2401
2834
|
stats = claudeStats;
|
|
2402
2835
|
}
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
if (!args["no-short"]) {
|
|
2406
|
-
shortUrl = await createShortlink(url, options.baseUrl);
|
|
2836
|
+
if (activityStats) {
|
|
2837
|
+
stats.activity = buildActivityGraph(activityStats, metric, days);
|
|
2407
2838
|
}
|
|
2839
|
+
const legacyUrl = encodeStatsToUrl(stats, options.baseUrl);
|
|
2840
|
+
const published = await publishArtifactWithFallback(
|
|
2841
|
+
buildWrappedArtifact(stats),
|
|
2842
|
+
options.baseUrl,
|
|
2843
|
+
legacyUrl,
|
|
2844
|
+
Boolean(args["no-short"])
|
|
2845
|
+
);
|
|
2408
2846
|
if (options.outputFormat === "json") {
|
|
2409
|
-
console.log(
|
|
2847
|
+
console.log(
|
|
2848
|
+
JSON.stringify(
|
|
2849
|
+
{
|
|
2850
|
+
...stats,
|
|
2851
|
+
url: published.canonicalUrl,
|
|
2852
|
+
shortUrl: published.shareUrl === published.canonicalUrl ? null : published.shareUrl,
|
|
2853
|
+
imageUrl: published.imageUrl
|
|
2854
|
+
},
|
|
2855
|
+
null,
|
|
2856
|
+
2
|
|
2857
|
+
)
|
|
2858
|
+
);
|
|
2410
2859
|
} else if (options.outputFormat === "quiet") {
|
|
2411
|
-
console.log(
|
|
2860
|
+
console.log(published.shareUrl);
|
|
2412
2861
|
} else {
|
|
2413
|
-
displayWrappedStats(stats,
|
|
2862
|
+
displayWrappedStats(stats, published.canonicalUrl, {
|
|
2414
2863
|
theme: options.theme,
|
|
2415
2864
|
hideCost: options.hideCost,
|
|
2416
|
-
shortUrl
|
|
2865
|
+
shortUrl: published.shareUrl === published.canonicalUrl ? null : published.shareUrl,
|
|
2866
|
+
imageUrl: published.imageUrl
|
|
2417
2867
|
});
|
|
2418
2868
|
}
|
|
2419
2869
|
}
|
|
2870
|
+
async function runActivity(args, config) {
|
|
2871
|
+
const metric = parseActivityMetric(args.metric);
|
|
2872
|
+
const days = parseActivityDays(args.days);
|
|
2873
|
+
const spinner = createSpinner("Preparing activity graph...");
|
|
2874
|
+
const stats = await spinner.whilePromise(
|
|
2875
|
+
loadActivityStats({
|
|
2876
|
+
codexOnly: args.codex,
|
|
2877
|
+
combined: args.combined,
|
|
2878
|
+
projectFilter: args.project ? process.cwd() : void 0,
|
|
2879
|
+
since: args.since,
|
|
2880
|
+
until: args.until
|
|
2881
|
+
})
|
|
2882
|
+
);
|
|
2883
|
+
if (!stats) {
|
|
2884
|
+
console.error("Error: No activity data found.");
|
|
2885
|
+
process.exit(1);
|
|
2886
|
+
}
|
|
2887
|
+
const artifact = buildActivityArtifact(stats, metric, days);
|
|
2888
|
+
const baseUrl = args.url || config.baseUrl || "https://vibestats.wolfai.dev";
|
|
2889
|
+
const fallbackUrl = encodeActivityToUrl(artifact.payload, baseUrl);
|
|
2890
|
+
let shared = null;
|
|
2891
|
+
if (args.share) {
|
|
2892
|
+
shared = await publishArtifactWithFallback(artifact, baseUrl, fallbackUrl, Boolean(args["no-short"]));
|
|
2893
|
+
}
|
|
2894
|
+
if (args.json) {
|
|
2895
|
+
console.log(
|
|
2896
|
+
JSON.stringify(
|
|
2897
|
+
{
|
|
2898
|
+
...artifact.payload,
|
|
2899
|
+
url: shared?.canonicalUrl || null,
|
|
2900
|
+
shortUrl: shared && shared.shareUrl !== shared.canonicalUrl ? shared.shareUrl : null,
|
|
2901
|
+
imageUrl: shared?.imageUrl || null
|
|
2902
|
+
},
|
|
2903
|
+
null,
|
|
2904
|
+
2
|
|
2905
|
+
)
|
|
2906
|
+
);
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
if (args.quiet) {
|
|
2910
|
+
console.log(shared?.shareUrl || fallbackUrl);
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
displayActivityStats(artifact.payload, shared?.canonicalUrl || fallbackUrl, {
|
|
2914
|
+
theme: config.theme,
|
|
2915
|
+
shortUrl: shared && shared.shareUrl !== shared.canonicalUrl ? shared.shareUrl : null,
|
|
2916
|
+
imageUrl: shared?.imageUrl || null
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2420
2919
|
function formatNumber2(n) {
|
|
2421
2920
|
if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
|
|
2422
2921
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|