ccusage 15.1.0 → 15.3.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/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  <a href="https://npmjs.com/package/ccusage"><img src="https://img.shields.io/npm/v/ccusage?color=yellow" alt="npm version" /></a>
8
8
  <a href="https://tanstack.com/stats/npm?packageGroups=%5B%7B%22packages%22:%5B%7B%22name%22:%22ccusage%22%7D%5D%7D%5D&range=30-days&transform=none&binType=daily&showDataMode=all&height=400"><img src="https://img.shields.io/npm/dy/ccusage" alt="NPM Downloads" /></a>
9
9
  <a href="https://packagephobia.com/result?p=ccusage"><img src="https://packagephobia.com/badge?p=ccusage" alt="install size" /></a>
10
+ <a href="https://deepwiki.com/ryoppippi/ccusage"><img src="https://img.shields.io/badge/DeepWiki-ryoppippi%2Fccusage-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==" alt="DeepWiki"></a>
11
+ <!-- DeepWiki badge generated by https://deepwiki.ryoppippi.com/ -->
10
12
  <a href="https://github.com/hesreallyhim/awesome-claude-code"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Claude Code" /></a>
11
13
  </p>
12
14
 
@@ -18,8 +20,29 @@
18
20
 
19
21
  ## Installation
20
22
 
23
+ ### Quick Start (Recommended)
24
+
25
+ Thanks to ccusage's incredibly small bundle size ([![install size](https://packagephobia.com/badge?p=ccusage)](https://packagephobia.com/result?p=ccusage)), you can run it directly without installation:
26
+
27
+ ```bash
28
+ # Using bunx (recommended for speed)
29
+ bunx ccusage
30
+
31
+ # Using npx
32
+ npx ccusage@latest
33
+
34
+ # Using deno (with security flags)
35
+ deno run -E -R=$HOME/.claude/projects/ -S=homedir -N='raw.githubusercontent.com:443' npm:ccusage@latest
36
+ ```
37
+
38
+ > 💡 **Tip**: We recommend using `bunx` instead of `npx` for a massive speed improvement!
39
+
40
+ ### Local Installation (Optional)
41
+
42
+ Since ccusage has such a small bundle size, installation is entirely optional:
43
+
21
44
  ```bash
22
- npm i -g ccusage
45
+ npm install -g ccusage
23
46
  ```
24
47
 
25
48
  ## Usage
@@ -60,13 +83,24 @@ ccusage daily --breakdown # Per-model cost breakdown
60
83
  - 🔄 **Cache Token Support**: Tracks and displays cache creation and cache read tokens separately
61
84
  - 🌐 **Offline Mode**: Use pre-cached pricing data without network connectivity with `--offline` (Claude models only)
62
85
  - 🔌 **MCP Integration**: Built-in Model Context Protocol server for integration with other tools
86
+ - 🚀 **Ultra-Small Bundle**: Unlike other CLI tools, we pay extreme attention to bundle size - incredibly small even without minification!
63
87
 
64
88
  ## Documentation
65
89
 
