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.
Files changed (3) hide show
  1. package/README.md +6 -1
  2. package/dist/index.js +140 -16
  3. 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, assumed same pricing as GPT-5.2)
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("max")) {
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("pro")) return "GPT-5.3 Pro";
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("pro")) return "GPT-5.2 Pro";
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": 71,
468
- "GPT-5.2": 70,
469
- "GPT-5.1 Max": 69,
470
- "GPT-5.1": 68,
471
- "GPT-5.1 Mini": 67,
472
- "GPT-5": 66,
473
- "GPT-5 Mini": 65,
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.0.0",
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: "Only output the shareable URL (wrapped mode)",
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 the wrapped page"
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: args.since,
2126
- until: args.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.2.0",
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
+ }