vibestats 1.2.0 → 1.3.1
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/README.md +6 -1
- package/dist/index.js +140 -16
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -31,11 +31,16 @@ vibestats --wrapped # Annual wrapped summary
|
|
|
31
31
|
|------|-------------|
|
|
32
32
|
| `--monthly` | Aggregate by month |
|
|
33
33
|
| `--model` | Aggregate by model |
|
|
34
|
+
| `--sessions` | Aggregate by session |
|
|
34
35
|
| `--total` | Show only totals |
|
|
35
36
|
| `--since YYYY-MM-DD` | Filter from date |
|
|
36
37
|
| `--until YYYY-MM-DD` | Filter to date |
|
|
38
|
+
| `--last N` | Last N days (shorthand: `--last7`, `--last30`, etc.) |
|
|
37
39
|
| `--compact`, `-c` | Compact table (hide cache columns) |
|
|
38
40
|
| `--json` | Output raw JSON |
|
|
41
|
+
| `--quiet`, `-q` | Quiet output (totals line) |
|
|
42
|
+
| `--share`, `-s` | Generate a shareable usage URL |
|
|
43
|
+
| `--project`, `-p` | Current project only (Claude Code) |
|
|
39
44
|
|
|
40
45
|
### Wrapped Mode
|
|
41
46
|
|
|
@@ -59,7 +64,7 @@ vibestats --wrapped # Annual wrapped summary
|
|
|
59
64
|
|------|-------------|
|
|
60
65
|
| `--init` | Create config file |
|
|
61
66
|
| `--config` | Show current config |
|
|
62
|
-
| `--url <url>` | Custom base URL |
|
|
67
|
+
| `--url <url>` | Custom base URL for shareable links/pages |
|
|
63
68
|
|
|
64
69
|
## Config File
|
|
65
70
|
|
package/dist/index.js
CHANGED
|
@@ -109,12 +109,17 @@ function getModelDisplayName(modelName) {
|
|
|
109
109
|
|
|
110
110
|
// src/codex-pricing.ts
|
|
111
111
|
var CODEX_MODEL_PRICING = {
|
|
112
|
-
// GPT-5.3 (latest flagship
|
|
112
|
+
// GPT-5.3 (latest flagship)
|
|
113
113
|
"gpt-5.3": {
|
|
114
114
|
input: 1.75,
|
|
115
115
|
output: 14,
|
|
116
116
|
cachedInput: 0.175
|
|
117
117
|
},
|
|
118
|
+
"gpt-5.3-codex-spark": {
|
|
119
|
+
input: 1.75,
|
|
120
|
+
output: 14,
|
|
121
|
+
cachedInput: 0.175
|
|
122
|
+
},
|
|
118
123
|
"gpt-5.3-codex": {
|
|
119
124
|
input: 1.75,
|
|
120
125
|
output: 14,
|
|
@@ -199,15 +204,33 @@ function getCodexModelPricing(modelName) {
|
|
|
199
204
|
return CODEX_MODEL_PRICING[modelName];
|
|
200
205
|
}
|
|
201
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
|
+
}
|
|
202
213
|
if (normalized.includes("5.3")) {
|
|
203
214
|
return CODEX_MODEL_PRICING["gpt-5.3"];
|
|
204
215
|
}
|
|
216
|
+
if (normalized.includes("5.2") && normalized.includes("codex")) {
|
|
217
|
+
return CODEX_MODEL_PRICING["gpt-5.2-codex"];
|
|
218
|
+
}
|
|
205
219
|
if (normalized.includes("5.2")) {
|
|
206
220
|
return CODEX_MODEL_PRICING["gpt-5.2"];
|
|
207
221
|
}
|
|
208
|
-
if (normalized.includes("5.1") && normalized.includes("
|
|
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")) {
|
|
209
226
|
return CODEX_MODEL_PRICING["gpt-5.1-codex-max"];
|
|
210
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
|
+
}
|
|
211
234
|
if (normalized.includes("5.1") && normalized.includes("mini")) {
|
|
212
235
|
return CODEX_MODEL_PRICING["gpt-5.1-codex-mini"];
|
|
213
236
|
}
|
|
@@ -233,10 +256,13 @@ function getCodexModelPricing(modelName) {
|
|
|
233
256
|
}
|
|
234
257
|
function getCodexModelDisplayName(modelName) {
|
|
235
258
|
const normalized = modelName.toLowerCase();
|
|
236
|
-
if (normalized.includes("5.3") && normalized.includes("
|
|
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";
|
|
237
261
|
if (normalized.includes("5.3")) return "GPT-5.3";
|
|
238
|
-
if (normalized.includes("5.2") && normalized.includes("
|
|
262
|
+
if (normalized.includes("5.2") && normalized.includes("codex")) return "GPT-5.2 Codex";
|
|
239
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";
|
|
240
266
|
if (normalized.includes("5.1") && normalized.includes("max")) return "GPT-5.1 Max";
|
|
241
267
|
if (normalized.includes("5.1") && normalized.includes("mini")) return "GPT-5.1 Mini";
|
|
242
268
|
if (normalized.includes("5.1") && normalized.includes("codex")) return "GPT-5.1 Codex";
|
|
@@ -464,13 +490,19 @@ function sortModelsByTier(models) {
|
|
|
464
490
|
"Haiku 4.5": 80,
|
|
465
491
|
"Haiku 3.5": 79,
|
|
466
492
|
"Haiku": 78,
|
|
467
|
-
"GPT-5.3":
|
|
468
|
-
"GPT-5.
|
|
469
|
-
"GPT-5.
|
|
470
|
-
"GPT-5.
|
|
471
|
-
"GPT-5.
|
|
472
|
-
"GPT-5":
|
|
473
|
-
"GPT-5
|
|
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,
|
|
474
506
|
"GPT-4o": 60,
|
|
475
507
|
"GPT-4o Mini": 59
|
|
476
508
|
};
|
|
@@ -1646,9 +1678,16 @@ function getModelAbbrevFromDisplayName(displayName) {
|
|
|
1646
1678
|
"Haiku 3.5": "h35",
|
|
1647
1679
|
Haiku: "haiku",
|
|
1648
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",
|
|
1649
1685
|
"GPT-5.2": "g52",
|
|
1686
|
+
"GPT-5.1 Codex Max": "g51cm",
|
|
1650
1687
|
"GPT-5.1 Max": "g51m",
|
|
1688
|
+
"GPT-5.1 Codex Mini": "g51cn",
|
|
1651
1689
|
"GPT-5.1 Mini": "g51n",
|
|
1690
|
+
"GPT-5.1 Codex": "g51c",
|
|
1652
1691
|
"GPT-5.1": "g51",
|
|
1653
1692
|
"GPT-5": "g5",
|
|
1654
1693
|
"GPT-5 Mini": "g5n",
|
|
@@ -1657,8 +1696,13 @@ function getModelAbbrevFromDisplayName(displayName) {
|
|
|
1657
1696
|
};
|
|
1658
1697
|
if (map[displayName]) return map[displayName];
|
|
1659
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";
|
|
1660
1701
|
if (normalized.includes("5.2")) return "g52";
|
|
1702
|
+
if (normalized.includes("5.1") && normalized.includes("codex") && normalized.includes("max")) return "g51cm";
|
|
1661
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";
|
|
1662
1706
|
if (normalized.includes("5.1") && normalized.includes("mini")) return "g51n";
|
|
1663
1707
|
if (normalized.includes("5.1")) return "g51";
|
|
1664
1708
|
if (normalized.includes("5") && normalized.includes("mini")) return "g5n";
|
|
@@ -1978,10 +2022,55 @@ function createSpinner(label = "Loading vibestats...") {
|
|
|
1978
2022
|
};
|
|
1979
2023
|
return { whilePromise, stop };
|
|
1980
2024
|
}
|
|
2025
|
+
function formatLocalDateYYYYMMDD(date) {
|
|
2026
|
+
const year = date.getFullYear();
|
|
2027
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
2028
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
2029
|
+
return `${year}-${month}-${day}`;
|
|
2030
|
+
}
|
|
2031
|
+
function parseLocalDateYYYYMMDD(dateStr) {
|
|
2032
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateStr.trim());
|
|
2033
|
+
if (!m) return null;
|
|
2034
|
+
const year = Number.parseInt(m[1], 10);
|
|
2035
|
+
const month = Number.parseInt(m[2], 10);
|
|
2036
|
+
const day = Number.parseInt(m[3], 10);
|
|
2037
|
+
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return null;
|
|
2038
|
+
if (month < 1 || month > 12) return null;
|
|
2039
|
+
if (day < 1 || day > 31) return null;
|
|
2040
|
+
const date = new Date(year, month - 1, day, 12, 0, 0, 0);
|
|
2041
|
+
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) return null;
|
|
2042
|
+
return date;
|
|
2043
|
+
}
|
|
2044
|
+
function parseLastDaysFlag(args) {
|
|
2045
|
+
const parseDays = (value) => {
|
|
2046
|
+
if (typeof value === "number") {
|
|
2047
|
+
if (!Number.isInteger(value) || value <= 0) return null;
|
|
2048
|
+
return value;
|
|
2049
|
+
}
|
|
2050
|
+
if (typeof value === "string") {
|
|
2051
|
+
const m = /^(\d+)\s*d?$/.exec(value.trim());
|
|
2052
|
+
if (!m) return null;
|
|
2053
|
+
const days = Number.parseInt(m[1], 10);
|
|
2054
|
+
if (!Number.isFinite(days) || days <= 0) return null;
|
|
2055
|
+
return days;
|
|
2056
|
+
}
|
|
2057
|
+
return null;
|
|
2058
|
+
};
|
|
2059
|
+
const direct = parseDays(args.last);
|
|
2060
|
+
if (direct) return direct;
|
|
2061
|
+
const shorthandDays = Object.keys(args).map((k) => {
|
|
2062
|
+
const m = /^last(\d+)$/.exec(k);
|
|
2063
|
+
if (!m) return null;
|
|
2064
|
+
return Number.parseInt(m[1], 10);
|
|
2065
|
+
}).filter((n) => typeof n === "number" && Number.isFinite(n) && n > 0);
|
|
2066
|
+
if (shorthandDays.length === 0) return null;
|
|
2067
|
+
shorthandDays.sort((a, b) => a - b);
|
|
2068
|
+
return shorthandDays[0];
|
|
2069
|
+
}
|
|
1981
2070
|
var main = defineCommand({
|
|
1982
2071
|
meta: {
|
|
1983
2072
|
name: "vibestats",
|
|
1984
|
-
version: "1.
|
|
2073
|
+
version: "1.3.0",
|
|
1985
2074
|
description: "AI coding stats - usage tracking and annual wrapped for Claude Code & Codex"
|
|
1986
2075
|
},
|
|
1987
2076
|
args: {
|
|
@@ -2012,7 +2101,7 @@ var main = defineCommand({
|
|
|
2012
2101
|
quiet: {
|
|
2013
2102
|
type: "boolean",
|
|
2014
2103
|
alias: "q",
|
|
2015
|
-
description: "
|
|
2104
|
+
description: "Quiet mode (usage: totals line; wrapped: URL only)",
|
|
2016
2105
|
default: false
|
|
2017
2106
|
},
|
|
2018
2107
|
// Usage-specific options
|
|
@@ -2047,6 +2136,11 @@ var main = defineCommand({
|
|
|
2047
2136
|
type: "string",
|
|
2048
2137
|
description: "End date for filtering (YYYY-MM-DD)"
|
|
2049
2138
|
},
|
|
2139
|
+
last: {
|
|
2140
|
+
type: "string",
|
|
2141
|
+
alias: "l",
|
|
2142
|
+
description: "Show only the last N days (e.g. --last 7). Shorthand also supported: --last7, --last30, etc."
|
|
2143
|
+
},
|
|
2050
2144
|
compact: {
|
|
2051
2145
|
type: "boolean",
|
|
2052
2146
|
alias: "c",
|
|
@@ -2075,7 +2169,7 @@ var main = defineCommand({
|
|
|
2075
2169
|
// Wrapped-specific options
|
|
2076
2170
|
url: {
|
|
2077
2171
|
type: "string",
|
|
2078
|
-
description: "Custom base URL for
|
|
2172
|
+
description: "Custom base URL for shareable links/pages"
|
|
2079
2173
|
},
|
|
2080
2174
|
"no-short": {
|
|
2081
2175
|
type: "boolean",
|
|
@@ -2113,6 +2207,23 @@ var main = defineCommand({
|
|
|
2113
2207
|
}
|
|
2114
2208
|
});
|
|
2115
2209
|
async function runUsage(args, config) {
|
|
2210
|
+
const requestedSince = args.since;
|
|
2211
|
+
const requestedUntil = args.until;
|
|
2212
|
+
const requestedLastDays = parseLastDaysFlag(args);
|
|
2213
|
+
let since = requestedSince;
|
|
2214
|
+
let until = requestedUntil;
|
|
2215
|
+
if (requestedLastDays && !since) {
|
|
2216
|
+
const anchorStr = until || formatLocalDateYYYYMMDD(/* @__PURE__ */ new Date());
|
|
2217
|
+
const anchor = parseLocalDateYYYYMMDD(anchorStr);
|
|
2218
|
+
if (!anchor) {
|
|
2219
|
+
console.error(`Error: Invalid --until date "${anchorStr}". Expected YYYY-MM-DD.`);
|
|
2220
|
+
process.exit(1);
|
|
2221
|
+
}
|
|
2222
|
+
const start = new Date(anchor);
|
|
2223
|
+
start.setDate(start.getDate() - (requestedLastDays - 1));
|
|
2224
|
+
since = formatLocalDateYYYYMMDD(start);
|
|
2225
|
+
if (!until) until = anchorStr;
|
|
2226
|
+
}
|
|
2116
2227
|
let aggregation = "daily";
|
|
2117
2228
|
if (args.sessions) aggregation = "session";
|
|
2118
2229
|
else if (args.monthly) aggregation = "monthly";
|
|
@@ -2122,14 +2233,27 @@ async function runUsage(args, config) {
|
|
|
2122
2233
|
const stats = await spinner.whilePromise(
|
|
2123
2234
|
loadUsageStats({
|
|
2124
2235
|
aggregation,
|
|
2125
|
-
since
|
|
2126
|
-
until
|
|
2236
|
+
since,
|
|
2237
|
+
until,
|
|
2127
2238
|
codexOnly: args.codex,
|
|
2128
2239
|
combined: args.combined,
|
|
2129
2240
|
projectFilter: args.project ? process.cwd() : void 0
|
|
2130
2241
|
})
|
|
2131
2242
|
);
|
|
2132
2243
|
if (!stats) {
|
|
2244
|
+
if (requestedSince || requestedUntil || requestedLastDays) {
|
|
2245
|
+
const rangeStr = `${since || "(start)"} to ${until || "(end)"}`;
|
|
2246
|
+
console.error(`Error: No usage data found for date range ${rangeStr}.`);
|
|
2247
|
+
if (args.codex) {
|
|
2248
|
+
console.error("Checked: ~/.codex/sessions and ~/.codex/archived_sessions");
|
|
2249
|
+
} else if (args.combined) {
|
|
2250
|
+
console.error("Checked: ~/.claude/projects and ~/.codex/sessions (plus archived sessions)");
|
|
2251
|
+
} else {
|
|
2252
|
+
console.error("Checked: ~/.claude/projects");
|
|
2253
|
+
}
|
|
2254
|
+
console.error("Try widening the range or removing the date filter.");
|
|
2255
|
+
process.exit(1);
|
|
2256
|
+
}
|
|
2133
2257
|
if (args.codex) {
|
|
2134
2258
|
console.error("Error: OpenAI Codex data not found at ~/.codex");
|
|
2135
2259
|
console.error("Make sure you have used the Codex CLI at least once.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibestats",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "AI coding stats - usage tracking and annual wrapped for Claude Code & Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "tsx src/index.ts",
|
|
23
|
+
"build": "tsup src/index.ts --format esm --dts --clean --shims",
|
|
24
|
+
"prepublishOnly": "pnpm build"
|
|
25
|
+
},
|
|
21
26
|
"keywords": [
|
|
22
27
|
"claude",
|
|
23
28
|
"claude-code",
|
|
@@ -45,9 +50,5 @@
|
|
|
45
50
|
},
|
|
46
51
|
"engines": {
|
|
47
52
|
"node": ">=18.0.0"
|
|
48
|
-
},
|
|
49
|
-
"scripts": {
|
|
50
|
-
"dev": "tsx src/index.ts",
|
|
51
|
-
"build": "tsup src/index.ts --format esm --dts --clean --shims"
|
|
52
53
|
}
|
|
53
|
-
}
|
|
54
|
+
}
|