ccusage 0.6.0 → 0.6.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 (43) hide show
  1. package/README.md +27 -19
  2. package/dist/calculate-cost.d.ts +11 -8
  3. package/dist/{core-BgFXUe_h.js → core-B0ovMhJe.js} +4 -4
  4. package/dist/{data-loader-CDv0IYZx.js → data-loader-DP5qBPn6.js} +112 -92
  5. package/dist/{data-loader-aUOjeZ06.d.ts → data-loader-VdEcqJHc.d.ts} +15 -11
  6. package/dist/data-loader.d.ts +2 -4
  7. package/dist/data-loader.js +4 -4
  8. package/dist/{debug-Dk36WQTw.js → debug-C_5Qx11m.js} +13 -13
  9. package/dist/debug.d.ts +4 -4
  10. package/dist/debug.js +5 -5
  11. package/dist/{dist-FwNhpFrW.js → dist-C0-Tf5eD.js} +1 -92
  12. package/dist/{dist-C_i5I27w.js → dist-LwbOR2Yw.js} +5 -5
  13. package/dist/{effect-WSjEuzC9-BsxP11fz.js → effect-WSjEuzC9-CJfWUy0j.js} +1 -1
  14. package/dist/{esm-vjyZjnpZ.js → esm-Dqsc1zmX.js} +1 -1
  15. package/dist/{index-CISmcbXk-BotItq1T.js → index-CISmcbXk-DCA05NUL.js} +5 -5
  16. package/dist/index.js +117 -42
  17. package/dist/{logger-DhDyJEC5.js → logger-DsQC4OvA.js} +17 -17
  18. package/dist/logger.js +1 -1
  19. package/dist/{mcp-G-TIOcuj.js → mcp-BQdv12mr.js} +82 -73
  20. package/dist/mcp.d.ts +2 -4
  21. package/dist/mcp.js +7 -8
  22. package/dist/{pricing-fetcher-BCv1Vods.js → pricing-fetcher-BPUgMrB_.js} +9 -9
  23. package/dist/{index-BurjgCfW.d.ts → pricing-fetcher-CfEgfzSr.d.ts} +19 -249
  24. package/dist/pricing-fetcher.d.ts +1 -2
  25. package/dist/pricing-fetcher.js +3 -3
  26. package/dist/{prompt-IToGuko2.js → prompt-DljZqwMa.js} +4 -4
  27. package/dist/{sury-DmrZ3_Oj-DhGOjCNc.js → sury-DmrZ3_Oj-CCL_DlTt.js} +1 -1
  28. package/dist/{types-CFnCBr2I.js → types-DS8M8QF_.js} +4 -4
  29. package/dist/{valibot-CQk-M5rL-Cq5E7F3g.js → valibot-CQk-M5rL-CkjrLVu1.js} +2 -2
  30. package/dist/{zod-Db63SLXj-BWdcigdx.js → zod-Db63SLXj-Dyc_OWjq.js} +3 -3
  31. package/package.json +8 -11
  32. package/dist/pricing-fetcher-DygIroMj.d.ts +0 -21
  33. package/dist/shared-args-BtMSktLn.js +0 -68
  34. package/dist/shared-args.d.ts +0 -108
  35. package/dist/shared-args.js +0 -8
  36. package/dist/types-BcXIBMQk.js +0 -42
  37. package/dist/types-y1JQzaKZ.d.ts +0 -81
  38. package/dist/types.d.ts +0 -3
  39. package/dist/types.js +0 -4
  40. package/dist/utils-C7kg8MXN.js +0 -10
  41. package/dist/utils.d.ts +0 -5
  42. package/dist/utils.js +0 -3
  43. /package/dist/{arktype-C-GObzDh-Dj1DVoqC.js → arktype-C-GObzDh-Bx7Fdrqj.js} +0 -0
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  [![npm version](https://img.shields.io/npm/v/ccusage?color=yellow)](https://npmjs.com/package/ccusage)
6
6
  [![NPM Downloads](https://img.shields.io/npm/dy/ccusage)](https://www.npmjs.com/package/ccusage)
7
7
  [![DeepWiki](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==)](https://deepwiki.com/ryoppippi/ccusage)
8
+
8
9
  <!-- DeepWiki badge generated by https://deepwiki.ryoppippi.com/ -->
9
10
 
10
11
  <div align="center">
@@ -16,22 +17,23 @@ A CLI tool for analyzing Claude Code usage from local JSONL files.
16
17
  Inspired by [this article](https://note.com/milliondev/n/n1d018da2d769) about tracking Claude Code usage costs.
17
18
 
18
19
  ## What is `ccusage` (by NotebookLM)
20
+
19
21
  <details>
20
22
  <summary>Podcact</summary>
21
23
 
22
24
  # English
23
- https://github.com/user-attachments/assets/7a00f2f3-82a7-41b6-a8da-e04b76b5e35a
24
25
 
26
+ https://github.com/user-attachments/assets/7a00f2f3-82a7-41b6-a8da-e04b76b5e35a
25
27
 
26
28
  # 日本語
29
+
27
30
  https://github.com/user-attachments/assets/db09fc06-bf57-4d37-9b06-514851bcc1d0
28
31
 
29
32
  </details>
30
33
 
31
-
32
34
  ## Motivation
33
35
 
34
- Claude Code's Max plan offers unlimited usage - but wouldn't it be interesting to know how much you'd be paying if you were on a pay-per-use plan?
36
+ Claude Code's Max plan offers unlimited usage - but wouldn't it be interesting to know how much you'd be paying if you were on a pay-per-use plan?
35
37
 
36
38
  This tool helps you understand the value you're getting from your subscription by calculating the equivalent costs of your actual usage. See how much you're saving and enjoy that satisfying feeling of getting great value! 😊
37
39
 
@@ -52,6 +54,7 @@ This tool helps you understand the value you're getting from your subscription b
52
54
  ⚠️ **This is NOT an official Claude tool** - it's an independent community project that analyzes locally stored usage data.
53
55
 
54
56
  **Cost calculations are estimates only** and may not reflect actual billing:
57
+
55
58
  - Costs shown are virtual/estimated based on token counts and model pricing data
56
59
  - Actual costs may vary due to pricing changes, special rates, or billing adjustments
57
60
  - We do not guarantee the accuracy of calculated costs
@@ -202,7 +205,7 @@ ccusage session --order desc # Show newest sessions first (default)
202
205
  All commands support the following options:
203
206
 
204
207
  - `-s, --since <date>`: Filter from date (YYYYMMDD format)
205
- - `-u, --until <date>`: Filter until date (YYYYMMDD format)
208
+ - `-u, --until <date>`: Filter until date (YYYYMMDD format)
206
209
  - `-p, --path <path>`: Custom path to Claude data directory (default: `~/.claude`)
207
210
  - `-j, --json`: Output results in JSON format instead of table
208
211
  - `-m, --mode <mode>`: Cost calculation mode: `auto` (default), `calculate`, or `display`
@@ -234,10 +237,12 @@ ccusage mcp --mode calculate
234
237
  ```
235
238
 
236
239
  The MCP server supports both **stdio** and **HTTP stream** transports:
240
+
237
241
  - **stdio** (default): Best for local integration where the client directly spawns the process
238
242
  - **HTTP stream**: Best for remote access when you need to call the server from another machine or network location
239
243
 
240
244
  Available MCP tools:
245
+
241
246
  - `daily`: Returns daily usage reports (accepts `since`, `until`, `mode` parameters)
242
247
  - `session`: Returns session usage reports (accepts `since`, `until`, `mode` parameters)
243
248
 
@@ -249,18 +254,18 @@ Available MCP tools:
249
254
 
250
255
  To use ccusage MCP with Claude Desktop, add this to your Claude Desktop configuration file:
251
256
 
252
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
257
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
253
258
  **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
254
259
 
255
260
  ```json
256
261
  {
257
- "mcpServers": {
258
- "ccusage": {
259
- "command": "npx",
260
- "args": ["ccusage@latest", "mcp"],
261
- "env": {}
262
- }
263
- }
262
+ "mcpServers": {
263
+ "ccusage": {
264
+ "command": "npx",
265
+ "args": ["ccusage@latest", "mcp"],
266
+ "env": {}
267
+ }
268
+ }
264
269
  }
265
270
  ```
266
271
 
@@ -268,13 +273,13 @@ Or if you have ccusage installed globally:
268
273
 
269
274
  ```json
270
275
  {
271
- "mcpServers": {
272
- "ccusage": {
273
- "command": "ccusage",
274
- "args": ["mcp"],
275
- "env": {}
276
- }
277
- }
276
+ "mcpServers": {
277
+ "ccusage": {
278
+ "command": "ccusage",
279
+ "args": ["mcp"],
280
+ "env": {}
281
+ }
282
+ }
278
283
  }
279
284
  ```
280
285
 
@@ -283,6 +288,7 @@ After adding this configuration, restart Claude Desktop. You'll then be able to
283
288
  ## Output Example
284
289
 
285
290
  ### Daily Report
291
+
286
292
  ```
287
293
  ╭──────────────────────────────────────────╮
288
294
  │ │
@@ -302,6 +308,7 @@ After adding this configuration, restart Claude Desktop. You'll then be able to
302
308
  ```
303
309
 
304
310
  ### Session Report
311
+
305
312
  ```
306
313
  ╭───────────────────────────────────────────────╮
307
314
  │ │
@@ -321,6 +328,7 @@ After adding this configuration, restart Claude Desktop. You'll then be able to
321
328
  ```
322
329
 
323
330
  ## Requirements
331
+
324
332
  - Claude Code usage history files (`~/.claude/projects/**/*.jsonl`)
325
333
 
326
334
  ## License
@@ -1,18 +1,21 @@
1
- import "./index-BurjgCfW.js";
2
- import "./pricing-fetcher-DygIroMj.js";
3
- import { TokenData, TokenTotals } from "./types-y1JQzaKZ.js";
4
- import { DailyUsage, MonthlyUsage, SessionUsage } from "./data-loader-aUOjeZ06.js";
1
+ import "./pricing-fetcher-CfEgfzSr.js";
2
+ import { DailyUsage, MonthlyUsage, SessionUsage } from "./data-loader-VdEcqJHc.js";
5
3
 
6
4
  //#region src/calculate-cost.d.ts
7
- declare function calculateTotals(data: Array<DailyUsage | MonthlyUsage | SessionUsage>): TokenTotals;
8
- declare function getTotalTokens(tokens: TokenData): number;
9
- declare function createTotalsObject(totals: TokenTotals): {
5
+ type TokenData = {
10
6
  inputTokens: number;
11
7
  outputTokens: number;
12
8
  cacheCreationTokens: number;
13
9
  cacheReadTokens: number;
14
- totalTokens: number;
10
+ };
11
+ type TokenTotals = TokenData & {
15
12
  totalCost: number;
16
13
  };
14
+ type TotalsObject = TokenTotals & {
15
+ totalTokens: number;
16
+ };
17
+ declare function calculateTotals(data: Array<DailyUsage | MonthlyUsage | SessionUsage>): TokenTotals;
18
+ declare function getTotalTokens(tokens: TokenData): number;
19
+ declare function createTotalsObject(totals: TokenTotals): TotalsObject;
17
20
  //#endregion
18
21
  export { calculateTotals, createTotalsObject, getTotalTokens };
@@ -38,8 +38,8 @@ const $output = Symbol("ZodOutput");
38
38
  const $input = Symbol("ZodInput");
39
39
  var $ZodRegistry = class {
40
40
  constructor() {
41
- this._map = new WeakMap();
42
- this._idmap = new Map();
41
+ this._map = /* @__PURE__ */ new WeakMap();
42
+ this._idmap = /* @__PURE__ */ new Map();
43
43
  }
44
44
  add(schema, ..._meta) {
45
45
  const meta = _meta[0];
@@ -85,7 +85,7 @@ var JSONSchemaGenerator = class {
85
85
  this.unrepresentable = params?.unrepresentable ?? "throw";
86
86
  this.override = params?.override ?? (() => {});
87
87
  this.io = params?.io ?? "output";
88
- this.seen = new Map();
88
+ this.seen = /* @__PURE__ */ new Map();
89
89
  }
90
90
  process(schema, _params = {
91
91
  path: [],
@@ -630,7 +630,7 @@ function toJSONSchema(input, _params) {
630
630
  return gen.emit(input, _params);
631
631
  }
632
632
  function isTransforming(_schema, _ctx) {
633
- const ctx = _ctx ?? { seen: new Set() };
633
+ const ctx = _ctx ?? { seen: /* @__PURE__ */ new Set() };
634
634
  if (ctx.seen.has(_schema)) return false;
635
635
  ctx.seen.add(_schema);
636
636
  const schema = _schema;
@@ -1,5 +1,5 @@
1
- import { array, number, object, optional, pipe, regex, safeParse, string } from "./dist-FwNhpFrW.js";
2
- import { calculateCostFromTokens, fetchModelPricing, getModelPricing } from "./pricing-fetcher-BCv1Vods.js";
1
+ import { array, number, object, optional, pipe, regex, safeParse, string } from "./dist-C0-Tf5eD.js";
2
+ import { calculateCostFromTokens, fetchModelPricing, getModelPricing } from "./pricing-fetcher-BPUgMrB_.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { homedir } from "node:os";
@@ -575,7 +575,7 @@ var require_walker = __commonJS({ "node_modules/fdir/dist/api/walker.js"(exports
575
575
  counts: new counter_1.Counter(),
576
576
  options,
577
577
  queue: new queue_1.Queue((error, state) => this.callbackInvoker(state, error, callback$1)),
578
- symlinks: new Map(),
578
+ symlinks: /* @__PURE__ */ new Map(),
579
579
  visited: [""].slice(0, 0),
580
580
  controller: new AbortController()
581
581
  };
@@ -2608,7 +2608,7 @@ function isDynamicPattern(pattern, options) {
2608
2608
  return scan$2.isGlob || scan$2.negated;
2609
2609
  }
2610
2610
  function log(...tasks) {
2611
- console.log(`[tinyglobby ${new Date().toLocaleTimeString("es")}]`, ...tasks);
2611
+ console.log(`[tinyglobby ${(/* @__PURE__ */ new Date()).toLocaleTimeString("es")}]`, ...tasks);
2612
2612
  }
2613
2613
  const PARENT_DIRECTORY = /^(\/?\.\.)+/;
2614
2614
  const ESCAPING_BACKSLASHES = /\\(?=[()[\]{}!*+?@|])/g;
@@ -2770,7 +2770,9 @@ async function glob(patternsOrOptions, options) {
2770
2770
 
2771
2771
  //#endregion
2772
2772
  //#region src/data-loader.ts
2773
- const getDefaultClaudePath = () => path.join(homedir(), ".claude");
2773
+ function getDefaultClaudePath() {
2774
+ return path.join(homedir(), ".claude");
2775
+ }
2774
2776
  const UsageDataSchema = object({
2775
2777
  timestamp: string(),
2776
2778
  version: optional(string()),
@@ -2812,29 +2814,29 @@ const MonthlyUsageSchema = object({
2812
2814
  cacheReadTokens: number(),
2813
2815
  totalCost: number()
2814
2816
  });
2815
- const formatDate = (dateStr) => {
2817
+ function formatDate(dateStr) {
2816
2818
  const date = new Date(dateStr);
2817
2819
  const year = date.getFullYear();
2818
2820
  const month = String(date.getMonth() + 1).padStart(2, "0");
2819
2821
  const day = String(date.getDate()).padStart(2, "0");
2820
2822
  return `${year}-${month}-${day}`;
2821
- };
2822
- const calculateCostForEntry = (data, mode, modelPricing) => {
2823
+ }
2824
+ function calculateCostForEntry(data, mode, modelPricing) {
2823
2825
  if (mode === "display") return data.costUSD ?? 0;
2824
2826
  if (mode === "calculate") {
2825
- if (data.message.model) {
2827
+ if (data.message.model != null) {
2826
2828
  const pricing = getModelPricing(data.message.model, modelPricing);
2827
- if (pricing) return calculateCostFromTokens(data.message.usage, pricing);
2829
+ if (pricing != null) return calculateCostFromTokens(data.message.usage, pricing);
2828
2830
  }
2829
2831
  return 0;
2830
2832
  }
2831
- if (data.costUSD !== void 0) return data.costUSD;
2832
- if (data.message.model) {
2833
+ if (data.costUSD != null) return data.costUSD;
2834
+ if (data.message.model != null) {
2833
2835
  const pricing = getModelPricing(data.message.model, modelPricing);
2834
- if (pricing) return calculateCostFromTokens(data.message.usage, pricing);
2836
+ if (pricing != null) return calculateCostFromTokens(data.message.usage, pricing);
2835
2837
  }
2836
2838
  return 0;
2837
- };
2839
+ }
2838
2840
  async function loadDailyUsageData(options) {
2839
2841
  const claudePath = options?.claudePath ?? getDefaultClaudePath();
2840
2842
  const claudeDir = path.join(claudePath, "projects");
@@ -2843,9 +2845,9 @@ async function loadDailyUsageData(options) {
2843
2845
  absolute: true
2844
2846
  });
2845
2847
  if (files.length === 0) return [];
2846
- const mode = options?.mode || "auto";
2848
+ const mode = options?.mode ?? "auto";
2847
2849
  const modelPricing = mode === "display" ? {} : await fetchModelPricing();
2848
- const dailyMap = new Map();
2850
+ const allEntries = [];
2849
2851
  for (const file of files) {
2850
2852
  const content = await readFile(file, "utf-8");
2851
2853
  const lines = content.trim().split("\n").filter((line) => line.length > 0);
@@ -2855,31 +2857,41 @@ async function loadDailyUsageData(options) {
2855
2857
  if (!result.success) continue;
2856
2858
  const data = result.output;
2857
2859
  const date = formatDate(data.timestamp);
2858
- const existing = dailyMap.get(date) || {
2859
- date,
2860
- inputTokens: 0,
2861
- outputTokens: 0,
2862
- cacheCreationTokens: 0,
2863
- cacheReadTokens: 0,
2864
- totalCost: 0
2865
- };
2866
- existing.inputTokens += data.message.usage.input_tokens ?? 0;
2867
- existing.outputTokens += data.message.usage.output_tokens ?? 0;
2868
- existing.cacheCreationTokens += data.message.usage.cache_creation_input_tokens ?? 0;
2869
- existing.cacheReadTokens += data.message.usage.cache_read_input_tokens ?? 0;
2870
2860
  const cost = calculateCostForEntry(data, mode, modelPricing);
2871
- existing.totalCost += cost;
2872
- dailyMap.set(date, existing);
2873
- } catch (e) {}
2861
+ allEntries.push({
2862
+ data,
2863
+ date,
2864
+ cost
2865
+ });
2866
+ } catch {}
2874
2867
  }
2875
- let results = Array.from(dailyMap.values());
2876
- if (options?.since || options?.until) results = results.filter((data) => {
2877
- const dateStr = data.date.replace(/-/g, "");
2878
- if (options.since && dateStr < options.since) return false;
2879
- if (options.until && dateStr > options.until) return false;
2868
+ const groupedByDate = Object.groupBy(allEntries, (entry) => entry.date);
2869
+ const results = Object.entries(groupedByDate).map(([date, entries]) => {
2870
+ if (entries == null) return void 0;
2871
+ return entries.reduce((acc, entry) => ({
2872
+ date,
2873
+ inputTokens: acc.inputTokens + (entry.data.message.usage.input_tokens ?? 0),
2874
+ outputTokens: acc.outputTokens + (entry.data.message.usage.output_tokens ?? 0),
2875
+ cacheCreationTokens: acc.cacheCreationTokens + (entry.data.message.usage.cache_creation_input_tokens ?? 0),
2876
+ cacheReadTokens: acc.cacheReadTokens + (entry.data.message.usage.cache_read_input_tokens ?? 0),
2877
+ totalCost: acc.totalCost + entry.cost
2878
+ }), {
2879
+ date,
2880
+ inputTokens: 0,
2881
+ outputTokens: 0,
2882
+ cacheCreationTokens: 0,
2883
+ cacheReadTokens: 0,
2884
+ totalCost: 0
2885
+ });
2886
+ }).filter((item) => item != null).filter((item) => {
2887
+ if (options?.since != null || options?.until != null) {
2888
+ const dateStr = item.date.replace(/-/g, "");
2889
+ if (options.since != null && dateStr < options.since) return false;
2890
+ if (options.until != null && dateStr > options.until) return false;
2891
+ }
2880
2892
  return true;
2881
2893
  });
2882
- const sortOrder = options?.order || "desc";
2894
+ const sortOrder = options?.order ?? "desc";
2883
2895
  const sortedResults = sort(results);
2884
2896
  return sortOrder === "desc" ? sortedResults.desc((item) => new Date(item.date).getTime()) : sortedResults.asc((item) => new Date(item.date).getTime());
2885
2897
  }
@@ -2891,88 +2903,96 @@ async function loadSessionData(options) {
2891
2903
  absolute: true
2892
2904
  });
2893
2905
  if (files.length === 0) return [];
2894
- const mode = options?.mode || "auto";
2906
+ const mode = options?.mode ?? "auto";
2895
2907
  const modelPricing = mode === "display" ? {} : await fetchModelPricing();
2896
- const sessionMap = new Map();
2908
+ const allEntries = [];
2897
2909
  for (const file of files) {
2898
2910
  const relativePath = path.relative(claudeDir, file);
2899
2911
  const parts = relativePath.split(path.sep);
2900
- const sessionId = parts[parts.length - 2];
2901
- const projectPath = parts.slice(0, -2).join(path.sep);
2912
+ const sessionId = parts[parts.length - 2] ?? "unknown";
2913
+ const joinedPath = parts.slice(0, -2).join(path.sep);
2914
+ const projectPath = joinedPath.length > 0 ? joinedPath : "Unknown Project";
2902
2915
  const content = await readFile(file, "utf-8");
2903
2916
  const lines = content.trim().split("\n").filter((line) => line.length > 0);
2904
- let lastTimestamp = "";
2905
2917
  for (const line of lines) try {
2906
2918
  const parsed = JSON.parse(line);
2907
2919
  const result = safeParse(UsageDataSchema, parsed);
2908
2920
  if (!result.success) continue;
2909
2921
  const data = result.output;
2910
- const key = `${projectPath}/${sessionId}`;
2911
- const existing = sessionMap.get(key) || {
2912
- sessionId: sessionId || "unknown",
2913
- projectPath: projectPath || "Unknown Project",
2914
- inputTokens: 0,
2915
- outputTokens: 0,
2916
- cacheCreationTokens: 0,
2917
- cacheReadTokens: 0,
2918
- totalCost: 0,
2919
- lastActivity: "",
2920
- versions: [],
2921
- versionSet: new Set()
2922
- };
2923
- existing.inputTokens += data.message.usage.input_tokens ?? 0;
2924
- existing.outputTokens += data.message.usage.output_tokens ?? 0;
2925
- existing.cacheCreationTokens += data.message.usage.cache_creation_input_tokens ?? 0;
2926
- existing.cacheReadTokens += data.message.usage.cache_read_input_tokens ?? 0;
2922
+ const sessionKey = `${projectPath}/${sessionId}`;
2927
2923
  const cost = calculateCostForEntry(data, mode, modelPricing);
2928
- existing.totalCost += cost;
2929
- if (data.timestamp > lastTimestamp) {
2930
- lastTimestamp = data.timestamp;
2931
- existing.lastActivity = formatDate(data.timestamp);
2932
- }
2933
- if (data.version) existing.versionSet.add(data.version);
2934
- sessionMap.set(key, existing);
2935
- } catch (e) {}
2924
+ allEntries.push({
2925
+ data,
2926
+ sessionKey,
2927
+ sessionId,
2928
+ projectPath,
2929
+ cost,
2930
+ timestamp: data.timestamp
2931
+ });
2932
+ } catch {}
2936
2933
  }
2937
- let results = Array.from(sessionMap.values()).map((session) => {
2938
- const { versionSet,...sessionData } = session;
2939
- return {
2940
- ...sessionData,
2934
+ const groupedBySessions = Object.groupBy(allEntries, (entry) => entry.sessionKey);
2935
+ const results = Object.entries(groupedBySessions).map(([_, entries]) => {
2936
+ if (entries == null) return void 0;
2937
+ const latestEntry = entries.reduce((latest, current) => current.timestamp > latest.timestamp ? current : latest);
2938
+ const versionSet = /* @__PURE__ */ new Set();
2939
+ for (const entry of entries) if (entry.data.version != null) versionSet.add(entry.data.version);
2940
+ const aggregated = entries.reduce((acc, entry) => ({
2941
+ sessionId: latestEntry.sessionId,
2942
+ projectPath: latestEntry.projectPath,
2943
+ inputTokens: acc.inputTokens + (entry.data.message.usage.input_tokens ?? 0),
2944
+ outputTokens: acc.outputTokens + (entry.data.message.usage.output_tokens ?? 0),
2945
+ cacheCreationTokens: acc.cacheCreationTokens + (entry.data.message.usage.cache_creation_input_tokens ?? 0),
2946
+ cacheReadTokens: acc.cacheReadTokens + (entry.data.message.usage.cache_read_input_tokens ?? 0),
2947
+ totalCost: acc.totalCost + entry.cost,
2948
+ lastActivity: formatDate(latestEntry.timestamp),
2941
2949
  versions: Array.from(versionSet).sort()
2942
- };
2943
- });
2944
- if (options?.since || options?.until) results = results.filter((session) => {
2945
- const dateStr = session.lastActivity.replace(/-/g, "");
2946
- if (options.since && dateStr < options.since) return false;
2947
- if (options.until && dateStr > options.until) return false;
2950
+ }), {
2951
+ sessionId: latestEntry.sessionId,
2952
+ projectPath: latestEntry.projectPath,
2953
+ inputTokens: 0,
2954
+ outputTokens: 0,
2955
+ cacheCreationTokens: 0,
2956
+ cacheReadTokens: 0,
2957
+ totalCost: 0,
2958
+ lastActivity: formatDate(latestEntry.timestamp),
2959
+ versions: Array.from(versionSet).sort()
2960
+ });
2961
+ return aggregated;
2962
+ }).filter((item) => item != null).filter((item) => {
2963
+ if (options?.since != null || options?.until != null) {
2964
+ const dateStr = item.lastActivity.replace(/-/g, "");
2965
+ if (options.since != null && dateStr < options.since) return false;
2966
+ if (options.until != null && dateStr > options.until) return false;
2967
+ }
2948
2968
  return true;
2949
2969
  });
2950
- const sortOrder = options?.order || "desc";
2970
+ const sortOrder = options?.order ?? "desc";
2951
2971
  const sortedResults = sort(results);
2952
2972
  return sortOrder === "desc" ? sortedResults.desc((item) => new Date(item.lastActivity).getTime()) : sortedResults.asc((item) => new Date(item.lastActivity).getTime());
2953
2973
  }
2954
2974
  async function loadMonthlyUsageData(options) {
2955
2975
  const dailyData = await loadDailyUsageData(options);
2956
- const monthlyMap = new Map();
2957
- for (const data of dailyData) {
2958
- const month = data.date.substring(0, 7);
2959
- const existing = monthlyMap.get(month) || {
2976
+ const groupedByMonth = Object.groupBy(dailyData, (data) => data.date.substring(0, 7));
2977
+ const monthlyArray = Object.entries(groupedByMonth).map(([month, dailyEntries]) => {
2978
+ if (dailyEntries == null) return void 0;
2979
+ return dailyEntries.reduce((acc, data) => ({
2980
+ month,
2981
+ inputTokens: acc.inputTokens + data.inputTokens,
2982
+ outputTokens: acc.outputTokens + data.outputTokens,
2983
+ cacheCreationTokens: acc.cacheCreationTokens + data.cacheCreationTokens,
2984
+ cacheReadTokens: acc.cacheReadTokens + data.cacheReadTokens,
2985
+ totalCost: acc.totalCost + data.totalCost
2986
+ }), {
2960
2987
  month,
2961
2988
  inputTokens: 0,
2962
2989
  outputTokens: 0,
2963
2990
  cacheCreationTokens: 0,
2964
2991
  cacheReadTokens: 0,
2965
2992
  totalCost: 0
2966
- };
2967
- existing.inputTokens += data.inputTokens;
2968
- existing.outputTokens += data.outputTokens;
2969
- existing.cacheCreationTokens += data.cacheCreationTokens;
2970
- existing.cacheReadTokens += data.cacheReadTokens;
2971
- existing.totalCost += data.totalCost;
2972
- monthlyMap.set(month, existing);
2973
- }
2974
- const monthlyArray = Array.from(monthlyMap.values());
2975
- const sortOrder = options?.order || "desc";
2993
+ });
2994
+ }).filter((item) => item != null);
2995
+ const sortOrder = options?.order ?? "desc";
2976
2996
  const sortedMonthly = sort(monthlyArray);
2977
2997
  return sortOrder === "desc" ? sortedMonthly.desc((item) => item.month) : sortedMonthly.asc((item) => item.month);
2978
2998
  }
@@ -1,9 +1,13 @@
1
- import { ArraySchema, InferOutput, NumberSchema, ObjectSchema, OptionalSchema, RegexAction, SchemaWithPipe, StringSchema } from "./index-BurjgCfW.js";
2
- import { ModelPricing } from "./pricing-fetcher-DygIroMj.js";
3
- import { CostMode, SortOrder } from "./types-y1JQzaKZ.js";
1
+ import { ArraySchema, InferOutput, ModelPricing, NumberSchema, ObjectSchema, OptionalSchema, RegexAction, SchemaWithPipe, StringSchema } from "./pricing-fetcher-CfEgfzSr.js";
4
2
 
3
+ //#region src/types.internal.d.ts
4
+ declare const CostModes: readonly ["auto", "calculate", "display"];
5
+ type CostMode = (typeof CostModes)[number];
6
+ declare const SortOrders: readonly ["desc", "asc"];
7
+ type SortOrder = (typeof SortOrders)[number];
8
+ //#endregion
5
9
  //#region src/data-loader.d.ts
6
- declare const getDefaultClaudePath: () => string;
10
+ declare function getDefaultClaudePath(): string;
7
11
  declare const UsageDataSchema: ObjectSchema<{
8
12
  readonly timestamp: StringSchema<undefined>;
9
13
  readonly version: OptionalSchema<StringSchema<undefined>, undefined>;
@@ -49,19 +53,19 @@ declare const MonthlyUsageSchema: ObjectSchema<{
49
53
  readonly totalCost: NumberSchema<undefined>;
50
54
  }, undefined>;
51
55
  type MonthlyUsage = InferOutput<typeof MonthlyUsageSchema>;
52
- declare const formatDate: (dateStr: string) => string;
53
- declare const calculateCostForEntry: (data: UsageData, mode: CostMode, modelPricing: Record<string, ModelPricing>) => number;
54
- interface DateFilter {
56
+ declare function formatDate(dateStr: string): string;
57
+ declare function calculateCostForEntry(data: UsageData, mode: CostMode, modelPricing: Record<string, ModelPricing>): number;
58
+ type DateFilter = {
55
59
  since?: string;
56
60
  until?: string;
57
- }
58
- interface LoadOptions extends DateFilter {
61
+ };
62
+ type LoadOptions = {
59
63
  claudePath?: string;
60
64
  mode?: CostMode;
61
65
  order?: SortOrder;
62
- }
66
+ } & DateFilter;
63
67
  declare function loadDailyUsageData(options?: LoadOptions): Promise<DailyUsage[]>;
64
68
  declare function loadSessionData(options?: LoadOptions): Promise<SessionUsage[]>;
65
69
  declare function loadMonthlyUsageData(options?: LoadOptions): Promise<MonthlyUsage[]>;
66
70
  //#endregion
67
- export { DailyUsage, DailyUsageSchema as DailyUsageSchema$1, DateFilter, LoadOptions, MonthlyUsage, MonthlyUsageSchema as MonthlyUsageSchema$1, SessionUsage, SessionUsageSchema as SessionUsageSchema$1, UsageData, UsageDataSchema as UsageDataSchema$1, calculateCostForEntry as calculateCostForEntry$1, formatDate as formatDate$1, getDefaultClaudePath as getDefaultClaudePath$1, loadDailyUsageData as loadDailyUsageData$1, loadMonthlyUsageData as loadMonthlyUsageData$1, loadSessionData as loadSessionData$1 };
71
+ export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData };
@@ -1,5 +1,3 @@
1
- import "./index-BurjgCfW.js";
2
- import "./pricing-fetcher-DygIroMj.js";
3
- import "./types-y1JQzaKZ.js";
4
- import { DailyUsage, DailyUsageSchema$1 as DailyUsageSchema, DateFilter, LoadOptions, MonthlyUsage, MonthlyUsageSchema$1 as MonthlyUsageSchema, SessionUsage, SessionUsageSchema$1 as SessionUsageSchema, UsageData, UsageDataSchema$1 as UsageDataSchema, calculateCostForEntry$1 as calculateCostForEntry, formatDate$1 as formatDate, getDefaultClaudePath$1 as getDefaultClaudePath, loadDailyUsageData$1 as loadDailyUsageData, loadMonthlyUsageData$1 as loadMonthlyUsageData, loadSessionData$1 as loadSessionData } from "./data-loader-aUOjeZ06.js";
1
+ import "./pricing-fetcher-CfEgfzSr.js";
2
+ import { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData } from "./data-loader-VdEcqJHc.js";
5
3
  export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData };
@@ -1,6 +1,6 @@
1
- import { DailyUsageSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData } from "./data-loader-CDv0IYZx.js";
2
- import "./dist-FwNhpFrW.js";
3
- import "./logger-DhDyJEC5.js";
4
- import "./pricing-fetcher-BCv1Vods.js";
1
+ import { DailyUsageSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData } from "./data-loader-DP5qBPn6.js";
2
+ import "./dist-C0-Tf5eD.js";
3
+ import "./logger-DsQC4OvA.js";
4
+ import "./pricing-fetcher-BPUgMrB_.js";
5
5
 
6
6
  export { DailyUsageSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData };
@@ -1,7 +1,7 @@
1
- import { UsageDataSchema, glob } from "./data-loader-CDv0IYZx.js";
2
- import { safeParse } from "./dist-FwNhpFrW.js";
3
- import { logger } from "./logger-DhDyJEC5.js";
4
- import { calculateCostFromTokens, fetchModelPricing, getModelPricing } from "./pricing-fetcher-BCv1Vods.js";
1
+ import { UsageDataSchema, glob } from "./data-loader-DP5qBPn6.js";
2
+ import { safeParse } from "./dist-C0-Tf5eD.js";
3
+ import { logger } from "./logger-DsQC4OvA.js";
4
+ import { calculateCostFromTokens, fetchModelPricing, getModelPricing } from "./pricing-fetcher-BPUgMrB_.js";
5
5
  import { readFile } from "node:fs/promises";
6
6
  import { homedir } from "node:os";
7
7
  import path from "node:path";
@@ -9,7 +9,7 @@ import path from "node:path";
9
9
  //#region src/debug.ts
10
10
  const MATCH_THRESHOLD_PERCENT = .1;
11
11
  async function detectMismatches(claudePath) {
12
- const claudeDir = claudePath || path.join(homedir(), ".claude", "projects");
12
+ const claudeDir = claudePath ?? path.join(homedir(), ".claude", "projects");
13
13
  const files = await glob(["**/*.jsonl"], {
14
14
  cwd: claudeDir,
15
15
  absolute: true
@@ -21,8 +21,8 @@ async function detectMismatches(claudePath) {
21
21
  matches: 0,
22
22
  mismatches: 0,
23
23
  discrepancies: [],
24
- modelStats: new Map(),
25
- versionStats: new Map()
24
+ modelStats: /* @__PURE__ */ new Map(),
25
+ versionStats: /* @__PURE__ */ new Map()
26
26
  };
27
27
  for (const file of files) {
28
28
  const content = await readFile(file, "utf-8");
@@ -33,23 +33,23 @@ async function detectMismatches(claudePath) {
33
33
  if (!result.success) continue;
34
34
  const data = result.output;
35
35
  stats.totalEntries++;
36
- if (data.costUSD !== void 0 && data.message.model && data.message.model !== "<synthetic>") {
36
+ if (data.costUSD !== void 0 && data.message.model != null && data.message.model !== "<synthetic>") {
37
37
  stats.entriesWithBoth++;
38
38
  const model = data.message.model;
39
39
  const pricing = getModelPricing(model, modelPricing);
40
- if (pricing) {
40
+ if (pricing != null) {
41
41
  const calculatedCost = calculateCostFromTokens(data.message.usage, pricing);
42
42
  const difference = Math.abs(data.costUSD - calculatedCost);
43
43
  const percentDiff = data.costUSD > 0 ? difference / data.costUSD * 100 : 0;
44
- const modelStat = stats.modelStats.get(model) || {
44
+ const modelStat = stats.modelStats.get(model) ?? {
45
45
  total: 0,
46
46
  matches: 0,
47
47
  mismatches: 0,
48
48
  avgPercentDiff: 0
49
49
  };
50
50
  modelStat.total++;
51
- if (data.version) {
52
- const versionStat = stats.versionStats.get(data.version) || {
51
+ if (data.version != null) {
52
+ const versionStat = stats.versionStats.get(data.version) ?? {
53
53
  total: 0,
54
54
  matches: 0,
55
55
  mismatches: 0,
@@ -82,7 +82,7 @@ async function detectMismatches(claudePath) {
82
82
  stats.modelStats.set(model, modelStat);
83
83
  }
84
84
  }
85
- } catch (e) {}
85
+ } catch {}
86
86
  }
87
87
  return stats;
88
88
  }