vibestats 1.0.23 → 1.2.0
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 +131 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,10 +6,17 @@ import { defineCommand, runMain } from "citty";
|
|
|
6
6
|
// src/usage/loader.ts
|
|
7
7
|
import { promises as fs } from "fs";
|
|
8
8
|
import { homedir } from "os";
|
|
9
|
-
import { join } from "path";
|
|
9
|
+
import { join, basename } from "path";
|
|
10
10
|
|
|
11
11
|
// src/pricing.ts
|
|
12
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
|
+
},
|
|
13
20
|
// Opus 4.5 (cheaper than Opus 4.1)
|
|
14
21
|
"claude-opus-4-5-20251101": {
|
|
15
22
|
input: 5,
|
|
@@ -63,6 +70,9 @@ function getModelPricing(modelName) {
|
|
|
63
70
|
if (MODEL_PRICING[modelName]) {
|
|
64
71
|
return MODEL_PRICING[modelName];
|
|
65
72
|
}
|
|
73
|
+
if (modelName.includes("opus-4-6") || modelName.includes("opus-4.6")) {
|
|
74
|
+
return MODEL_PRICING["claude-opus-4-6-20260101"];
|
|
75
|
+
}
|
|
66
76
|
if (modelName.includes("opus-4-5") || modelName.includes("opus-4.5")) {
|
|
67
77
|
return MODEL_PRICING["claude-opus-4-5-20251101"];
|
|
68
78
|
}
|
|
@@ -84,6 +94,7 @@ function getModelPricing(modelName) {
|
|
|
84
94
|
return MODEL_PRICING["claude-sonnet-4-5-20250929"];
|
|
85
95
|
}
|
|
86
96
|
function getModelDisplayName(modelName) {
|
|
97
|
+
if (modelName.includes("opus-4-6") || modelName.includes("opus-4.6")) return "Opus 4.6";
|
|
87
98
|
if (modelName.includes("opus-4-5") || modelName.includes("opus-4.5")) return "Opus 4.5";
|
|
88
99
|
if (modelName.includes("opus-4-1") || modelName.includes("opus-4.1")) return "Opus 4.1";
|
|
89
100
|
if (modelName.includes("opus")) return "Opus";
|
|
@@ -98,7 +109,18 @@ function getModelDisplayName(modelName) {
|
|
|
98
109
|
|
|
99
110
|
// src/codex-pricing.ts
|
|
100
111
|
var CODEX_MODEL_PRICING = {
|
|
101
|
-
// GPT-5.
|
|
112
|
+
// GPT-5.3 (latest flagship, assumed same pricing as GPT-5.2)
|
|
113
|
+
"gpt-5.3": {
|
|
114
|
+
input: 1.75,
|
|
115
|
+
output: 14,
|
|
116
|
+
cachedInput: 0.175
|
|
117
|
+
},
|
|
118
|
+
"gpt-5.3-codex": {
|
|
119
|
+
input: 1.75,
|
|
120
|
+
output: 14,
|
|
121
|
+
cachedInput: 0.175
|
|
122
|
+
},
|
|
123
|
+
// GPT-5.2
|
|
102
124
|
// Cached input is ~10% of input price (prompt caching discount)
|
|
103
125
|
"gpt-5.2": {
|
|
104
126
|
input: 1.75,
|
|
@@ -177,6 +199,9 @@ function getCodexModelPricing(modelName) {
|
|
|
177
199
|
return CODEX_MODEL_PRICING[modelName];
|
|
178
200
|
}
|
|
179
201
|
const normalized = modelName.toLowerCase();
|
|
202
|
+
if (normalized.includes("5.3")) {
|
|
203
|
+
return CODEX_MODEL_PRICING["gpt-5.3"];
|
|
204
|
+
}
|
|
180
205
|
if (normalized.includes("5.2")) {
|
|
181
206
|
return CODEX_MODEL_PRICING["gpt-5.2"];
|
|
182
207
|
}
|
|
@@ -208,6 +233,8 @@ function getCodexModelPricing(modelName) {
|
|
|
208
233
|
}
|
|
209
234
|
function getCodexModelDisplayName(modelName) {
|
|
210
235
|
const normalized = modelName.toLowerCase();
|
|
236
|
+
if (normalized.includes("5.3") && normalized.includes("pro")) return "GPT-5.3 Pro";
|
|
237
|
+
if (normalized.includes("5.3")) return "GPT-5.3";
|
|
211
238
|
if (normalized.includes("5.2") && normalized.includes("pro")) return "GPT-5.2 Pro";
|
|
212
239
|
if (normalized.includes("5.2")) return "GPT-5.2";
|
|
213
240
|
if (normalized.includes("5.1") && normalized.includes("max")) return "GPT-5.1 Max";
|
|
@@ -289,16 +316,38 @@ async function findJsonlFiles(dir, visited = /* @__PURE__ */ new Set(), depth =
|
|
|
289
316
|
}
|
|
290
317
|
return result;
|
|
291
318
|
}
|
|
292
|
-
async function
|
|
319
|
+
async function resolveProjectDir(projectsDir, cwd) {
|
|
320
|
+
let current = cwd;
|
|
321
|
+
while (current && current !== "/") {
|
|
322
|
+
const encoded = current.replace(/\//g, "-");
|
|
323
|
+
const candidate = join(projectsDir, encoded);
|
|
324
|
+
if (await pathExists(candidate)) return candidate;
|
|
325
|
+
const parent = join(current, "..");
|
|
326
|
+
if (parent === current) break;
|
|
327
|
+
current = parent;
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
async function parseClaudeJsonl(projectFilter) {
|
|
293
332
|
const entries = [];
|
|
294
333
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
295
334
|
const claudeDir = getClaudeDir();
|
|
296
335
|
const projectsDir = join(claudeDir, "projects");
|
|
297
336
|
if (!await pathExists(projectsDir)) return entries;
|
|
298
|
-
|
|
337
|
+
let searchDir;
|
|
338
|
+
if (projectFilter) {
|
|
339
|
+
const resolved = await resolveProjectDir(projectsDir, projectFilter);
|
|
340
|
+
if (!resolved) return entries;
|
|
341
|
+
searchDir = resolved;
|
|
342
|
+
} else {
|
|
343
|
+
searchDir = projectsDir;
|
|
344
|
+
}
|
|
345
|
+
const jsonlFiles = await findJsonlFiles(searchDir);
|
|
299
346
|
for (const filePath of jsonlFiles) {
|
|
347
|
+
const sessionId = basename(filePath, ".jsonl");
|
|
300
348
|
try {
|
|
301
349
|
const content = await fs.readFile(filePath, "utf-8");
|
|
350
|
+
if (!content.trim()) continue;
|
|
302
351
|
const lines = content.split("\n");
|
|
303
352
|
for (const line of lines) {
|
|
304
353
|
if (!line.trim()) continue;
|
|
@@ -329,7 +378,9 @@ async function parseClaudeJsonl() {
|
|
|
329
378
|
cacheWriteTokens,
|
|
330
379
|
cacheReadTokens,
|
|
331
380
|
cost,
|
|
332
|
-
source: "claude"
|
|
381
|
+
source: "claude",
|
|
382
|
+
sessionId,
|
|
383
|
+
timestamp
|
|
333
384
|
});
|
|
334
385
|
} catch {
|
|
335
386
|
}
|
|
@@ -403,6 +454,7 @@ function filterByDateRange(entries, since, until) {
|
|
|
403
454
|
}
|
|
404
455
|
function sortModelsByTier(models) {
|
|
405
456
|
const tierPriority = {
|
|
457
|
+
"Opus 4.6": 101,
|
|
406
458
|
"Opus 4.5": 100,
|
|
407
459
|
"Opus 4.1": 99,
|
|
408
460
|
"Opus": 98,
|
|
@@ -412,6 +464,7 @@ function sortModelsByTier(models) {
|
|
|
412
464
|
"Haiku 4.5": 80,
|
|
413
465
|
"Haiku 3.5": 79,
|
|
414
466
|
"Haiku": 78,
|
|
467
|
+
"GPT-5.3": 71,
|
|
415
468
|
"GPT-5.2": 70,
|
|
416
469
|
"GPT-5.1 Max": 69,
|
|
417
470
|
"GPT-5.1": 68,
|
|
@@ -513,6 +566,50 @@ function aggregateByModel(entries) {
|
|
|
513
566
|
}
|
|
514
567
|
return Array.from(modelMap.values()).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
515
568
|
}
|
|
569
|
+
function aggregateBySession(entries) {
|
|
570
|
+
const sessionMap = /* @__PURE__ */ new Map();
|
|
571
|
+
for (const e of entries) {
|
|
572
|
+
const sid = e.sessionId || "unknown";
|
|
573
|
+
const existing = sessionMap.get(sid);
|
|
574
|
+
if (existing) {
|
|
575
|
+
existing.inputTokens += e.inputTokens;
|
|
576
|
+
existing.outputTokens += e.outputTokens;
|
|
577
|
+
existing.cacheWriteTokens += e.cacheWriteTokens;
|
|
578
|
+
existing.cacheReadTokens += e.cacheReadTokens;
|
|
579
|
+
existing.totalTokens += e.inputTokens + e.outputTokens + e.cacheWriteTokens + e.cacheReadTokens;
|
|
580
|
+
existing.cost += e.cost;
|
|
581
|
+
existing.modelsSet.add(e.model);
|
|
582
|
+
if (e.timestamp && e.timestamp < existing.firstTimestamp) {
|
|
583
|
+
existing.firstTimestamp = e.timestamp;
|
|
584
|
+
}
|
|
585
|
+
} else {
|
|
586
|
+
sessionMap.set(sid, {
|
|
587
|
+
modelsSet: /* @__PURE__ */ new Set([e.model]),
|
|
588
|
+
inputTokens: e.inputTokens,
|
|
589
|
+
outputTokens: e.outputTokens,
|
|
590
|
+
cacheWriteTokens: e.cacheWriteTokens,
|
|
591
|
+
cacheReadTokens: e.cacheReadTokens,
|
|
592
|
+
totalTokens: e.inputTokens + e.outputTokens + e.cacheWriteTokens + e.cacheReadTokens,
|
|
593
|
+
cost: e.cost,
|
|
594
|
+
firstTimestamp: e.timestamp || ""
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return Array.from(sessionMap.entries()).filter(([, data]) => data.totalTokens > 0).map(([sessionId, data]) => {
|
|
599
|
+
const date = data.firstTimestamp ? toLocalDateString(data.firstTimestamp) : "unknown";
|
|
600
|
+
const shortId = sessionId.slice(0, 12);
|
|
601
|
+
return {
|
|
602
|
+
key: `${date} ${shortId}`,
|
|
603
|
+
inputTokens: data.inputTokens,
|
|
604
|
+
outputTokens: data.outputTokens,
|
|
605
|
+
cacheWriteTokens: data.cacheWriteTokens,
|
|
606
|
+
cacheReadTokens: data.cacheReadTokens,
|
|
607
|
+
totalTokens: data.totalTokens,
|
|
608
|
+
cost: data.cost,
|
|
609
|
+
models: sortModelsByTier(Array.from(data.modelsSet))
|
|
610
|
+
};
|
|
611
|
+
}).sort((a, b) => a.key.localeCompare(b.key));
|
|
612
|
+
}
|
|
516
613
|
function computeTotals(rows) {
|
|
517
614
|
return rows.reduce(
|
|
518
615
|
(acc, row) => ({
|
|
@@ -547,10 +644,10 @@ function computeModelBreakdown(entries) {
|
|
|
547
644
|
})).sort((a, b) => b.tokens - a.tokens);
|
|
548
645
|
}
|
|
549
646
|
async function loadUsageStats(options) {
|
|
550
|
-
const { aggregation, since, until, codexOnly, combined } = options;
|
|
647
|
+
const { aggregation, since, until, codexOnly, combined, projectFilter } = options;
|
|
551
648
|
let entries = [];
|
|
552
649
|
if (!codexOnly) {
|
|
553
|
-
entries = entries.concat(await parseClaudeJsonl());
|
|
650
|
+
entries = entries.concat(await parseClaudeJsonl(projectFilter));
|
|
554
651
|
}
|
|
555
652
|
if (codexOnly || combined) {
|
|
556
653
|
entries = entries.concat(await parseCodexJsonl());
|
|
@@ -576,6 +673,9 @@ async function loadUsageStats(options) {
|
|
|
576
673
|
case "model":
|
|
577
674
|
rows = aggregateByModel(entries);
|
|
578
675
|
break;
|
|
676
|
+
case "session":
|
|
677
|
+
rows = aggregateBySession(entries);
|
|
678
|
+
break;
|
|
579
679
|
case "total":
|
|
580
680
|
rows = [];
|
|
581
681
|
break;
|
|
@@ -644,13 +744,16 @@ function displayUsageTable(stats, options = {}) {
|
|
|
644
744
|
let title = "Claude Code Usage";
|
|
645
745
|
if (stats.source === "codex") title = "Codex CLI Usage";
|
|
646
746
|
else if (stats.source === "combined") title = "AI Coding Usage (Claude + Codex)";
|
|
647
|
-
const aggLabel = stats.aggregation === "daily" ? "Daily" : stats.aggregation === "monthly" ? "Monthly" : stats.aggregation === "model" ? "By Model" : "Total";
|
|
747
|
+
const aggLabel = stats.aggregation === "daily" ? "Daily" : stats.aggregation === "monthly" ? "Monthly" : stats.aggregation === "model" ? "By Model" : stats.aggregation === "session" ? "By Session" : "Total";
|
|
648
748
|
console.log(`${c.orange}${c.bold}${title} - ${aggLabel} Report${c.reset}`);
|
|
649
749
|
console.log(`${c.gray}Date range: ${stats.dateRange.start} to ${stats.dateRange.end}${c.reset}`);
|
|
650
750
|
console.log(`${c.gray}${"\u2500".repeat(Math.min(termWidth - 2, 120))}${c.reset}`);
|
|
651
751
|
console.log();
|
|
652
|
-
const
|
|
653
|
-
const
|
|
752
|
+
const isSession = stats.aggregation === "session";
|
|
753
|
+
const firstCol = isSession ? "Session" : "Date";
|
|
754
|
+
const firstWidth = isSession ? 24 : 12;
|
|
755
|
+
const headers = useCompact ? [firstCol, "Input", "Output", "Total", ...hideCost ? [] : ["Cost"]] : [firstCol, "", "Input", "Output", "Cache W", "Cache R", "Total", ...hideCost ? [] : ["Cost"]];
|
|
756
|
+
const widths = useCompact ? [firstWidth, 14, 12, 16, ...hideCost ? [] : [12]] : [firstWidth, 16, 14, 12, 14, 14, 16, ...hideCost ? [] : [12]];
|
|
654
757
|
const headerRow = headers.map((h, i) => padLeft(h, widths[i])).join(" ");
|
|
655
758
|
console.log(`${c.bold}${headerRow}${c.reset}`);
|
|
656
759
|
for (const row of stats.rows) {
|
|
@@ -659,7 +762,7 @@ function displayUsageTable(stats, options = {}) {
|
|
|
659
762
|
const remainingModels = models.slice(1);
|
|
660
763
|
if (useCompact) {
|
|
661
764
|
const cols = [
|
|
662
|
-
padRight(row.key.slice(0,
|
|
765
|
+
padRight(row.key.slice(0, firstWidth), widths[0]),
|
|
663
766
|
padLeft(formatNumber(row.inputTokens), widths[1]),
|
|
664
767
|
padLeft(formatNumber(row.outputTokens), widths[2]),
|
|
665
768
|
padLeft(formatNumber(row.totalTokens), widths[3]),
|
|
@@ -669,7 +772,7 @@ function displayUsageTable(stats, options = {}) {
|
|
|
669
772
|
} else {
|
|
670
773
|
const modelDisplay = firstModel ? `- ${firstModel}` : "";
|
|
671
774
|
const cols = [
|
|
672
|
-
padRight(row.key.slice(0,
|
|
775
|
+
padRight(row.key.slice(0, firstWidth), widths[0]),
|
|
673
776
|
padRight(modelDisplay.slice(0, 16), widths[1]),
|
|
674
777
|
padLeft(formatNumber(row.inputTokens), widths[2]),
|
|
675
778
|
padLeft(formatNumber(row.outputTokens), widths[3]),
|
|
@@ -1957,6 +2060,18 @@ var main = defineCommand({
|
|
|
1957
2060
|
description: "Generate a shareable URL (usage mode)",
|
|
1958
2061
|
default: false
|
|
1959
2062
|
},
|
|
2063
|
+
// Project and session filtering
|
|
2064
|
+
project: {
|
|
2065
|
+
type: "boolean",
|
|
2066
|
+
alias: "p",
|
|
2067
|
+
description: "Show stats for the current project only",
|
|
2068
|
+
default: false
|
|
2069
|
+
},
|
|
2070
|
+
sessions: {
|
|
2071
|
+
type: "boolean",
|
|
2072
|
+
description: "Show per-session breakdown",
|
|
2073
|
+
default: false
|
|
2074
|
+
},
|
|
1960
2075
|
// Wrapped-specific options
|
|
1961
2076
|
url: {
|
|
1962
2077
|
type: "string",
|
|
@@ -1999,7 +2114,8 @@ var main = defineCommand({
|
|
|
1999
2114
|
});
|
|
2000
2115
|
async function runUsage(args, config) {
|
|
2001
2116
|
let aggregation = "daily";
|
|
2002
|
-
if (args.
|
|
2117
|
+
if (args.sessions) aggregation = "session";
|
|
2118
|
+
else if (args.monthly) aggregation = "monthly";
|
|
2003
2119
|
else if (args.model) aggregation = "model";
|
|
2004
2120
|
else if (args.total) aggregation = "total";
|
|
2005
2121
|
const spinner = createSpinner("Loading vibestats...");
|
|
@@ -2009,7 +2125,8 @@ async function runUsage(args, config) {
|
|
|
2009
2125
|
since: args.since,
|
|
2010
2126
|
until: args.until,
|
|
2011
2127
|
codexOnly: args.codex,
|
|
2012
|
-
combined: args.combined
|
|
2128
|
+
combined: args.combined,
|
|
2129
|
+
projectFilter: args.project ? process.cwd() : void 0
|
|
2013
2130
|
})
|
|
2014
2131
|
);
|
|
2015
2132
|
if (!stats) {
|