66
- Full documentation is available at **[ccusage.ryoppippi.com](https://ccusage.ryoppippi.com/)**
90
+ Full documentation is available at **[ccusage.com](https://ccusage.com/)**
67
91
 
68
92
  ## Sponsors
69
93
 
94
+ ### Featured Sponsor
95
+
96
+ Check out these [47 Claude Code ProTips from Greg Baugues.](https://www.youtube.com/watch?v=TiNpzxoBPz0&lc=UgyVgQyOhfJJlheVMcB4AaABAg)
97
+
98
+ <p align="center">
99
+ <a href="https://www.youtube.com/watch?v=TiNpzxoBPz0&lc=UgyVgQyOhfJJlheVMcB4AaABAg">
100
+ <img src="https://cdn.jsdelivr.net/gh/ryoppippi/ccusage@main/docs/public/claude_code_protips_thumbnail_v1.png" alt="47 Claude Code ProTips from Greg Baugues" width="600">
101
+ </a>
102
+ </p>
103
+
70
104
  <p align="center">
71
105
  <a href="https://github.com/sponsors/ryoppippi">
72
106
  <img src="https://cdn.jsdelivr.net/gh/ryoppippi/sponsors@main/sponsors.svg">
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Calculates the total number of tokens across all token types
3
+ * Supports both raw usage data format and aggregated data format
4
+ * @param tokenCounts - Object containing counts for each token type
5
+ * @returns Total number of tokens
6
+ */
7
+ function getTotalTokens(tokenCounts) {
8
+ const cacheCreation = "cacheCreationInputTokens" in tokenCounts ? tokenCounts.cacheCreationInputTokens : tokenCounts.cacheCreationTokens;
9
+ const cacheRead = "cacheReadInputTokens" in tokenCounts ? tokenCounts.cacheReadInputTokens : tokenCounts.cacheReadTokens;
10
+ return tokenCounts.inputTokens + tokenCounts.outputTokens + cacheCreation + cacheRead;
11
+ }
12
+ export { getTotalTokens };
@@ -725,7 +725,7 @@ const ipv6CidrRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1
725
725
  const base64Regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
726
726
  const base64urlRegex = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/;
727
727
  const dateRegexSource = `((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))`;
728
- const dateRegex = new RegExp(`^${dateRegexSource}$`);
728
+ const dateRegex = /* @__PURE__ */ new RegExp(`^${dateRegexSource}$`);
729
729
  function timeRegexSource(args) {
730
730
  let secondsRegexSource = `[0-5]\\d`;
731
731
  if (args.precision) secondsRegexSource = `${secondsRegexSource}\\.\\d{${args.precision}}`;
@@ -734,7 +734,7 @@ function timeRegexSource(args) {
734
734
  return `([01]\\d|2[0-3]):[0-5]\\d(:${secondsRegexSource})${secondsQuantifier}`;
735
735
  }
736
736
  function timeRegex(args) {
737
- return new RegExp(`^${timeRegexSource(args)}$`);
737
+ return /* @__PURE__ */ new RegExp(`^${timeRegexSource(args)}$`);
738
738
  }
739
739
  function datetimeRegex(args) {
740
740
  let regex = `${dateRegexSource}T${timeRegexSource(args)}`;
@@ -742,7 +742,7 @@ function datetimeRegex(args) {
742
742
  opts.push(args.local ? `Z?` : `Z`);
743
743
  if (args.offset) opts.push(`([+-]\\d{2}:?\\d{2})`);
744
744
  regex = `${regex}(${opts.join("|")})`;
745
- return new RegExp(`^${regex}$`);
745
+ return /* @__PURE__ */ new RegExp(`^${regex}$`);
746
746
  }
747
747
  function isValidIP(ip, version) {
748
748
  if ((version === "v4" || !version) && ipv4Regex.test(ip)) return true;
@@ -3581,11 +3581,6 @@ const createDailyDate = (value) => dailyDateSchema.parse(value);
3581
3581
  const createMonthlyDate = (value) => monthlyDateSchema.parse(value);
3582
3582
  const createProjectPath = (value) => projectPathSchema.parse(value);
3583
3583
  /**
3584
- * Legacy schema for backward compatibility
3585
- * @deprecated Use filterDateSchema instead for better type safety
3586
- */
3587
- const dateSchema = stringType().regex(/^\d{8}$/, "Date must be in YYYYMMDD format");
3588
- /**
3589
3584
  * Available cost calculation modes
3590
3585
  * - auto: Use pre-calculated costs when available, otherwise calculate from tokens
3591
3586
  * - calculate: Always calculate costs from token counts using model pricing
@@ -3609,4 +3604,4 @@ const modelPricingSchema = objectType({
3609
3604
  cache_creation_input_token_cost: numberType().optional(),
3610
3605
  cache_read_input_token_cost: numberType().optional()
3611
3606
  });
3612
- export { CostModes, SortOrders, ZodFirstPartyTypeKind, ZodOptional, ZodType, activityDateSchema, arrayType, booleanType, createDailyDate, createMonthlyDate, createProjectPath, createSessionId, dailyDateSchema, dateSchema, discriminatedUnionType, enumType, isoTimestampSchema, literalType, messageIdSchema, modelNameSchema, modelPricingSchema, monthlyDateSchema, numberType, objectType, optionalType, projectPathSchema, recordType, requestIdSchema, sessionIdSchema, stringType, unionType, unknownType, versionSchema };
3607
+ export { CostModes, SortOrders, ZodFirstPartyTypeKind, ZodOptional, ZodType, activityDateSchema, arrayType, booleanType, createDailyDate, createMonthlyDate, createProjectPath, createSessionId, dailyDateSchema, discriminatedUnionType, enumType, filterDateSchema, isoTimestampSchema, literalType, messageIdSchema, modelNameSchema, modelPricingSchema, monthlyDateSchema, numberType, objectType, optionalType, projectPathSchema, recordType, requestIdSchema, sessionIdSchema, stringType, unionType, unknownType, versionSchema };
@@ -1,3 +1,4 @@
1
+ import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
1
2
  /**
2
3
  * Calculates total token usage and cost across multiple usage data entries
3
4
  * @param data - Array of daily, monthly, or session usage data
@@ -19,26 +20,14 @@ function calculateTotals(data) {
19
20
  });
20
21
  }
21
22
  /**
22
- * Calculates the sum of all token types (input, output, cache creation, cache read)
23
- * @param tokens - Token data containing different token counts
24
- * @returns Total number of tokens across all types
25
- */
26
- function getTotalTokens(tokens) {
27
- return tokens.inputTokens + tokens.outputTokens + tokens.cacheCreationTokens + tokens.cacheReadTokens;
28
- }
29
- /**
30
23
  * Creates a complete totals object by adding total token count to existing totals
31
24
  * @param totals - Token totals with cost information
32
25
  * @returns Complete totals object including total token sum
33
26
  */
34
27
  function createTotalsObject(totals) {
35
28
  return {
36
- inputTokens: totals.inputTokens,
37
- outputTokens: totals.outputTokens,
38
- cacheCreationTokens: totals.cacheCreationTokens,
39
- cacheReadTokens: totals.cacheReadTokens,
40
- totalTokens: getTotalTokens(totals),
41
- totalCost: totals.totalCost
29
+ ...totals,
30
+ totalTokens: getTotalTokens(totals)
42
31
  };
43
32
  }
44
- export { calculateTotals, createTotalsObject, getTotalTokens };
33
+ export { calculateTotals, createTotalsObject };
@@ -1,17 +1,51 @@
1
- import { DailyUsage, MonthlyUsage, SessionUsage } from "./data-loader-DZczD-9E.js";
2
- import "./pricing-fetcher-BZe7AafW.js";
1
+ import { DailyUsage, MonthlyUsage, SessionUsage } from "./data-loader-a9CiVyT5.js";
2
+ import "./pricing-fetcher-B3SvKOod.js";
3
3
 
4
- //#region src/calculate-cost.d.ts
4
+ //#region src/_token-utils.d.ts
5
5
 
6
6
  /**
7
- * Token usage data structure containing input, output, and cache token counts
7
+ * @fileoverview Token calculation utilities
8
+ *
9
+ * This module provides shared utilities for calculating token totals
10
+ * across different token types. Used throughout the application to
11
+ * ensure consistent token counting logic.
12
+ */
13
+ /**
14
+ * Token counts structure for raw usage data (uses InputTokens suffix)
15
+ */
16
+ type TokenCounts = {
17
+ inputTokens: number;
18
+ outputTokens: number;
19
+ cacheCreationInputTokens: number;
20
+ cacheReadInputTokens: number;
21
+ };
22
+ /**
23
+ * Token counts structure for aggregated data (uses shorter names)
8
24
  */
9
- type TokenData = {
25
+ type AggregatedTokenCounts = {
10
26
  inputTokens: number;
11
27
  outputTokens: number;
12
28
  cacheCreationTokens: number;
13
29
  cacheReadTokens: number;
14
30
  };
31
+ /**
32
+ * Union type that supports both token count formats
33
+ */
34
+ type AnyTokenCounts = TokenCounts | AggregatedTokenCounts;
35
+ /**
36
+ * Calculates the total number of tokens across all token types
37
+ * Supports both raw usage data format and aggregated data format
38
+ * @param tokenCounts - Object containing counts for each token type
39
+ * @returns Total number of tokens
40
+ */
41
+ declare function getTotalTokens(tokenCounts: AnyTokenCounts): number;
42
+ //#endregion
43
+ //#region src/calculate-cost.d.ts
44
+ /**
45
+ * Alias for AggregatedTokenCounts from shared utilities
46
+ * @deprecated Use AggregatedTokenCounts from _token-utils.ts instead
47
+ */
48
+ type TokenData = AggregatedTokenCounts;
15
49
  /**
16
50
  * Token totals including cost information
17
51
  */
@@ -30,12 +64,8 @@ type TotalsObject = TokenTotals & {
30
64
  * @returns Aggregated token totals and cost
31
65
  */
32
66
  declare function calculateTotals(data: Array<DailyUsage | MonthlyUsage | SessionUsage>): TokenTotals;
33
- /**
34
- * Calculates the sum of all token types (input, output, cache creation, cache read)
35
- * @param tokens - Token data containing different token counts
36
- * @returns Total number of tokens across all types
37
- */
38
- declare function getTotalTokens(tokens: TokenData): number;
67
+ // Re-export getTotalTokens from shared utilities for backward compatibility
68
+
39
69
  /**
40
70
  * Creates a complete totals object by adding total token count to existing totals
41
71
  * @param totals - Token totals with cost information
@@ -1,3 +1,4 @@
1
- import "./_types-Cr2YEzKm.js";
2
- import { calculateTotals, createTotalsObject, getTotalTokens } from "./calculate-cost-CoS7we68.js";
1
+ import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
2
+ import "./_types-BHFM59hI.js";
3
+ import { calculateTotals, createTotalsObject } from "./calculate-cost-BDqO4yWA.js";
3
4
  export { calculateTotals, createTotalsObject, getTotalTokens };
@@ -1,11 +1,12 @@
1
- import { CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, DEFAULT_RECENT_DAYS, PricingFetcher, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, require_usingCtx } from "./pricing-fetcher-Dm8hcn_h.js";
2
- import { activityDateSchema, arrayType, createDailyDate, createMonthlyDate, createProjectPath, createSessionId, dailyDateSchema, isoTimestampSchema, messageIdSchema, modelNameSchema, monthlyDateSchema, numberType, objectType, projectPathSchema, requestIdSchema, sessionIdSchema, versionSchema } from "./_types-Cr2YEzKm.js";
3
- import { logger } from "./logger-Cke8hliP.js";
1
+ import { CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, DEFAULT_RECENT_DAYS, PricingFetcher, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, isFailure, isPromise, require_usingCtx } from "./pricing-fetcher-DaK2jizg.js";
2
+ import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
3
+ import { activityDateSchema, arrayType, booleanType, createDailyDate, createMonthlyDate, createProjectPath, createSessionId, dailyDateSchema, isoTimestampSchema, messageIdSchema, modelNameSchema, monthlyDateSchema, numberType, objectType, projectPathSchema, requestIdSchema, sessionIdSchema, stringType, versionSchema } from "./_types-BHFM59hI.js";
4
+ import { logger } from "./logger-DeTONwj8.js";
4
5
  import a, { readFile } from "node:fs/promises";
5
- import F, { homedir } from "node:os";
6
6
  import path, { posix } from "node:path";
7
7
  import process$1 from "node:process";
8
8
  import b from "node:fs";
9
+ import F from "node:os";
9
10
  function toArray(array) {
10
11
  array = array ?? [];
11
12
  return Array.isArray(array) ? array : [array];
@@ -240,6 +241,35 @@ function isObject$1(x) {
240
241
  */ function unreachable(...args) {
241
242
  throw new UnreachableError(args);
242
243
  }
244
+ const isResult = (result) => "object" == typeof result && null !== result && "type" in result && ("Success" === result.type && "value" in result || "Failure" === result.type && "error" in result);
245
+ const unwrap = (...args) => {
246
+ const firstArgument = args[0];
247
+ if (isResult(firstArgument) || isPromise(firstArgument)) {
248
+ const result = firstArgument;
249
+ const hasDefault$1 = 2 === args.length;
250
+ const defaultValue$1 = hasDefault$1 ? args[1] : void 0;
251
+ const apply = (r) => {
252
+ if (isFailure(r)) {
253
+ if (hasDefault$1) return defaultValue$1;
254
+ throw new Error(String(r.error));
255
+ }
256
+ return r.value;
257
+ };
258
+ return isPromise(result) ? result.then(apply) : apply(result);
259
+ }
260
+ const hasDefault = 1 === args.length;
261
+ const defaultValue = hasDefault ? args[0] : void 0;
262
+ return (result) => {
263
+ const apply = (r) => {
264
+ if (isFailure(r)) {
265
+ if (hasDefault) return defaultValue;
266
+ throw new Error(String(r.error));
267
+ }
268
+ return r.value;
269
+ };
270
+ return isPromise(result) ? result.then(apply) : apply(result);
271
+ };
272
+ };
243
273
  function groupBy(arr, getKeyFromItem) {
244
274
  const result = {};
245
275
  for (let i = 0; i < arr.length; i++) {
@@ -3139,12 +3169,14 @@ function createBlock(startTime, entries, now, sessionDurationMs) {
3139
3169
  };
3140
3170
  let costUSD = 0;
3141
3171
  const models = [];
3172
+ let usageLimitResetTime;
3142
3173
  for (const entry of entries) {
3143
3174
  tokenCounts.inputTokens += entry.usage.inputTokens;
3144
3175
  tokenCounts.outputTokens += entry.usage.outputTokens;
3145
3176
  tokenCounts.cacheCreationInputTokens += entry.usage.cacheCreationInputTokens;
3146
3177
  tokenCounts.cacheReadInputTokens += entry.usage.cacheReadInputTokens;
3147
3178
  costUSD += entry.costUSD ?? 0;
3179
+ usageLimitResetTime = entry.usageLimitResetTime ?? usageLimitResetTime;
3148
3180
  models.push(entry.model);
3149
3181
  }
3150
3182
  return {
@@ -3156,7 +3188,8 @@ function createBlock(startTime, entries, now, sessionDurationMs) {
3156
3188
  entries,
3157
3189
  tokenCounts,
3158
3190
  costUSD,
3159
- models: uniq(models)
3191
+ models: uniq(models),
3192
+ usageLimitResetTime
3160
3193
  };
3161
3194
  }
3162
3195
  /**
@@ -3202,7 +3235,7 @@ function calculateBurnRate(block) {
3202
3235
  const lastEntry = lastEntryData.timestamp;
3203
3236
  const durationMinutes = (lastEntry.getTime() - firstEntry.getTime()) / (1e3 * 60);
3204
3237
  if (durationMinutes <= 0) return null;
3205
- const totalTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
3238
+ const totalTokens = getTotalTokens(block.tokenCounts);
3206
3239
  const tokensPerMinute = totalTokens / durationMinutes;
3207
3240
  const costPerHour = block.costUSD / durationMinutes * 60;
3208
3241
  return {
@@ -3222,7 +3255,7 @@ function projectBlockUsage(block) {
3222
3255
  const now = /* @__PURE__ */ new Date();
3223
3256
  const remainingTime = block.endTime.getTime() - now.getTime();
3224
3257
  const remainingMinutes = Math.max(0, remainingTime / (1e3 * 60));
3225
- const currentTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
3258
+ const currentTokens = getTotalTokens(block.tokenCounts);
3226
3259
  const projectedAdditionalTokens = burnRate.tokensPerMinute * remainingMinutes;
3227
3260
  const totalTokens = currentTokens + projectedAdditionalTokens;
3228
3261
  const projectedAdditionalCost = burnRate.costPerHour / 60 * remainingMinutes;
@@ -3241,7 +3274,7 @@ function projectBlockUsage(block) {
3241
3274
  */
3242
3275
  function filterRecentBlocks(blocks, days = DEFAULT_RECENT_DAYS) {
3243
3276
  const now = /* @__PURE__ */ new Date();
3244
- const cutoffTime = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
3277
+ const cutoffTime = /* @__PURE__ */ new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
3245
3278
  return blocks.filter((block) => {
3246
3279
  return block.startTime >= cutoffTime || block.isActive;
3247
3280
  });
@@ -3291,26 +3324,6 @@ function getClaudePaths() {
3291
3324
  return paths;
3292
3325
  }
3293
3326
  /**
3294
- * Default path for Claude data directory
3295
- * Uses environment variable CLAUDE_CONFIG_DIR if set, otherwise defaults to ~/.claude
3296
- * @deprecated Use getClaudePaths() instead for multiple path support
3297
- */
3298
- function getDefaultClaudePath() {
3299
- const envPath = (process$1.env[CLAUDE_CONFIG_DIR_ENV] ?? "").trim();
3300
- if (envPath !== "") {
3301
- const firstPath = envPath.split(",")[0]?.trim();
3302
- if (firstPath != null && firstPath !== "") {
3303
- if (!isDirectorySync(firstPath)) throw new Error(`CLAUDE_CONFIG_DIR path is not a valid directory: ${firstPath}`);
3304
- return firstPath;
3305
- }
3306
- }
3307
- const newDefaultPath = DEFAULT_CLAUDE_CONFIG_PATH;
3308
- if (isDirectorySync(newDefaultPath)) return newDefaultPath;
3309
- const oldDefaultPath = path.join(USER_HOME_DIR, DEFAULT_CLAUDE_CODE_PATH);
3310
- if (isDirectorySync(oldDefaultPath)) return oldDefaultPath;
3311
- return oldDefaultPath;
3312
- }
3313
- /**
3314
3327
  * Zod schema for validating Claude usage data from JSONL files
3315
3328
  */
3316
3329
  const usageDataSchema = objectType({
@@ -3324,10 +3337,12 @@ const usageDataSchema = objectType({
3324
3337
  cache_read_input_tokens: numberType().optional()
3325
3338
  }),
3326
3339
  model: modelNameSchema.optional(),
3327
- id: messageIdSchema.optional()
3340
+ id: messageIdSchema.optional(),
3341
+ content: arrayType(objectType({ text: stringType().optional() })).optional()
3328
3342
  }),
3329
3343
  costUSD: numberType().optional(),
3330
- requestId: requestIdSchema.optional()
3344
+ requestId: requestIdSchema.optional(),
3345
+ isApiErrorMessage: booleanType().optional()
3331
3346
  });
3332
3347
  /**
3333
3348
  * Zod schema for model-specific usage breakdown data
@@ -3602,17 +3617,33 @@ async function sortFilesByTimestamp(files) {
3602
3617
  async function calculateCostForEntry(data, mode, fetcher) {
3603
3618
  if (mode === "display") return data.costUSD ?? 0;
3604
3619
  if (mode === "calculate") {
3605
- if (data.message.model != null) return fetcher.calculateCostFromTokens(data.message.usage, data.message.model);
3620
+ if (data.message.model != null) return unwrap(fetcher.calculateCostFromTokens(data.message.usage, data.message.model), 0);
3606
3621
  return 0;
3607
3622
  }
3608
3623
  if (mode === "auto") {
3609
3624
  if (data.costUSD != null) return data.costUSD;
3610
- if (data.message.model != null) return fetcher.calculateCostFromTokens(data.message.usage, data.message.model);
3625
+ if (data.message.model != null) return unwrap(fetcher.calculateCostFromTokens(data.message.usage, data.message.model), 0);
3611
3626
  return 0;
3612
3627
  }
3613
3628
  unreachable(mode);
3614
3629
  }
3615
3630
  /**
3631
+ * Get Claude Code usage limit expiration date
3632
+ * @param data - Usage data entry
3633
+ * @returns Usage limit expiration date
3634
+ */
3635
+ function getUsageLimitResetTime(data) {
3636
+ let resetTime = null;
3637
+ if (data.isApiErrorMessage === true) {
3638
+ const timestampMatch = data.message?.content?.find((c) => c.text != null && c.text.includes("Claude AI usage limit reached"))?.text?.match(/\|(\d+)/) ?? null;
3639
+ if (timestampMatch?.[1] != null) {
3640
+ const resetTimestamp = Number.parseInt(timestampMatch[1]);
3641
+ resetTime = resetTimestamp > 0 ? /* @__PURE__ */ new Date(resetTimestamp * 1e3) : null;
3642
+ }
3643
+ }
3644
+ return resetTime;
3645
+ }
3646
+ /**
3616
3647
  * Loads and aggregates Claude usage data by day
3617
3648
  * Processes all JSONL files in the Claude projects directory and groups usage by date
3618
3649
  * @param options - Optional configuration for loading and filtering data
@@ -3849,6 +3880,7 @@ async function loadSessionBlockData(options) {
3849
3880
  if (isDuplicateEntry(uniqueHash, processedHashes)) continue;
3850
3881
  markAsProcessed(uniqueHash, processedHashes);
3851
3882
  const cost = fetcher != null ? await calculateCostForEntry(data, mode, fetcher) : data.costUSD ?? 0;
3883
+ const usageLimitResetTime = getUsageLimitResetTime(data);
3852
3884
  allEntries.push({
3853
3885
  timestamp: new Date(data.timestamp),
3854
3886
  usage: {
@@ -3859,7 +3891,8 @@ async function loadSessionBlockData(options) {
3859
3891
  },
3860
3892
  costUSD: cost,
3861
3893
  model: data.message.model ?? "unknown",
3862
- version: data.version
3894
+ version: data.version,
3895
+ usageLimitResetTime: usageLimitResetTime ?? void 0
3863
3896
  });
3864
3897
  } catch (error) {
3865
3898
  logger.debug(`Skipping invalid JSON line in 5-hour blocks: ${error instanceof Error ? error.message : String(error)}`);
@@ -3879,4 +3912,4 @@ async function loadSessionBlockData(options) {
3879
3912
  _usingCtx4.d();
3880
3913
  }
3881
3914
  }
3882
- export { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, dailyUsageSchema, filterRecentBlocks, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, glob, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, projectBlockUsage, sessionUsageSchema, sortFilesByTimestamp, uniq, usageDataSchema };
3915
+ export { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, dailyUsageSchema, filterRecentBlocks, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, glob, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, projectBlockUsage, sessionUsageSchema, sortFilesByTimestamp, uniq, unwrap, usageDataSchema };
@@ -1,4 +1,4 @@
1
- import { CostMode, PricingFetcher, SortOrder } from "./pricing-fetcher-BZe7AafW.js";
1
+ import { CostMode, PricingFetcher, SortOrder } from "./pricing-fetcher-B3SvKOod.js";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/_session-blocks.d.ts
@@ -17,6 +17,7 @@ type LoadedUsageEntry = {
17
17
  costUSD: number | null;
18
18
  model: string;
19
19
  version?: string;
20
+ usageLimitResetTime?: Date; // Claude API usage limit reset time
20
21
  };
21
22
  /**
22
23
  * Aggregated token counts for different token types
@@ -41,6 +42,7 @@ type SessionBlock = {
41
42
  tokenCounts: TokenCounts;
42
43
  costUSD: number;
43
44
  models: string[];
45
+ usageLimitResetTime?: Date; // Claude API usage limit reset time
44
46
  };
45
47
  /**
46
48
  * Represents usage burn rate calculations
@@ -53,12 +55,6 @@ type SessionBlock = {
53
55
  * @returns Array of valid Claude data directory paths
54
56
  */
55
57
  declare function getClaudePaths(): string[];
56
- /**
57
- * Default path for Claude data directory
58
- * Uses environment variable CLAUDE_CONFIG_DIR if set, otherwise defaults to ~/.claude
59
- * @deprecated Use getClaudePaths() instead for multiple path support
60
- */
61
- declare function getDefaultClaudePath(): string;
62
58
  /**
63
59
  * Zod schema for validating Claude usage data from JSONL files
64
60
  */
@@ -84,6 +80,13 @@ declare const usageDataSchema: z.ZodObject<{
84
80
  }>;
85
81
  model: z.ZodOptional<z.ZodBranded<z.ZodString, "ModelName">>;
86
82
  id: z.ZodOptional<z.ZodBranded<z.ZodString, "MessageId">>;
83
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
84
+ text: z.ZodOptional<z.ZodString>;
85
+ }, "strip", z.ZodTypeAny, {
86
+ text?: string | undefined;
87
+ }, {
88
+ text?: string | undefined;
89
+ }>, "many">>;
87
90
  }, "strip", z.ZodTypeAny, {
88
91
  usage: {
89
92
  input_tokens: number;
@@ -93,6 +96,9 @@ declare const usageDataSchema: z.ZodObject<{
93
96
  };
94
97
  model?: (string & z.BRAND<"ModelName">) | undefined;
95
98
  id?: (string & z.BRAND<"MessageId">) | undefined;
99
+ content?: {
100
+ text?: string | undefined;
101
+ }[] | undefined;
96
102
  }, {
97
103
  usage: {
98
104
  input_tokens: number;
@@ -102,9 +108,13 @@ declare const usageDataSchema: z.ZodObject<{
102
108
  };
103
109
  model?: string | undefined;
104
110
  id?: string | undefined;
111
+ content?: {
112
+ text?: string | undefined;
113
+ }[] | undefined;
105
114
  }>;
106
115
  costUSD: z.ZodOptional<z.ZodNumber>;
107
116
  requestId: z.ZodOptional<z.ZodBranded<z.ZodString, "RequestId">>;
117
+ isApiErrorMessage: z.ZodOptional<z.ZodBoolean>;
108
118
  }, "strip", z.ZodTypeAny, {
109
119
  timestamp: string & z.BRAND<"ISOTimestamp">;
110
120
  version?: (string & z.BRAND<"Version">) | undefined;
@@ -117,9 +127,13 @@ declare const usageDataSchema: z.ZodObject<{
117
127
  };
118
128
  model?: (string & z.BRAND<"ModelName">) | undefined;
119
129
  id?: (string & z.BRAND<"MessageId">) | undefined;
130
+ content?: {
131
+ text?: string | undefined;
132
+ }[] | undefined;
120
133
  };
121
134
  costUSD?: number | undefined;
122
135
  requestId?: (string & z.BRAND<"RequestId">) | undefined;
136
+ isApiErrorMessage?: boolean | undefined;
123
137
  }, {
124
138
  timestamp: string;
125
139
  version?: string | undefined;
@@ -132,9 +146,13 @@ declare const usageDataSchema: z.ZodObject<{
132
146
  };
133
147
  model?: string | undefined;
134
148
  id?: string | undefined;
149
+ content?: {
150
+ text?: string | undefined;
151
+ }[] | undefined;
135
152
  };
136
153
  costUSD?: number | undefined;
137
154
  requestId?: string | undefined;
155
+ isApiErrorMessage?: boolean | undefined;
138
156
  }>;
139
157
  /**
140
158
  * Type definition for Claude usage data entries from JSONL files
@@ -422,6 +440,12 @@ declare function sortFilesByTimestamp(files: string[]): Promise<string[]>;
422
440
  * @returns Calculated cost in USD
423
441
  */
424
442
  declare function calculateCostForEntry(data: UsageData, mode: CostMode, fetcher: PricingFetcher): Promise<number>;
443
+ /**
444
+ * Get Claude Code usage limit expiration date
445
+ * @param data - Usage data entry
446
+ * @returns Usage limit expiration date
447
+ */
448
+ declare function getUsageLimitResetTime(data: UsageData): Date | null;
425
449
  /**
426
450
  * Date range filter for limiting usage data by date
427
451
  */
@@ -468,4 +492,4 @@ declare function loadMonthlyUsageData(options?: LoadOptions): Promise<MonthlyUsa
468
492
  */
469
493
  declare function loadSessionBlockData(options?: LoadOptions): Promise<SessionBlock[]>;
470
494
  //#endregion
471
- export { DailyUsage, DateFilter, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
495
+ export { DailyUsage, DateFilter, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
@@ -1,3 +1,3 @@
1
- import { DailyUsage, DateFilter, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-DZczD-9E.js";
2
- import "./pricing-fetcher-BZe7AafW.js";
3
- export { DailyUsage, DateFilter, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
1
+ import { DailyUsage, DateFilter, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-a9CiVyT5.js";
2
+ import "./pricing-fetcher-B3SvKOod.js";
3
+ export { DailyUsage, DateFilter, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
@@ -1,5 +1,6 @@
1
- import "./pricing-fetcher-Dm8hcn_h.js";
2
- import "./_types-Cr2YEzKm.js";
3
- import { calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-BeaFK_sH.js";
4
- import "./logger-Cke8hliP.js";
5
- export { calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
1
+ import "./pricing-fetcher-DaK2jizg.js";
2
+ import "./_token-utils-WjkbrjKv.js";
3
+ import "./_types-BHFM59hI.js";
4
+ import { calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-R9pMvoQp.js";
5
+ import "./logger-DeTONwj8.js";
6
+ export { calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
@@ -1,6 +1,6 @@
1
- import { CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, PricingFetcher, USAGE_DATA_GLOB_PATTERN, __toESM, require_usingCtx } from "./pricing-fetcher-Dm8hcn_h.js";
2
- import { getDefaultClaudePath, glob, usageDataSchema } from "./data-loader-BeaFK_sH.js";
3
- import { logger } from "./logger-Cke8hliP.js";
1
+ import { CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, PricingFetcher, USAGE_DATA_GLOB_PATTERN, __toESM, isFailure, require_usingCtx, try_ } from "./pricing-fetcher-DaK2jizg.js";
2
+ import { getClaudePaths, glob, unwrap, usageDataSchema } from "./data-loader-R9pMvoQp.js";
3
+ import { logger } from "./logger-DeTONwj8.js";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  var import_usingCtx = __toESM(require_usingCtx(), 1);
@@ -13,7 +13,13 @@ var import_usingCtx = __toESM(require_usingCtx(), 1);
13
13
  async function detectMismatches(claudePath) {
14
14
  try {
15
15
  var _usingCtx = (0, import_usingCtx.default)();
16
- const claudeDir = claudePath ?? path.join(getDefaultClaudePath(), CLAUDE_PROJECTS_DIR_NAME);
16
+ let claudeDir;
17
+ if (claudePath != null && claudePath !== "") claudeDir = claudePath;
18
+ else {
19
+ const paths = getClaudePaths();
20
+ if (paths.length === 0) throw new Error("No valid Claude data directory found");
21
+ claudeDir = path.join(paths[0], CLAUDE_PROJECTS_DIR_NAME);
22
+ }
17
23
  const files = await glob([USAGE_DATA_GLOB_PATTERN], {
18
24
  cwd: claudeDir,
19
25
  absolute: true
@@ -31,16 +37,21 @@ async function detectMismatches(claudePath) {
31
37
  for (const file of files) {
32
38
  const content = await readFile(file, "utf-8");
33
39
  const lines = content.trim().split("\n").filter((line) => line.length > 0);
34
- for (const line of lines) try {
35
- const parsed = JSON.parse(line);
36
- const result = usageDataSchema.safeParse(parsed);
37
- if (!result.success) continue;
38
- const data = result.data;
40
+ for (const line of lines) {
41
+ const parseParser = try_({
42
+ try: () => JSON.parse(line),
43
+ catch: () => /* @__PURE__ */ new Error("Invalid JSON")
44
+ });
45
+ const parseResult = parseParser();
46
+ if (isFailure(parseResult)) continue;
47
+ const schemaResult = usageDataSchema.safeParse(parseResult.value);
48
+ if (!schemaResult.success) continue;
49
+ const data = schemaResult.data;
39
50
  stats.totalEntries++;
40
51
  if (data.costUSD !== void 0 && data.message.model != null && data.message.model !== "<synthetic>") {
41
52
  stats.entriesWithBoth++;
42
53
  const model = data.message.model;
43
- const calculatedCost = await fetcher.calculateCostFromTokens(data.message.usage, model);
54
+ const calculatedCost = await unwrap(fetcher.calculateCostFromTokens(data.message.usage, model));
44
55
  const difference = Math.abs(data.costUSD - calculatedCost);
45
56
  const percentDiff = data.costUSD > 0 ? difference / data.costUSD * 100 : 0;
46
57
  const modelStat = stats.modelStats.get(model) ?? {
@@ -83,7 +94,7 @@ async function detectMismatches(claudePath) {
83
94
  modelStat.avgPercentDiff = (modelStat.avgPercentDiff * (modelStat.total - 1) + percentDiff) / modelStat.total;
84
95
  stats.modelStats.set(model, modelStat);
85
96
  }
86
- } catch {}
97
+ }
87
98
  }
88
99
  return stats;
89
100
  } catch (_) {