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.
- package/README.md +27 -19
- package/dist/calculate-cost.d.ts +11 -8
- package/dist/{core-BgFXUe_h.js → core-B0ovMhJe.js} +4 -4
- package/dist/{data-loader-CDv0IYZx.js → data-loader-DP5qBPn6.js} +112 -92
- package/dist/{data-loader-aUOjeZ06.d.ts → data-loader-VdEcqJHc.d.ts} +15 -11
- package/dist/data-loader.d.ts +2 -4
- package/dist/data-loader.js +4 -4
- package/dist/{debug-Dk36WQTw.js → debug-C_5Qx11m.js} +13 -13
- package/dist/debug.d.ts +4 -4
- package/dist/debug.js +5 -5
- package/dist/{dist-FwNhpFrW.js → dist-C0-Tf5eD.js} +1 -92
- package/dist/{dist-C_i5I27w.js → dist-LwbOR2Yw.js} +5 -5
- package/dist/{effect-WSjEuzC9-BsxP11fz.js → effect-WSjEuzC9-CJfWUy0j.js} +1 -1
- package/dist/{esm-vjyZjnpZ.js → esm-Dqsc1zmX.js} +1 -1
- package/dist/{index-CISmcbXk-BotItq1T.js → index-CISmcbXk-DCA05NUL.js} +5 -5
- package/dist/index.js +117 -42
- package/dist/{logger-DhDyJEC5.js → logger-DsQC4OvA.js} +17 -17
- package/dist/logger.js +1 -1
- package/dist/{mcp-G-TIOcuj.js → mcp-BQdv12mr.js} +82 -73
- package/dist/mcp.d.ts +2 -4
- package/dist/mcp.js +7 -8
- package/dist/{pricing-fetcher-BCv1Vods.js → pricing-fetcher-BPUgMrB_.js} +9 -9
- package/dist/{index-BurjgCfW.d.ts → pricing-fetcher-CfEgfzSr.d.ts} +19 -249
- package/dist/pricing-fetcher.d.ts +1 -2
- package/dist/pricing-fetcher.js +3 -3
- package/dist/{prompt-IToGuko2.js → prompt-DljZqwMa.js} +4 -4
- package/dist/{sury-DmrZ3_Oj-DhGOjCNc.js → sury-DmrZ3_Oj-CCL_DlTt.js} +1 -1
- package/dist/{types-CFnCBr2I.js → types-DS8M8QF_.js} +4 -4
- package/dist/{valibot-CQk-M5rL-Cq5E7F3g.js → valibot-CQk-M5rL-CkjrLVu1.js} +2 -2
- package/dist/{zod-Db63SLXj-BWdcigdx.js → zod-Db63SLXj-Dyc_OWjq.js} +3 -3
- package/package.json +8 -11
- package/dist/pricing-fetcher-DygIroMj.d.ts +0 -21
- package/dist/shared-args-BtMSktLn.js +0 -68
- package/dist/shared-args.d.ts +0 -108
- package/dist/shared-args.js +0 -8
- package/dist/types-BcXIBMQk.js +0 -42
- package/dist/types-y1JQzaKZ.d.ts +0 -81
- package/dist/types.d.ts +0 -3
- package/dist/types.js +0 -4
- package/dist/utils-C7kg8MXN.js +0 -10
- package/dist/utils.d.ts +0 -5
- package/dist/utils.js +0 -3
- /package/dist/{arktype-C-GObzDh-Dj1DVoqC.js → arktype-C-GObzDh-Bx7Fdrqj.js} +0 -0
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
[](https://npmjs.com/package/ccusage)
|
|
6
6
|
[](https://www.npmjs.com/package/ccusage)
|
|
7
7
|
[](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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
package/dist/calculate-cost.d.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import "./
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
2
|
-
import { calculateCostFromTokens, fetchModelPricing, getModelPricing } from "./pricing-fetcher-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
2848
|
+
const mode = options?.mode ?? "auto";
|
|
2847
2849
|
const modelPricing = mode === "display" ? {} : await fetchModelPricing();
|
|
2848
|
-
const
|
|
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
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2861
|
+
allEntries.push({
|
|
2862
|
+
data,
|
|
2863
|
+
date,
|
|
2864
|
+
cost
|
|
2865
|
+
});
|
|
2866
|
+
} catch {}
|
|
2874
2867
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
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
|
|
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
|
|
2906
|
+
const mode = options?.mode ?? "auto";
|
|
2895
2907
|
const modelPricing = mode === "display" ? {} : await fetchModelPricing();
|
|
2896
|
-
const
|
|
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
|
|
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
|
|
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
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2924
|
+
allEntries.push({
|
|
2925
|
+
data,
|
|
2926
|
+
sessionKey,
|
|
2927
|
+
sessionId,
|
|
2928
|
+
projectPath,
|
|
2929
|
+
cost,
|
|
2930
|
+
timestamp: data.timestamp
|
|
2931
|
+
});
|
|
2932
|
+
} catch {}
|
|
2936
2933
|
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
return
|
|
2940
|
-
|
|
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
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
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
|
|
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
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
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
|
-
|
|
2968
|
-
|
|
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 "./
|
|
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
|
|
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
|
|
53
|
-
declare
|
|
54
|
-
|
|
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
|
-
|
|
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
|
|
71
|
+
export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData };
|
package/dist/data-loader.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import "./
|
|
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 };
|
package/dist/data-loader.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { DailyUsageSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData } from "./data-loader-
|
|
2
|
-
import "./dist-
|
|
3
|
-
import "./logger-
|
|
4
|
-
import "./pricing-fetcher-
|
|
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-
|
|
2
|
-
import { safeParse } from "./dist-
|
|
3
|
-
import { logger } from "./logger-
|
|
4
|
-
import { calculateCostFromTokens, fetchModelPricing, getModelPricing } from "./pricing-fetcher-
|
|
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
|
|
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
|
|
85
|
+
} catch {}
|
|
86
86
|
}
|
|
87
87
|
return stats;
|
|
88
88
|
}
|