vibestats 1.3.3 → 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 +988 -416
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -3,6 +3,663 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { defineCommand, runMain } from "citty";
|
|
5
5
|
|
|
6
|
+
// src/codex-pricing.ts
|
|
7
|
+
var GPT_54_PRICING = { input: 2.5, output: 15, cachedInput: 0.25 };
|
|
8
|
+
var GPT_53_PRICING = { input: 1.75, output: 14, cachedInput: 0.175 };
|
|
9
|
+
var GPT_51_PRICING = { input: 1.25, output: 10, cachedInput: 0.125 };
|
|
10
|
+
var GPT_5_MINI_PRICING = { input: 0.25, output: 2, cachedInput: 0.025 };
|
|
11
|
+
var GPT_5_NANO_PRICING = { input: 0.05, output: 0.4, cachedInput: 5e-3 };
|
|
12
|
+
var GPT_4O_PRICING = { input: 2.5, output: 10, cachedInput: 1.25 };
|
|
13
|
+
var GPT_4O_MINI_PRICING = { input: 0.15, output: 0.6, cachedInput: 0.075 };
|
|
14
|
+
var CODEX_MODEL_DEFINITIONS = [
|
|
15
|
+
{
|
|
16
|
+
key: "gpt-5.4",
|
|
17
|
+
displayName: "GPT-5.4",
|
|
18
|
+
token: "g54",
|
|
19
|
+
sortPriority: 77,
|
|
20
|
+
pricing: GPT_54_PRICING,
|
|
21
|
+
aliases: ["GPT-5.4"],
|
|
22
|
+
exactNames: ["gpt-5.4"]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: "gpt-5.3-codex-spark",
|
|
26
|
+
displayName: "GPT-5.3 Codex Spark",
|
|
27
|
+
token: "g53s",
|
|
28
|
+
sortPriority: 76,
|
|
29
|
+
pricing: GPT_53_PRICING,
|
|
30
|
+
aliases: ["GPT-5.3 Codex Spark"],
|
|
31
|
+
exactNames: ["gpt-5.3-codex-spark"],
|
|
32
|
+
includePatterns: [["5.3", "spark"]]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "gpt-5.3-codex",
|
|
36
|
+
displayName: "GPT-5.3 Codex",
|
|
37
|
+
token: "g53c",
|
|
38
|
+
sortPriority: 75,
|
|
39
|
+
pricing: GPT_53_PRICING,
|
|
40
|
+
aliases: ["GPT-5.3 Codex"],
|
|
41
|
+
exactNames: ["gpt-5.3-codex"],
|
|
42
|
+
includePatterns: [["5.3", "codex"]]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: "gpt-5.3",
|
|
46
|
+
displayName: "GPT-5.3",
|
|
47
|
+
token: "g53",
|
|
48
|
+
sortPriority: 74,
|
|
49
|
+
pricing: GPT_53_PRICING,
|
|
50
|
+
aliases: ["GPT-5.3"],
|
|
51
|
+
exactNames: ["gpt-5.3"],
|
|
52
|
+
includePatterns: [["5.3"]]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: "gpt-5.2-codex",
|
|
56
|
+
displayName: "GPT-5.2 Codex",
|
|
57
|
+
token: "g52c",
|
|
58
|
+
sortPriority: 73,
|
|
59
|
+
pricing: GPT_53_PRICING,
|
|
60
|
+
aliases: ["GPT-5.2 Codex"],
|
|
61
|
+
exactNames: ["gpt-5.2-codex"],
|
|
62
|
+
includePatterns: [["5.2", "codex"]]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: "gpt-5.2",
|
|
66
|
+
displayName: "GPT-5.2",
|
|
67
|
+
token: "g52",
|
|
68
|
+
sortPriority: 72,
|
|
69
|
+
pricing: GPT_53_PRICING,
|
|
70
|
+
aliases: ["GPT-5.2"],
|
|
71
|
+
exactNames: ["gpt-5.2"],
|
|
72
|
+
includePatterns: [["5.2"]]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: "gpt-5.1-codex-max",
|
|
76
|
+
displayName: "GPT-5.1 Codex Max",
|
|
77
|
+
token: "g51cm",
|
|
78
|
+
sortPriority: 71,
|
|
79
|
+
pricing: GPT_51_PRICING,
|
|
80
|
+
aliases: ["GPT-5.1 Codex Max"],
|
|
81
|
+
exactNames: ["gpt-5.1-codex-max"],
|
|
82
|
+
includePatterns: [["5.1", "codex", "max"]]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "legacy-gpt-5.1-max",
|
|
86
|
+
displayName: "GPT-5.1 Max",
|
|
87
|
+
token: "g51m",
|
|
88
|
+
sortPriority: 70,
|
|
89
|
+
pricing: GPT_51_PRICING,
|
|
90
|
+
aliases: ["GPT-5.1 Max"]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: "gpt-5.1-codex-mini",
|
|
94
|
+
displayName: "GPT-5.1 Codex Mini",
|
|
95
|
+
token: "g51cn",
|
|
96
|
+
sortPriority: 69,
|
|
97
|
+
pricing: GPT_5_MINI_PRICING,
|
|
98
|
+
aliases: ["GPT-5.1 Codex Mini"],
|
|
99
|
+
exactNames: ["gpt-5.1-codex-mini"],
|
|
100
|
+
includePatterns: [["5.1", "codex", "mini"]]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
key: "legacy-gpt-5.1-mini",
|
|
104
|
+
displayName: "GPT-5.1 Mini",
|
|
105
|
+
token: "g51n",
|
|
106
|
+
sortPriority: 68,
|
|
107
|
+
pricing: GPT_5_MINI_PRICING,
|
|
108
|
+
aliases: ["GPT-5.1 Mini"]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
key: "gpt-5.1-codex",
|
|
112
|
+
displayName: "GPT-5.1 Codex",
|
|
113
|
+
token: "g51c",
|
|
114
|
+
sortPriority: 67,
|
|
115
|
+
pricing: GPT_51_PRICING,
|
|
116
|
+
aliases: ["GPT-5.1 Codex"],
|
|
117
|
+
exactNames: ["gpt-5.1-codex"],
|
|
118
|
+
includePatterns: [["5.1", "codex"]]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
key: "gpt-5.1",
|
|
122
|
+
displayName: "GPT-5.1",
|
|
123
|
+
token: "g51",
|
|
124
|
+
sortPriority: 66,
|
|
125
|
+
pricing: GPT_51_PRICING,
|
|
126
|
+
aliases: ["GPT-5.1"],
|
|
127
|
+
exactNames: ["gpt-5.1"],
|
|
128
|
+
includePatterns: [["5.1"]]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
key: "gpt-5",
|
|
132
|
+
displayName: "GPT-5",
|
|
133
|
+
token: "g5",
|
|
134
|
+
sortPriority: 64,
|
|
135
|
+
pricing: GPT_51_PRICING,
|
|
136
|
+
aliases: ["GPT-5"],
|
|
137
|
+
exactNames: ["gpt-5"]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: "gpt-5-codex",
|
|
141
|
+
displayName: "GPT-5 Codex",
|
|
142
|
+
sortPriority: 64,
|
|
143
|
+
pricing: GPT_51_PRICING,
|
|
144
|
+
aliases: ["GPT-5 Codex"],
|
|
145
|
+
exactNames: ["gpt-5-codex"]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
key: "gpt-5-mini",
|
|
149
|
+
displayName: "GPT-5 Mini",
|
|
150
|
+
token: "g5n",
|
|
151
|
+
sortPriority: 63,
|
|
152
|
+
pricing: GPT_5_MINI_PRICING,
|
|
153
|
+
aliases: ["GPT-5 Mini"],
|
|
154
|
+
exactNames: ["gpt-5-mini"]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
key: "gpt-5-codex-mini",
|
|
158
|
+
displayName: "GPT-5 Mini",
|
|
159
|
+
token: "g5n",
|
|
160
|
+
sortPriority: 63,
|
|
161
|
+
pricing: GPT_5_MINI_PRICING,
|
|
162
|
+
aliases: ["GPT-5 Codex Mini"],
|
|
163
|
+
exactNames: ["gpt-5-codex-mini"]
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
key: "gpt-5-nano",
|
|
167
|
+
displayName: "GPT-5 Nano",
|
|
168
|
+
sortPriority: 62,
|
|
169
|
+
pricing: GPT_5_NANO_PRICING,
|
|
170
|
+
aliases: ["GPT-5 Nano"],
|
|
171
|
+
exactNames: ["gpt-5-nano"]
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
key: "gpt-4o",
|
|
175
|
+
displayName: "GPT-4o",
|
|
176
|
+
token: "g4o",
|
|
177
|
+
sortPriority: 60,
|
|
178
|
+
pricing: GPT_4O_PRICING,
|
|
179
|
+
aliases: ["GPT-4o"],
|
|
180
|
+
exactNames: ["gpt-4o"]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: "gpt-4o-mini",
|
|
184
|
+
displayName: "GPT-4o Mini",
|
|
185
|
+
token: "g4om",
|
|
186
|
+
sortPriority: 59,
|
|
187
|
+
pricing: GPT_4O_MINI_PRICING,
|
|
188
|
+
aliases: ["GPT-4o Mini"],
|
|
189
|
+
exactNames: ["gpt-4o-mini"]
|
|
190
|
+
}
|
|
191
|
+
];
|
|
192
|
+
function normalizeName(value) {
|
|
193
|
+
return value.trim().toLowerCase();
|
|
194
|
+
}
|
|
195
|
+
function findCodexModelDefinition(modelName) {
|
|
196
|
+
const normalized = normalizeName(modelName);
|
|
197
|
+
const exactMatch = CODEX_MODEL_DEFINITIONS.find((definition) => {
|
|
198
|
+
if (normalizeName(definition.displayName) === normalized) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
if (definition.aliases?.some((alias) => normalizeName(alias) === normalized)) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (definition.exactNames?.includes(normalized)) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
});
|
|
209
|
+
if (exactMatch) {
|
|
210
|
+
return exactMatch;
|
|
211
|
+
}
|
|
212
|
+
return CODEX_MODEL_DEFINITIONS.find(
|
|
213
|
+
(definition) => definition.includePatterns?.some(
|
|
214
|
+
(pattern) => pattern.every((part) => normalized.includes(part))
|
|
215
|
+
) ?? false
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
function formatUnknownCodexDisplayName(modelName) {
|
|
219
|
+
const normalized = normalizeName(modelName);
|
|
220
|
+
if (normalized.includes("4o-mini")) return "GPT-4o Mini";
|
|
221
|
+
if (normalized.includes("4o")) return "GPT-4o";
|
|
222
|
+
const gpt5Match = normalized.match(/gpt[- ]?5(?:\.(\d+))?/);
|
|
223
|
+
if (!gpt5Match) {
|
|
224
|
+
return modelName;
|
|
225
|
+
}
|
|
226
|
+
const version = gpt5Match[1] ? `.${gpt5Match[1]}` : "";
|
|
227
|
+
if (normalized.includes("mini")) return `GPT-5${version} Mini`;
|
|
228
|
+
if (normalized.includes("nano")) return `GPT-5${version} Nano`;
|
|
229
|
+
if (normalized.includes("codex")) return `GPT-5${version} Codex`;
|
|
230
|
+
return `GPT-5${version}`;
|
|
231
|
+
}
|
|
232
|
+
function encodeRawModelToken(displayName) {
|
|
233
|
+
return `raw_${encodeURIComponent(displayName)}`;
|
|
234
|
+
}
|
|
235
|
+
var CODEX_MODEL_PRICING = CODEX_MODEL_DEFINITIONS.reduce(
|
|
236
|
+
(acc, definition) => {
|
|
237
|
+
if (definition.pricing && definition.exactNames?.length) {
|
|
238
|
+
acc[definition.exactNames[0]] = definition.pricing;
|
|
239
|
+
}
|
|
240
|
+
return acc;
|
|
241
|
+
},
|
|
242
|
+
{}
|
|
243
|
+
);
|
|
244
|
+
function getCodexModelPricing(modelName) {
|
|
245
|
+
return findCodexModelDefinition(modelName)?.pricing ?? GPT_51_PRICING;
|
|
246
|
+
}
|
|
247
|
+
function getCodexModelAbbreviation(modelName) {
|
|
248
|
+
const definition = findCodexModelDefinition(modelName);
|
|
249
|
+
if (definition?.token) {
|
|
250
|
+
return definition.token;
|
|
251
|
+
}
|
|
252
|
+
return encodeRawModelToken(getCodexModelDisplayName(modelName));
|
|
253
|
+
}
|
|
254
|
+
function getCodexModelDisplayName(modelName) {
|
|
255
|
+
return findCodexModelDefinition(modelName)?.displayName ?? formatUnknownCodexDisplayName(modelName);
|
|
256
|
+
}
|
|
257
|
+
function getCodexModelSortPriority(modelName) {
|
|
258
|
+
const definition = findCodexModelDefinition(modelName);
|
|
259
|
+
if (definition) {
|
|
260
|
+
return definition.sortPriority;
|
|
261
|
+
}
|
|
262
|
+
const displayName = getCodexModelDisplayName(modelName);
|
|
263
|
+
const gpt5VersionMatch = displayName.match(/^GPT-5\.(\d+)/);
|
|
264
|
+
if (gpt5VersionMatch) {
|
|
265
|
+
return 64 + parseInt(gpt5VersionMatch[1], 10);
|
|
266
|
+
}
|
|
267
|
+
if (displayName === "GPT-5 Mini") return 63;
|
|
268
|
+
if (displayName === "GPT-5 Nano") return 62;
|
|
269
|
+
if (displayName === "GPT-5") return 64;
|
|
270
|
+
if (displayName === "GPT-4o Mini") return 59;
|
|
271
|
+
if (displayName === "GPT-4o") return 60;
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
function calculateCodexCost(modelName, inputTokens, outputTokens, cachedInputTokens) {
|
|
275
|
+
const pricing = getCodexModelPricing(modelName);
|
|
276
|
+
const regularInputTokens = inputTokens - cachedInputTokens;
|
|
277
|
+
const inputCost = regularInputTokens / 1e6 * pricing.input;
|
|
278
|
+
const cachedCost = cachedInputTokens / 1e6 * pricing.cachedInput;
|
|
279
|
+
const outputCost = outputTokens / 1e6 * pricing.output;
|
|
280
|
+
return inputCost + cachedCost + outputCost;
|
|
281
|
+
}
|
|
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
|
+
|
|
6
663
|
// src/usage/loader.ts
|
|
7
664
|
import { promises as fs } from "fs";
|
|
8
665
|
import { homedir } from "os";
|
|
@@ -24,6 +681,13 @@ var MODEL_PRICING = {
|
|
|
24
681
|
cacheWrite: 6.25,
|
|
25
682
|
cacheRead: 0.5
|
|
26
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
|
+
},
|
|
27
691
|
// Sonnet 4.5
|
|
28
692
|
"claude-sonnet-4-5-20250929": {
|
|
29
693
|
input: 3,
|
|
@@ -79,6 +743,9 @@ function getModelPricing(modelName) {
|
|
|
79
743
|
if (modelName.includes("opus-4-1") || modelName.includes("opus-4.1") || modelName.includes("opus-4")) {
|
|
80
744
|
return MODEL_PRICING["claude-opus-4-1-20250805"];
|
|
81
745
|
}
|
|
746
|
+
if (modelName.includes("sonnet-4-6") || modelName.includes("sonnet-4.6")) {
|
|
747
|
+
return MODEL_PRICING["claude-sonnet-4-6-20260101"];
|
|
748
|
+
}
|
|
82
749
|
if (modelName.includes("sonnet-4-5") || modelName.includes("sonnet-4.5")) {
|
|
83
750
|
return MODEL_PRICING["claude-sonnet-4-5-20250929"];
|
|
84
751
|
}
|
|
@@ -98,190 +765,14 @@ function getModelDisplayName(modelName) {
|
|
|
98
765
|
if (modelName.includes("opus-4-5") || modelName.includes("opus-4.5")) return "Opus 4.5";
|
|
99
766
|
if (modelName.includes("opus-4-1") || modelName.includes("opus-4.1")) return "Opus 4.1";
|
|
100
767
|
if (modelName.includes("opus")) return "Opus";
|
|
768
|
+
if (modelName.includes("sonnet-4-6") || modelName.includes("sonnet-4.6")) return "Sonnet 4.6";
|
|
101
769
|
if (modelName.includes("sonnet-4-5") || modelName.includes("sonnet-4.5")) return "Sonnet 4.5";
|
|
102
770
|
if (modelName.includes("sonnet-3-5") || modelName.includes("sonnet-3.5")) return "Sonnet 3.5";
|
|
103
771
|
if (modelName.includes("sonnet")) return "Sonnet";
|
|
104
772
|
if (modelName.includes("haiku-4-5") || modelName.includes("haiku-4.5")) return "Haiku 4.5";
|
|
105
773
|
if (modelName.includes("haiku-3-5") || modelName.includes("haiku-3.5")) return "Haiku 3.5";
|
|
106
774
|
if (modelName.includes("haiku")) return "Haiku";
|
|
107
|
-
return modelName;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// src/codex-pricing.ts
|
|
111
|
-
var CODEX_MODEL_PRICING = {
|
|
112
|
-
// GPT-5.3 (latest flagship)
|
|
113
|
-
"gpt-5.3": {
|
|
114
|
-
input: 1.75,
|
|
115
|
-
output: 14,
|
|
116
|
-
cachedInput: 0.175
|
|
117
|
-
},
|
|
118
|
-
"gpt-5.3-codex-spark": {
|
|
119
|
-
input: 1.75,
|
|
120
|
-
output: 14,
|
|
121
|
-
cachedInput: 0.175
|
|
122
|
-
},
|
|
123
|
-
"gpt-5.3-codex": {
|
|
124
|
-
input: 1.75,
|
|
125
|
-
output: 14,
|
|
126
|
-
cachedInput: 0.175
|
|
127
|
-
},
|
|
128
|
-
// GPT-5.2
|
|
129
|
-
// Cached input is ~10% of input price (prompt caching discount)
|
|
130
|
-
"gpt-5.2": {
|
|
131
|
-
input: 1.75,
|
|
132
|
-
output: 14,
|
|
133
|
-
cachedInput: 0.175
|
|
134
|
-
},
|
|
135
|
-
"gpt-5.2-codex": {
|
|
136
|
-
input: 1.75,
|
|
137
|
-
output: 14,
|
|
138
|
-
cachedInput: 0.175
|
|
139
|
-
},
|
|
140
|
-
// GPT-5.1 models
|
|
141
|
-
"gpt-5.1": {
|
|
142
|
-
input: 1.25,
|
|
143
|
-
output: 10,
|
|
144
|
-
cachedInput: 0.125
|
|
145
|
-
},
|
|
146
|
-
"gpt-5.1-codex": {
|
|
147
|
-
input: 1.25,
|
|
148
|
-
output: 10,
|
|
149
|
-
cachedInput: 0.125
|
|
150
|
-
},
|
|
151
|
-
"gpt-5.1-codex-max": {
|
|
152
|
-
input: 1.25,
|
|
153
|
-
output: 10,
|
|
154
|
-
cachedInput: 0.125
|
|
155
|
-
},
|
|
156
|
-
// GPT-5.1 Mini
|
|
157
|
-
"gpt-5.1-codex-mini": {
|
|
158
|
-
input: 0.25,
|
|
159
|
-
output: 2,
|
|
160
|
-
cachedInput: 0.025
|
|
161
|
-
},
|
|
162
|
-
// GPT-5 base models
|
|
163
|
-
"gpt-5": {
|
|
164
|
-
input: 1.25,
|
|
165
|
-
output: 10,
|
|
166
|
-
cachedInput: 0.125
|
|
167
|
-
},
|
|
168
|
-
"gpt-5-codex": {
|
|
169
|
-
input: 1.25,
|
|
170
|
-
output: 10,
|
|
171
|
-
cachedInput: 0.125
|
|
172
|
-
},
|
|
173
|
-
// GPT-5 Mini variants
|
|
174
|
-
"gpt-5-mini": {
|
|
175
|
-
input: 0.25,
|
|
176
|
-
output: 2,
|
|
177
|
-
cachedInput: 0.025
|
|
178
|
-
},
|
|
179
|
-
"gpt-5-codex-mini": {
|
|
180
|
-
input: 0.25,
|
|
181
|
-
output: 2,
|
|
182
|
-
cachedInput: 0.025
|
|
183
|
-
},
|
|
184
|
-
// GPT-5 Nano
|
|
185
|
-
"gpt-5-nano": {
|
|
186
|
-
input: 0.05,
|
|
187
|
-
output: 0.4,
|
|
188
|
-
cachedInput: 5e-3
|
|
189
|
-
},
|
|
190
|
-
// GPT-4o (fallback for older sessions)
|
|
191
|
-
"gpt-4o": {
|
|
192
|
-
input: 2.5,
|
|
193
|
-
output: 10,
|
|
194
|
-
cachedInput: 1.25
|
|
195
|
-
},
|
|
196
|
-
"gpt-4o-mini": {
|
|
197
|
-
input: 0.15,
|
|
198
|
-
output: 0.6,
|
|
199
|
-
cachedInput: 0.075
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
function getCodexModelPricing(modelName) {
|
|
203
|
-
if (CODEX_MODEL_PRICING[modelName]) {
|
|
204
|
-
return CODEX_MODEL_PRICING[modelName];
|
|
205
|
-
}
|
|
206
|
-
const normalized = modelName.toLowerCase();
|
|
207
|
-
if (normalized.includes("5.3") && normalized.includes("spark")) {
|
|
208
|
-
return CODEX_MODEL_PRICING["gpt-5.3-codex-spark"];
|
|
209
|
-
}
|
|
210
|
-
if (normalized.includes("5.3") && normalized.includes("codex")) {
|
|
211
|
-
return CODEX_MODEL_PRICING["gpt-5.3-codex"];
|
|
212
|
-
}
|
|
213
|
-
if (normalized.includes("5.3")) {
|
|
214
|
-
return CODEX_MODEL_PRICING["gpt-5.3"];
|
|
215
|
-
}
|
|
216
|
-
if (normalized.includes("5.2") && normalized.includes("codex")) {
|
|
217
|
-
return CODEX_MODEL_PRICING["gpt-5.2-codex"];
|
|
218
|
-
}
|
|
219
|
-
if (normalized.includes("5.2")) {
|
|
220
|
-
return CODEX_MODEL_PRICING["gpt-5.2"];
|
|
221
|
-
}
|
|
222
|
-
if (normalized.includes("5.1") && normalized.includes("codex") && normalized.includes("mini")) {
|
|
223
|
-
return CODEX_MODEL_PRICING["gpt-5.1-codex-mini"];
|
|
224
|
-
}
|
|
225
|
-
if (normalized.includes("5.1") && normalized.includes("codex") && normalized.includes("max")) {
|
|
226
|
-
return CODEX_MODEL_PRICING["gpt-5.1-codex-max"];
|
|
227
|
-
}
|
|
228
|
-
if (normalized.includes("5.1") && normalized.includes("codex")) {
|
|
229
|
-
return CODEX_MODEL_PRICING["gpt-5.1-codex"];
|
|
230
|
-
}
|
|
231
|
-
if (normalized.includes("5.1") && normalized.includes("max")) {
|
|
232
|
-
return CODEX_MODEL_PRICING["gpt-5.1"];
|
|
233
|
-
}
|
|
234
|
-
if (normalized.includes("5.1") && normalized.includes("mini")) {
|
|
235
|
-
return CODEX_MODEL_PRICING["gpt-5.1-codex-mini"];
|
|
236
|
-
}
|
|
237
|
-
if (normalized.includes("5.1")) {
|
|
238
|
-
return CODEX_MODEL_PRICING["gpt-5.1"];
|
|
239
|
-
}
|
|
240
|
-
if (normalized.includes("nano")) {
|
|
241
|
-
return CODEX_MODEL_PRICING["gpt-5-nano"];
|
|
242
|
-
}
|
|
243
|
-
if (normalized.includes("5") && normalized.includes("mini")) {
|
|
244
|
-
return CODEX_MODEL_PRICING["gpt-5-mini"];
|
|
245
|
-
}
|
|
246
|
-
if (normalized.includes("gpt-5") || normalized.includes("gpt5")) {
|
|
247
|
-
return CODEX_MODEL_PRICING["gpt-5"];
|
|
248
|
-
}
|
|
249
|
-
if (normalized.includes("4o-mini")) {
|
|
250
|
-
return CODEX_MODEL_PRICING["gpt-4o-mini"];
|
|
251
|
-
}
|
|
252
|
-
if (normalized.includes("4o")) {
|
|
253
|
-
return CODEX_MODEL_PRICING["gpt-4o"];
|
|
254
|
-
}
|
|
255
|
-
return CODEX_MODEL_PRICING["gpt-5"];
|
|
256
|
-
}
|
|
257
|
-
function getCodexModelDisplayName(modelName) {
|
|
258
|
-
const normalized = modelName.toLowerCase();
|
|
259
|
-
if (normalized.includes("5.3") && normalized.includes("spark")) return "GPT-5.3 Codex Spark";
|
|
260
|
-
if (normalized.includes("5.3") && normalized.includes("codex")) return "GPT-5.3 Codex";
|
|
261
|
-
if (normalized.includes("5.3")) return "GPT-5.3";
|
|
262
|
-
if (normalized.includes("5.2") && normalized.includes("codex")) return "GPT-5.2 Codex";
|
|
263
|
-
if (normalized.includes("5.2")) return "GPT-5.2";
|
|
264
|
-
if (normalized.includes("5.1") && normalized.includes("codex") && normalized.includes("max")) return "GPT-5.1 Codex Max";
|
|
265
|
-
if (normalized.includes("5.1") && normalized.includes("codex") && normalized.includes("mini")) return "GPT-5.1 Codex Mini";
|
|
266
|
-
if (normalized.includes("5.1") && normalized.includes("max")) return "GPT-5.1 Max";
|
|
267
|
-
if (normalized.includes("5.1") && normalized.includes("mini")) return "GPT-5.1 Mini";
|
|
268
|
-
if (normalized.includes("5.1") && normalized.includes("codex")) return "GPT-5.1 Codex";
|
|
269
|
-
if (normalized.includes("5.1")) return "GPT-5.1";
|
|
270
|
-
if (normalized.includes("nano")) return "GPT-5 Nano";
|
|
271
|
-
if (normalized.includes("5") && normalized.includes("mini")) return "GPT-5 Mini";
|
|
272
|
-
if (normalized.includes("5") && normalized.includes("codex")) return "GPT-5 Codex";
|
|
273
|
-
if (normalized.includes("gpt-5")) return "GPT-5";
|
|
274
|
-
if (normalized.includes("4o-mini")) return "GPT-4o Mini";
|
|
275
|
-
if (normalized.includes("4o")) return "GPT-4o";
|
|
276
|
-
return modelName;
|
|
277
|
-
}
|
|
278
|
-
function calculateCodexCost(modelName, inputTokens, outputTokens, cachedInputTokens) {
|
|
279
|
-
const pricing = getCodexModelPricing(modelName);
|
|
280
|
-
const regularInputTokens = inputTokens - cachedInputTokens;
|
|
281
|
-
const inputCost = regularInputTokens / 1e6 * pricing.input;
|
|
282
|
-
const cachedCost = cachedInputTokens / 1e6 * pricing.cachedInput;
|
|
283
|
-
const outputCost = outputTokens / 1e6 * pricing.output;
|
|
284
|
-
return inputCost + cachedCost + outputCost;
|
|
775
|
+
return modelName;
|
|
285
776
|
}
|
|
286
777
|
|
|
287
778
|
// src/usage/loader.ts
|
|
@@ -404,6 +895,7 @@ async function parseClaudeJsonl(projectFilter) {
|
|
|
404
895
|
cacheWriteTokens,
|
|
405
896
|
cacheReadTokens,
|
|
406
897
|
cost,
|
|
898
|
+
messageCount: 1,
|
|
407
899
|
source: "claude",
|
|
408
900
|
sessionId,
|
|
409
901
|
timestamp
|
|
@@ -460,7 +952,10 @@ async function parseCodexJsonl() {
|
|
|
460
952
|
cacheWriteTokens: 0,
|
|
461
953
|
cacheReadTokens: cachedInputTokens,
|
|
462
954
|
cost,
|
|
463
|
-
|
|
955
|
+
messageCount: 1,
|
|
956
|
+
source: "codex",
|
|
957
|
+
sessionId: basename(filePath, ".jsonl"),
|
|
958
|
+
timestamp
|
|
464
959
|
});
|
|
465
960
|
}
|
|
466
961
|
} catch {
|
|
@@ -479,36 +974,22 @@ function filterByDateRange(entries, since, until) {
|
|
|
479
974
|
});
|
|
480
975
|
}
|
|
481
976
|
function sortModelsByTier(models) {
|
|
482
|
-
const
|
|
977
|
+
const claudeTierPriority = {
|
|
483
978
|
"Opus 4.6": 101,
|
|
484
979
|
"Opus 4.5": 100,
|
|
485
980
|
"Opus 4.1": 99,
|
|
486
981
|
"Opus": 98,
|
|
982
|
+
"Sonnet 4.6": 91,
|
|
487
983
|
"Sonnet 4.5": 90,
|
|
488
984
|
"Sonnet 3.5": 89,
|
|
489
985
|
"Sonnet": 88,
|
|
490
986
|
"Haiku 4.5": 80,
|
|
491
987
|
"Haiku 3.5": 79,
|
|
492
|
-
"Haiku": 78
|
|
493
|
-
"GPT-5.3 Codex Spark": 76,
|
|
494
|
-
"GPT-5.3 Codex": 75,
|
|
495
|
-
"GPT-5.3": 74,
|
|
496
|
-
"GPT-5.2 Codex": 73,
|
|
497
|
-
"GPT-5.2": 72,
|
|
498
|
-
"GPT-5.1 Codex Max": 71,
|
|
499
|
-
"GPT-5.1 Max": 70,
|
|
500
|
-
"GPT-5.1 Codex Mini": 69,
|
|
501
|
-
"GPT-5.1 Mini": 68,
|
|
502
|
-
"GPT-5.1 Codex": 67,
|
|
503
|
-
"GPT-5.1": 66,
|
|
504
|
-
"GPT-5": 64,
|
|
505
|
-
"GPT-5 Mini": 63,
|
|
506
|
-
"GPT-4o": 60,
|
|
507
|
-
"GPT-4o Mini": 59
|
|
988
|
+
"Haiku": 78
|
|
508
989
|
};
|
|
509
990
|
return models.sort((a, b) => {
|
|
510
|
-
const priorityA =
|
|
511
|
-
const priorityB =
|
|
991
|
+
const priorityA = claudeTierPriority[a] ?? getCodexModelSortPriority(a);
|
|
992
|
+
const priorityB = claudeTierPriority[b] ?? getCodexModelSortPriority(b);
|
|
512
993
|
return priorityB - priorityA;
|
|
513
994
|
});
|
|
514
995
|
}
|
|
@@ -675,6 +1156,76 @@ function computeModelBreakdown(entries) {
|
|
|
675
1156
|
percentage: totalTokens > 0 ? Math.round(data.tokens / totalTokens * 100) : 0
|
|
676
1157
|
})).sort((a, b) => b.tokens - a.tokens);
|
|
677
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
|
+
}
|
|
678
1229
|
async function loadUsageStats(options) {
|
|
679
1230
|
const { aggregation, since, until, codexOnly, combined, projectFilter } = options;
|
|
680
1231
|
let entries = [];
|
|
@@ -730,6 +1281,28 @@ async function loadUsageStats(options) {
|
|
|
730
1281
|
modelBreakdown
|
|
731
1282
|
};
|
|
732
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
|
+
}
|
|
1301
|
+
let source = "claude";
|
|
1302
|
+
if (codexOnly) source = "codex";
|
|
1303
|
+
else if (combined) source = "combined";
|
|
1304
|
+
return computeActivityStats(entries, source);
|
|
1305
|
+
}
|
|
733
1306
|
|
|
734
1307
|
// src/usage/table.ts
|
|
735
1308
|
var colors = {
|
|
@@ -1601,200 +2174,6 @@ function combineWrappedStats(claude, codex) {
|
|
|
1601
2174
|
};
|
|
1602
2175
|
}
|
|
1603
2176
|
|
|
1604
|
-
// src/url-encoder.ts
|
|
1605
|
-
function formatCompactNumber(num) {
|
|
1606
|
-
if (num >= 1e9) {
|
|
1607
|
-
return `${(num / 1e9).toFixed(1)}B`;
|
|
1608
|
-
}
|
|
1609
|
-
if (num >= 1e6) {
|
|
1610
|
-
return `${(num / 1e6).toFixed(1)}M`;
|
|
1611
|
-
}
|
|
1612
|
-
if (num >= 1e3) {
|
|
1613
|
-
return `${(num / 1e3).toFixed(1)}K`;
|
|
1614
|
-
}
|
|
1615
|
-
return num.toString();
|
|
1616
|
-
}
|
|
1617
|
-
var dayToNumber = {
|
|
1618
|
-
Sunday: 0,
|
|
1619
|
-
Monday: 1,
|
|
1620
|
-
Tuesday: 2,
|
|
1621
|
-
Wednesday: 3,
|
|
1622
|
-
Thursday: 4,
|
|
1623
|
-
Friday: 5,
|
|
1624
|
-
Saturday: 6
|
|
1625
|
-
};
|
|
1626
|
-
function encodeStatsToUrl(stats, baseUrl = "https://vibestats.wolfai.dev") {
|
|
1627
|
-
const params = new URLSearchParams();
|
|
1628
|
-
params.set("s", stats.sessions.toString());
|
|
1629
|
-
params.set("t", formatCompactNumber(stats.totalTokens));
|
|
1630
|
-
params.set("c", stats.totalCost.toFixed(2));
|
|
1631
|
-
params.set("d", stats.daysActive.toString());
|
|
1632
|
-
params.set("ls", stats.longestStreak.toString());
|
|
1633
|
-
params.set("cs", stats.currentStreak.toString());
|
|
1634
|
-
params.set("ph", stats.peakHour.toString());
|
|
1635
|
-
params.set("pd", (dayToNumber[stats.peakDay] ?? 0).toString());
|
|
1636
|
-
params.set("fm", getModelAbbrevFromDisplayName(stats.favoriteModel));
|
|
1637
|
-
const mbParts = stats.modelBreakdown.slice(0, 3).map((m) => {
|
|
1638
|
-
const abbr = getModelAbbrevFromDisplayName(m.model);
|
|
1639
|
-
return `${abbr}:${m.percentage}`;
|
|
1640
|
-
});
|
|
1641
|
-
params.set("mb", mbParts.join(","));
|
|
1642
|
-
if (stats.topTools && stats.topTools.length > 0) {
|
|
1643
|
-
params.set("tt", stats.topTools.slice(0, 5).join(","));
|
|
1644
|
-
}
|
|
1645
|
-
if (stats.developerStyle) {
|
|
1646
|
-
const styleMap = {
|
|
1647
|
-
reader: "r",
|
|
1648
|
-
writer: "w",
|
|
1649
|
-
executor: "e",
|
|
1650
|
-
balanced: "b"
|
|
1651
|
-
};
|
|
1652
|
-
params.set("st", styleMap[stats.developerStyle] || "b");
|
|
1653
|
-
}
|
|
1654
|
-
if (stats.topProject) {
|
|
1655
|
-
params.set("tp", stats.topProject);
|
|
1656
|
-
}
|
|
1657
|
-
if (stats.projectCount) {
|
|
1658
|
-
params.set("pc", stats.projectCount.toString());
|
|
1659
|
-
}
|
|
1660
|
-
params.set("wg", formatCompactNumber(stats.wordsGenerated));
|
|
1661
|
-
const firstDate = new Date(stats.firstSessionDate);
|
|
1662
|
-
params.set("fad", firstDate.toISOString().split("T")[0].replace(/-/g, ""));
|
|
1663
|
-
if (stats.source && stats.source !== "claude") {
|
|
1664
|
-
params.set("src", stats.source);
|
|
1665
|
-
}
|
|
1666
|
-
return `${baseUrl}/wrapped/?${params.toString()}`;
|
|
1667
|
-
}
|
|
1668
|
-
function getModelAbbrevFromDisplayName(displayName) {
|
|
1669
|
-
const map = {
|
|
1670
|
-
// Claude models
|
|
1671
|
-
"Opus 4.5": "o45",
|
|
1672
|
-
"Opus 4.1": "o41",
|
|
1673
|
-
Opus: "opus",
|
|
1674
|
-
"Sonnet 4.5": "s45",
|
|
1675
|
-
"Sonnet 3.5": "s35",
|
|
1676
|
-
Sonnet: "sonnet",
|
|
1677
|
-
"Haiku 4.5": "h45",
|
|
1678
|
-
"Haiku 3.5": "h35",
|
|
1679
|
-
Haiku: "haiku",
|
|
1680
|
-
// Codex/OpenAI models
|
|
1681
|
-
"GPT-5.3 Codex Spark": "g53s",
|
|
1682
|
-
"GPT-5.3 Codex": "g53c",
|
|
1683
|
-
"GPT-5.3": "g53",
|
|
1684
|
-
"GPT-5.2 Codex": "g52c",
|
|
1685
|
-
"GPT-5.2": "g52",
|
|
1686
|
-
"GPT-5.1 Codex Max": "g51cm",
|
|
1687
|
-
"GPT-5.1 Max": "g51m",
|
|
1688
|
-
"GPT-5.1 Codex Mini": "g51cn",
|
|
1689
|
-
"GPT-5.1 Mini": "g51n",
|
|
1690
|
-
"GPT-5.1 Codex": "g51c",
|
|
1691
|
-
"GPT-5.1": "g51",
|
|
1692
|
-
"GPT-5": "g5",
|
|
1693
|
-
"GPT-5 Mini": "g5n",
|
|
1694
|
-
"GPT-4o": "g4o",
|
|
1695
|
-
"GPT-4o Mini": "g4om"
|
|
1696
|
-
};
|
|
1697
|
-
if (map[displayName]) return map[displayName];
|
|
1698
|
-
const normalized = displayName.toLowerCase();
|
|
1699
|
-
if (normalized.includes("5.3") && normalized.includes("spark")) return "g53s";
|
|
1700
|
-
if (normalized.includes("5.3") && normalized.includes("codex")) return "g53c";
|
|
1701
|
-
if (normalized.includes("5.2")) return "g52";
|
|
1702
|
-
if (normalized.includes("5.1") && normalized.includes("codex") && normalized.includes("max")) return "g51cm";
|
|
1703
|
-
if (normalized.includes("5.1") && normalized.includes("max")) return "g51m";
|
|
1704
|
-
if (normalized.includes("5.1") && normalized.includes("codex") && normalized.includes("mini")) return "g51cn";
|
|
1705
|
-
if (normalized.includes("5.1") && normalized.includes("codex")) return "g51c";
|
|
1706
|
-
if (normalized.includes("5.1") && normalized.includes("mini")) return "g51n";
|
|
1707
|
-
if (normalized.includes("5.1")) return "g51";
|
|
1708
|
-
if (normalized.includes("5") && normalized.includes("mini")) return "g5n";
|
|
1709
|
-
if (normalized.includes("gpt-5") || normalized.includes("gpt5")) return "g5";
|
|
1710
|
-
if (normalized.includes("4o") && normalized.includes("mini")) return "g4om";
|
|
1711
|
-
if (normalized.includes("4o")) return "g4o";
|
|
1712
|
-
if (normalized.includes("opus") && normalized.includes("4.5")) return "o45";
|
|
1713
|
-
if (normalized.includes("opus") && normalized.includes("4.1")) return "o41";
|
|
1714
|
-
if (normalized.includes("opus")) return "opus";
|
|
1715
|
-
if (normalized.includes("sonnet") && normalized.includes("4.5")) return "s45";
|
|
1716
|
-
if (normalized.includes("sonnet") && normalized.includes("3.5")) return "s35";
|
|
1717
|
-
if (normalized.includes("sonnet")) return "sonnet";
|
|
1718
|
-
if (normalized.includes("haiku") && normalized.includes("4.5")) return "h45";
|
|
1719
|
-
if (normalized.includes("haiku") && normalized.includes("3.5")) return "h35";
|
|
1720
|
-
if (normalized.includes("haiku")) return "haiku";
|
|
1721
|
-
return displayName.slice(0, 5).toLowerCase();
|
|
1722
|
-
}
|
|
1723
|
-
function aggregateRowsToMonthly(rows) {
|
|
1724
|
-
const monthMap = /* @__PURE__ */ new Map();
|
|
1725
|
-
for (const row of rows) {
|
|
1726
|
-
const month = row.key.slice(0, 7);
|
|
1727
|
-
const existing = monthMap.get(month);
|
|
1728
|
-
if (existing) {
|
|
1729
|
-
existing.inputTokens += row.inputTokens;
|
|
1730
|
-
existing.outputTokens += row.outputTokens;
|
|
1731
|
-
existing.cacheWriteTokens += row.cacheWriteTokens;
|
|
1732
|
-
existing.cacheReadTokens += row.cacheReadTokens;
|
|
1733
|
-
existing.totalTokens += row.totalTokens;
|
|
1734
|
-
existing.cost += row.cost;
|
|
1735
|
-
} else {
|
|
1736
|
-
monthMap.set(month, { ...row, key: month });
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
return Array.from(monthMap.values()).sort((a, b) => a.key.localeCompare(b.key));
|
|
1740
|
-
}
|
|
1741
|
-
function encodeUsageToUrl(stats, baseUrl = "https://vibestats.wolfai.dev") {
|
|
1742
|
-
const params = new URLSearchParams();
|
|
1743
|
-
if (stats.source !== "claude") {
|
|
1744
|
-
params.set("src", stats.source);
|
|
1745
|
-
}
|
|
1746
|
-
const formatDateCompact = (d) => d.replace(/-/g, "");
|
|
1747
|
-
const startMs = new Date(stats.dateRange.start).getTime();
|
|
1748
|
-
const endMs = new Date(stats.dateRange.end).getTime();
|
|
1749
|
-
const daySpan = Math.ceil((endMs - startMs) / (1e3 * 60 * 60 * 24));
|
|
1750
|
-
const useMonthly = stats.aggregation === "daily" && daySpan > 31;
|
|
1751
|
-
const aggMap = { daily: "d", monthly: "m", model: "mo", total: "t" };
|
|
1752
|
-
const effectiveAgg = useMonthly ? "monthly" : stats.aggregation;
|
|
1753
|
-
params.set("agg", aggMap[effectiveAgg] || "d");
|
|
1754
|
-
let rowsToEncode;
|
|
1755
|
-
if (useMonthly) {
|
|
1756
|
-
rowsToEncode = aggregateRowsToMonthly(stats.rows);
|
|
1757
|
-
} else {
|
|
1758
|
-
rowsToEncode = stats.rows.slice(-31);
|
|
1759
|
-
}
|
|
1760
|
-
const startDate = useMonthly ? stats.dateRange.start : rowsToEncode[0]?.key || stats.dateRange.start;
|
|
1761
|
-
const endDate = useMonthly ? stats.dateRange.end : rowsToEncode[rowsToEncode.length - 1]?.key || stats.dateRange.end;
|
|
1762
|
-
params.set("dr", `${formatDateCompact(startDate)}-${formatDateCompact(endDate)}`);
|
|
1763
|
-
const rows = rowsToEncode.map((row) => {
|
|
1764
|
-
let key = row.key;
|
|
1765
|
-
if (effectiveAgg === "daily" && row.key.length === 10) {
|
|
1766
|
-
key = row.key.slice(5).replace("-", "");
|
|
1767
|
-
}
|
|
1768
|
-
return [
|
|
1769
|
-
key,
|
|
1770
|
-
formatCompactNumber(row.inputTokens),
|
|
1771
|
-
formatCompactNumber(row.outputTokens),
|
|
1772
|
-
formatCompactNumber(row.cacheWriteTokens),
|
|
1773
|
-
formatCompactNumber(row.cacheReadTokens),
|
|
1774
|
-
formatCompactNumber(row.totalTokens),
|
|
1775
|
-
row.cost.toFixed(2)
|
|
1776
|
-
].join(":");
|
|
1777
|
-
});
|
|
1778
|
-
params.set("rows", rows.join("|"));
|
|
1779
|
-
const t = stats.totals;
|
|
1780
|
-
params.set("tot", [
|
|
1781
|
-
formatCompactNumber(t.inputTokens),
|
|
1782
|
-
formatCompactNumber(t.outputTokens),
|
|
1783
|
-
formatCompactNumber(t.cacheWriteTokens),
|
|
1784
|
-
formatCompactNumber(t.cacheReadTokens),
|
|
1785
|
-
formatCompactNumber(t.totalTokens),
|
|
1786
|
-
t.cost.toFixed(2)
|
|
1787
|
-
].join(":"));
|
|
1788
|
-
if (stats.modelBreakdown.length > 0) {
|
|
1789
|
-
const mb = stats.modelBreakdown.slice(0, 5).map((m) => {
|
|
1790
|
-
const abbr = getModelAbbrevFromDisplayName(m.model);
|
|
1791
|
-
return `${abbr}:${m.percentage}:${m.cost.toFixed(2)}`;
|
|
1792
|
-
});
|
|
1793
|
-
params.set("mb", mb.join(","));
|
|
1794
|
-
}
|
|
1795
|
-
return `${baseUrl}?${params.toString()}`;
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
2177
|
// src/display.ts
|
|
1799
2178
|
var colors2 = {
|
|
1800
2179
|
reset: "\x1B[0m",
|
|
@@ -1893,27 +2272,72 @@ function formatHour(hour) {
|
|
|
1893
2272
|
if (hour === 12) return "12pm";
|
|
1894
2273
|
return `${hour - 12}pm`;
|
|
1895
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
|
+
}
|
|
1896
2309
|
|
|
1897
|
-
// src/
|
|
1898
|
-
|
|
1899
|
-
|
|
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) {
|
|
1900
2318
|
try {
|
|
1901
|
-
const apiUrl = new URL("/
|
|
2319
|
+
const apiUrl = new URL("/vibestats/shares", baseUrl);
|
|
1902
2320
|
const response = await fetch(apiUrl.toString(), {
|
|
1903
2321
|
method: "POST",
|
|
1904
2322
|
headers: {
|
|
1905
2323
|
"Content-Type": "application/json"
|
|
1906
2324
|
},
|
|
1907
|
-
body: JSON.stringify({
|
|
2325
|
+
body: JSON.stringify({
|
|
2326
|
+
artifact,
|
|
2327
|
+
legacyQuery: extractLegacyQuery(legacyUrl)
|
|
2328
|
+
})
|
|
1908
2329
|
});
|
|
1909
2330
|
if (!response.ok) {
|
|
1910
2331
|
return null;
|
|
1911
2332
|
}
|
|
1912
2333
|
const data = await response.json();
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
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
|
+
};
|
|
1917
2341
|
} catch {
|
|
1918
2342
|
return null;
|
|
1919
2343
|
}
|
|
@@ -2067,6 +2491,65 @@ function parseLastDaysFlag(args) {
|
|
|
2067
2491
|
shorthandDays.sort((a, b) => a - b);
|
|
2068
2492
|
return shorthandDays[0];
|
|
2069
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
|
+
}
|
|
2070
2553
|
var main = defineCommand({
|
|
2071
2554
|
meta: {
|
|
2072
2555
|
name: "vibestats",
|
|
@@ -2081,6 +2564,11 @@ var main = defineCommand({
|
|
|
2081
2564
|
description: "Show annual wrapped summary instead of usage stats",
|
|
2082
2565
|
default: false
|
|
2083
2566
|
},
|
|
2567
|
+
activity: {
|
|
2568
|
+
type: "boolean",
|
|
2569
|
+
description: "Show the GitHub-style activity graph summary",
|
|
2570
|
+
default: false
|
|
2571
|
+
},
|
|
2084
2572
|
// Data source
|
|
2085
2573
|
codex: {
|
|
2086
2574
|
type: "boolean",
|
|
@@ -2147,6 +2635,14 @@ var main = defineCommand({
|
|
|
2147
2635
|
description: "Use compact table format (hide cache columns)",
|
|
2148
2636
|
default: false
|
|
2149
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
|
+
},
|
|
2150
2646
|
// Share option for usage mode
|
|
2151
2647
|
share: {
|
|
2152
2648
|
type: "boolean",
|
|
@@ -2199,7 +2695,9 @@ var main = defineCommand({
|
|
|
2199
2695
|
console.log(JSON.stringify(config, null, 2));
|
|
2200
2696
|
return;
|
|
2201
2697
|
}
|
|
2202
|
-
if (args.
|
|
2698
|
+
if (args.activity) {
|
|
2699
|
+
await runActivity(args, config);
|
|
2700
|
+
} else if (args.wrapped) {
|
|
2203
2701
|
await runWrapped(args, config);
|
|
2204
2702
|
} else {
|
|
2205
2703
|
await runUsage(args, config);
|
|
@@ -2270,10 +2768,13 @@ async function runUsage(args, config) {
|
|
|
2270
2768
|
let shareUrl = null;
|
|
2271
2769
|
if (args.share) {
|
|
2272
2770
|
const fullUrl = encodeUsageToUrl(stats, baseUrl);
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2771
|
+
const shared = await publishArtifactWithFallback(
|
|
2772
|
+
buildUsageArtifact(stats),
|
|
2773
|
+
baseUrl,
|
|
2774
|
+
fullUrl,
|
|
2775
|
+
Boolean(args["no-short"])
|
|
2776
|
+
);
|
|
2777
|
+
shareUrl = shared.shareUrl;
|
|
2277
2778
|
}
|
|
2278
2779
|
if (args.quiet && args.share && shareUrl) {
|
|
2279
2780
|
console.log(shareUrl);
|
|
@@ -2306,9 +2807,14 @@ async function runUsage(args, config) {
|
|
|
2306
2807
|
}
|
|
2307
2808
|
async function runWrapped(args, config) {
|
|
2308
2809
|
const options = resolveOptions(args, config);
|
|
2810
|
+
const metric = parseActivityMetric(args.metric);
|
|
2811
|
+
const days = parseActivityDays(args.days);
|
|
2309
2812
|
const spinner = createSpinner("Preparing wrapped...");
|
|
2310
|
-
const data = await spinner.whilePromise(
|
|
2311
|
-
|
|
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
|
+
])
|
|
2312
2818
|
);
|
|
2313
2819
|
validateData(data, { codexOnly: args.codex, combined: args.combined });
|
|
2314
2820
|
let claudeStats = null;
|
|
@@ -2327,23 +2833,89 @@ async function runWrapped(args, config) {
|
|
|
2327
2833
|
} else {
|
|
2328
2834
|
stats = claudeStats;
|
|
2329
2835
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
if (!args["no-short"]) {
|
|
2333
|
-
shortUrl = await createShortlink(url, options.baseUrl);
|
|
2836
|
+
if (activityStats) {
|
|
2837
|
+
stats.activity = buildActivityGraph(activityStats, metric, days);
|
|
2334
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
|
+
);
|
|
2335
2846
|
if (options.outputFormat === "json") {
|
|
2336
|
-
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
|
+
);
|
|
2337
2859
|
} else if (options.outputFormat === "quiet") {
|
|
2338
|
-
console.log(
|
|
2860
|
+
console.log(published.shareUrl);
|
|
2339
2861
|
} else {
|
|
2340
|
-
displayWrappedStats(stats,
|
|
2862
|
+
displayWrappedStats(stats, published.canonicalUrl, {
|
|
2341
2863
|
theme: options.theme,
|
|
2342
2864
|
hideCost: options.hideCost,
|
|
2343
|
-
shortUrl
|
|
2865
|
+
shortUrl: published.shareUrl === published.canonicalUrl ? null : published.shareUrl,
|
|
2866
|
+
imageUrl: published.imageUrl
|
|
2344
2867
|
});
|
|
2345
2868
|
}
|
|
2346
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
|
+
}
|
|
2347
2919
|
function formatNumber2(n) {
|
|
2348
2920
|
if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
|
|
2349
2921
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|