ccusage 0.5.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 +65 -20
- package/dist/calculate-cost.d.ts +11 -8
- package/dist/{core-BgFXUe_h.js → core-B0ovMhJe.js} +4 -4
- package/dist/{data-loader-r5ZcMQy7.js → data-loader-DP5qBPn6.js} +153 -96
- package/dist/{data-loader-LMCrJ-lW.d.ts → data-loader-VdEcqJHc.d.ts} +28 -13
- package/dist/data-loader.d.ts +3 -5
- package/dist/data-loader.js +5 -5
- package/dist/{debug-BVxGf4UL.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 +246 -62
- package/dist/{logger-Cu4Ir1a5.js → logger-DsQC4OvA.js} +17 -17
- package/dist/logger.js +1 -1
- package/dist/{mcp-DAzj5Pua.js → mcp-BQdv12mr.js} +83 -74
- package/dist/mcp.d.ts +2 -4
- package/dist/mcp.js +7 -8
- package/dist/{pricing-fetcher-B5yPtoTB.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-DN3jRldX.js +0 -61
- package/dist/shared-args.d.ts +0 -94
- package/dist/shared-args.js +0 -8
- package/dist/types-B3ib19os.d.ts +0 -79
- package/dist/types-DFrbJmnT.js +0 -41
- 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,34 +17,36 @@ 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
|
|
|
38
40
|
## Features
|
|
39
41
|
|
|
40
42
|
- 📊 **Daily Report**: View token usage and costs aggregated by date
|
|
43
|
+
- 📅 **Monthly Report**: View token usage and costs aggregated by month
|
|
41
44
|
- 💬 **Session Report**: View usage grouped by conversation sessions
|
|
42
45
|
- 📅 **Date Filtering**: Filter reports by date range using `--since` and `--until`
|
|
43
46
|
- 📁 **Custom Path**: Support for custom Claude data directory locations
|
|
44
47
|
- 🎨 **Beautiful Output**: Colorful table-formatted display
|
|
45
48
|
- 📄 **JSON Output**: Export data in structured JSON format with `--json`
|
|
46
|
-
- 💰 **Cost Tracking**: Shows costs in USD for each day/session
|
|
49
|
+
- 💰 **Cost Tracking**: Shows costs in USD for each day/month/session
|
|
47
50
|
- 🔄 **Cache Token Support**: Tracks and displays cache creation and cache read tokens separately
|
|
48
51
|
|
|
49
52
|
## Important Disclaimer
|
|
@@ -51,6 +54,7 @@ This tool helps you understand the value you're getting from your subscription b
|
|
|
51
54
|
⚠️ **This is NOT an official Claude tool** - it's an independent community project that analyzes locally stored usage data.
|
|
52
55
|
|
|
53
56
|
**Cost calculations are estimates only** and may not reflect actual billing:
|
|
57
|
+
|
|
54
58
|
- Costs shown are virtual/estimated based on token counts and model pricing data
|
|
55
59
|
- Actual costs may vary due to pricing changes, special rates, or billing adjustments
|
|
56
60
|
- We do not guarantee the accuracy of calculated costs
|
|
@@ -134,10 +138,41 @@ ccusage daily --json
|
|
|
134
138
|
ccusage daily --mode auto # Use costUSD when available, calculate otherwise (default)
|
|
135
139
|
ccusage daily --mode calculate # Always calculate costs from tokens
|
|
136
140
|
ccusage daily --mode display # Always show pre-calculated costUSD values
|
|
141
|
+
|
|
142
|
+
# Control sort order
|
|
143
|
+
ccusage daily --order asc # Show oldest dates first
|
|
144
|
+
ccusage daily --order desc # Show newest dates first (default)
|
|
137
145
|
```
|
|
138
146
|
|
|
139
147
|
`ccusage` is an alias for `ccusage daily`, so you can run it without specifying the subcommand.
|
|
140
148
|
|
|
149
|
+
### Monthly Report
|
|
150
|
+
|
|
151
|
+
Shows token usage and costs aggregated by month:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Show all monthly usage
|
|
155
|
+
ccusage monthly
|
|
156
|
+
|
|
157
|
+
# Filter by date range
|
|
158
|
+
ccusage monthly --since 20250101 --until 20250531
|
|
159
|
+
|
|
160
|
+
# Use custom Claude data directory
|
|
161
|
+
ccusage monthly --path /custom/path/to/.claude
|
|
162
|
+
|
|
163
|
+
# Output in JSON format
|
|
164
|
+
ccusage monthly --json
|
|
165
|
+
|
|
166
|
+
# Control cost calculation mode
|
|
167
|
+
ccusage monthly --mode auto # Use costUSD when available, calculate otherwise (default)
|
|
168
|
+
ccusage monthly --mode calculate # Always calculate costs from tokens
|
|
169
|
+
ccusage monthly --mode display # Always show pre-calculated costUSD values
|
|
170
|
+
|
|
171
|
+
# Control sort order
|
|
172
|
+
ccusage monthly --order asc # Show oldest months first
|
|
173
|
+
ccusage monthly --order desc # Show newest months first (default)
|
|
174
|
+
```
|
|
175
|
+
|
|
141
176
|
### Session Report
|
|
142
177
|
|
|
143
178
|
Shows usage grouped by conversation sessions, sorted by cost:
|
|
@@ -159,6 +194,10 @@ ccusage session --json
|
|
|
159
194
|
ccusage session --mode auto # Use costUSD when available, calculate otherwise (default)
|
|
160
195
|
ccusage session --mode calculate # Always calculate costs from tokens
|
|
161
196
|
ccusage session --mode display # Always show pre-calculated costUSD values
|
|
197
|
+
|
|
198
|
+
# Control sort order
|
|
199
|
+
ccusage session --order asc # Show oldest sessions first
|
|
200
|
+
ccusage session --order desc # Show newest sessions first (default)
|
|
162
201
|
```
|
|
163
202
|
|
|
164
203
|
### Options
|
|
@@ -166,10 +205,11 @@ ccusage session --mode display # Always show pre-calculated costUSD values
|
|
|
166
205
|
All commands support the following options:
|
|
167
206
|
|
|
168
207
|
- `-s, --since <date>`: Filter from date (YYYYMMDD format)
|
|
169
|
-
- `-u, --until <date>`: Filter until date (YYYYMMDD format)
|
|
208
|
+
- `-u, --until <date>`: Filter until date (YYYYMMDD format)
|
|
170
209
|
- `-p, --path <path>`: Custom path to Claude data directory (default: `~/.claude`)
|
|
171
210
|
- `-j, --json`: Output results in JSON format instead of table
|
|
172
211
|
- `-m, --mode <mode>`: Cost calculation mode: `auto` (default), `calculate`, or `display`
|
|
212
|
+
- `-o, --order <order>`: Sort order: `desc` (newest first, default) or `asc` (oldest first).
|
|
173
213
|
- `-d, --debug`: Show pricing mismatch information for debugging
|
|
174
214
|
- `--debug-samples <number>`: Number of sample discrepancies to show in debug output (default: 5)
|
|
175
215
|
- `-h, --help`: Display help message
|
|
@@ -197,10 +237,12 @@ ccusage mcp --mode calculate
|
|
|
197
237
|
```
|
|
198
238
|
|
|
199
239
|
The MCP server supports both **stdio** and **HTTP stream** transports:
|
|
240
|
+
|
|
200
241
|
- **stdio** (default): Best for local integration where the client directly spawns the process
|
|
201
242
|
- **HTTP stream**: Best for remote access when you need to call the server from another machine or network location
|
|
202
243
|
|
|
203
244
|
Available MCP tools:
|
|
245
|
+
|
|
204
246
|
- `daily`: Returns daily usage reports (accepts `since`, `until`, `mode` parameters)
|
|
205
247
|
- `session`: Returns session usage reports (accepts `since`, `until`, `mode` parameters)
|
|
206
248
|
|
|
@@ -212,18 +254,18 @@ Available MCP tools:
|
|
|
212
254
|
|
|
213
255
|
To use ccusage MCP with Claude Desktop, add this to your Claude Desktop configuration file:
|
|
214
256
|
|
|
215
|
-
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
257
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
216
258
|
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
217
259
|
|
|
218
260
|
```json
|
|
219
261
|
{
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
262
|
+
"mcpServers": {
|
|
263
|
+
"ccusage": {
|
|
264
|
+
"command": "npx",
|
|
265
|
+
"args": ["ccusage@latest", "mcp"],
|
|
266
|
+
"env": {}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
227
269
|
}
|
|
228
270
|
```
|
|
229
271
|
|
|
@@ -231,13 +273,13 @@ Or if you have ccusage installed globally:
|
|
|
231
273
|
|
|
232
274
|
```json
|
|
233
275
|
{
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
276
|
+
"mcpServers": {
|
|
277
|
+
"ccusage": {
|
|
278
|
+
"command": "ccusage",
|
|
279
|
+
"args": ["mcp"],
|
|
280
|
+
"env": {}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
241
283
|
}
|
|
242
284
|
```
|
|
243
285
|
|
|
@@ -246,6 +288,7 @@ After adding this configuration, restart Claude Desktop. You'll then be able to
|
|
|
246
288
|
## Output Example
|
|
247
289
|
|
|
248
290
|
### Daily Report
|
|
291
|
+
|
|
249
292
|
```
|
|
250
293
|
╭──────────────────────────────────────────╮
|
|
251
294
|
│ │
|
|
@@ -265,6 +308,7 @@ After adding this configuration, restart Claude Desktop. You'll then be able to
|
|
|
265
308
|
```
|
|
266
309
|
|
|
267
310
|
### Session Report
|
|
311
|
+
|
|
268
312
|
```
|
|
269
313
|
╭───────────────────────────────────────────────╮
|
|
270
314
|
│ │
|
|
@@ -284,6 +328,7 @@ After adding this configuration, restart Claude Desktop. You'll then be able to
|
|
|
284
328
|
```
|
|
285
329
|
|
|
286
330
|
## Requirements
|
|
331
|
+
|
|
287
332
|
- Claude Code usage history files (`~/.claude/projects/**/*.jsonl`)
|
|
288
333
|
|
|
289
334
|
## License
|
package/dist/calculate-cost.d.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import "./
|
|
3
|
-
import { TokenData, TokenTotals } from "./types-B3ib19os.js";
|
|
4
|
-
import { DailyUsage, SessionUsage } from "./data-loader-LMCrJ-lW.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, 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
|
};
|
|
@@ -2110,9 +2110,9 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
|
|
|
2110
2110
|
if (glob$1 === "" || typeof glob$1 !== "string" && !isState) throw new TypeError("Expected pattern to be a non-empty string");
|
|
2111
2111
|
const opts = options || {};
|
|
2112
2112
|
const posix$1 = opts.windows;
|
|
2113
|
-
const regex = isState ? picomatch$2.compileRe(glob$1, options) : picomatch$2.makeRe(glob$1, options, false, true);
|
|
2114
|
-
const state = regex.state;
|
|
2115
|
-
delete regex.state;
|
|
2113
|
+
const regex$1 = isState ? picomatch$2.compileRe(glob$1, options) : picomatch$2.makeRe(glob$1, options, false, true);
|
|
2114
|
+
const state = regex$1.state;
|
|
2115
|
+
delete regex$1.state;
|
|
2116
2116
|
let isIgnored = () => false;
|
|
2117
2117
|
if (opts.ignore) {
|
|
2118
2118
|
const ignoreOpts = {
|
|
@@ -2124,14 +2124,14 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
|
|
|
2124
2124
|
isIgnored = picomatch$2(opts.ignore, ignoreOpts, returnState);
|
|
2125
2125
|
}
|
|
2126
2126
|
const matcher = (input, returnObject = false) => {
|
|
2127
|
-
const { isMatch, match, output } = picomatch$2.test(input, regex, options, {
|
|
2127
|
+
const { isMatch, match, output } = picomatch$2.test(input, regex$1, options, {
|
|
2128
2128
|
glob: glob$1,
|
|
2129
2129
|
posix: posix$1
|
|
2130
2130
|
});
|
|
2131
2131
|
const result = {
|
|
2132
2132
|
glob: glob$1,
|
|
2133
2133
|
state,
|
|
2134
|
-
regex,
|
|
2134
|
+
regex: regex$1,
|
|
2135
2135
|
posix: posix$1,
|
|
2136
2136
|
input,
|
|
2137
2137
|
output,
|
|
@@ -2170,7 +2170,7 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
|
|
|
2170
2170
|
* @return {Object} Returns an object with matching info.
|
|
2171
2171
|
* @api public
|
|
2172
2172
|
*/
|
|
2173
|
-
picomatch$2.test = (input, regex, options, { glob: glob$1, posix: posix$1 } = {}) => {
|
|
2173
|
+
picomatch$2.test = (input, regex$1, options, { glob: glob$1, posix: posix$1 } = {}) => {
|
|
2174
2174
|
if (typeof input !== "string") throw new TypeError("Expected input to be a string");
|
|
2175
2175
|
if (input === "") return {
|
|
2176
2176
|
isMatch: false,
|
|
@@ -2184,8 +2184,8 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
|
|
|
2184
2184
|
output = format ? format(input) : input;
|
|
2185
2185
|
match = output === glob$1;
|
|
2186
2186
|
}
|
|
2187
|
-
if (match === false || opts.capture === true) if (opts.matchBase === true || opts.basename === true) match = picomatch$2.matchBase(input, regex, options, posix$1);
|
|
2188
|
-
else match = regex.exec(output);
|
|
2187
|
+
if (match === false || opts.capture === true) if (opts.matchBase === true || opts.basename === true) match = picomatch$2.matchBase(input, regex$1, options, posix$1);
|
|
2188
|
+
else match = regex$1.exec(output);
|
|
2189
2189
|
return {
|
|
2190
2190
|
isMatch: Boolean(match),
|
|
2191
2191
|
match,
|
|
@@ -2206,8 +2206,8 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
|
|
|
2206
2206
|
* @api public
|
|
2207
2207
|
*/
|
|
2208
2208
|
picomatch$2.matchBase = (input, glob$1, options) => {
|
|
2209
|
-
const regex = glob$1 instanceof RegExp ? glob$1 : picomatch$2.makeRe(glob$1, options);
|
|
2210
|
-
return regex.test(utils$1.basename(input));
|
|
2209
|
+
const regex$1 = glob$1 instanceof RegExp ? glob$1 : picomatch$2.makeRe(glob$1, options);
|
|
2210
|
+
return regex$1.test(utils$1.basename(input));
|
|
2211
2211
|
};
|
|
2212
2212
|
/**
|
|
2213
2213
|
* Returns true if **any** of the given glob `patterns` match the specified `string`.
|
|
@@ -2291,9 +2291,9 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
|
|
|
2291
2291
|
const append = opts.contains ? "" : "$";
|
|
2292
2292
|
let source = `${prepend}(?:${state.output})${append}`;
|
|
2293
2293
|
if (state && state.negated === true) source = `^(?!${source}).*$`;
|
|
2294
|
-
const regex = picomatch$2.toRegex(source, options);
|
|
2295
|
-
if (returnState === true) regex.state = state;
|
|
2296
|
-
return regex;
|
|
2294
|
+
const regex$1 = picomatch$2.toRegex(source, options);
|
|
2295
|
+
if (returnState === true) regex$1.state = state;
|
|
2296
|
+
return regex$1;
|
|
2297
2297
|
};
|
|
2298
2298
|
/**
|
|
2299
2299
|
* Create a regular expression from a parsed glob pattern.
|
|
@@ -2573,14 +2573,14 @@ function getPartialMatcher(patterns, options) {
|
|
|
2573
2573
|
if (inputParts[0] === ".." && ONLY_PARENT_DIRECTORIES.test(input)) return true;
|
|
2574
2574
|
for (let i = 0; i < patterns.length; i++) {
|
|
2575
2575
|
const patternParts = patternsParts[i];
|
|
2576
|
-
const regex = regexes[i];
|
|
2576
|
+
const regex$1 = regexes[i];
|
|
2577
2577
|
const inputPatternCount = inputParts.length;
|
|
2578
2578
|
const minParts = Math.min(inputPatternCount, patternParts.length);
|
|
2579
2579
|
let j = 0;
|
|
2580
2580
|
while (j < minParts) {
|
|
2581
2581
|
const part = patternParts[j];
|
|
2582
2582
|
if (part.includes("/")) return true;
|
|
2583
|
-
const match = regex[j].test(inputParts[j]);
|
|
2583
|
+
const match = regex$1[j].test(inputParts[j]);
|
|
2584
2584
|
if (!match) break;
|
|
2585
2585
|
if (part === "**") return true;
|
|
2586
2586
|
j++;
|
|
@@ -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()),
|
|
@@ -2786,7 +2788,7 @@ const UsageDataSchema = object({
|
|
|
2786
2788
|
costUSD: optional(number())
|
|
2787
2789
|
});
|
|
2788
2790
|
const DailyUsageSchema = object({
|
|
2789
|
-
date: string(),
|
|
2791
|
+
date: pipe(string(), regex(/^\d{4}-\d{2}-\d{2}$/)),
|
|
2790
2792
|
inputTokens: number(),
|
|
2791
2793
|
outputTokens: number(),
|
|
2792
2794
|
cacheCreationTokens: number(),
|
|
@@ -2804,30 +2806,38 @@ const SessionUsageSchema = object({
|
|
|
2804
2806
|
lastActivity: string(),
|
|
2805
2807
|
versions: array(string())
|
|
2806
2808
|
});
|
|
2807
|
-
const
|
|
2809
|
+
const MonthlyUsageSchema = object({
|
|
2810
|
+
month: pipe(string(), regex(/^\d{4}-\d{2}$/)),
|
|
2811
|
+
inputTokens: number(),
|
|
2812
|
+
outputTokens: number(),
|
|
2813
|
+
cacheCreationTokens: number(),
|
|
2814
|
+
cacheReadTokens: number(),
|
|
2815
|
+
totalCost: number()
|
|
2816
|
+
});
|
|
2817
|
+
function formatDate(dateStr) {
|
|
2808
2818
|
const date = new Date(dateStr);
|
|
2809
2819
|
const year = date.getFullYear();
|
|
2810
2820
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
2811
2821
|
const day = String(date.getDate()).padStart(2, "0");
|
|
2812
2822
|
return `${year}-${month}-${day}`;
|
|
2813
|
-
}
|
|
2814
|
-
|
|
2823
|
+
}
|
|
2824
|
+
function calculateCostForEntry(data, mode, modelPricing) {
|
|
2815
2825
|
if (mode === "display") return data.costUSD ?? 0;
|
|
2816
2826
|
if (mode === "calculate") {
|
|
2817
|
-
if (data.message.model) {
|
|
2827
|
+
if (data.message.model != null) {
|
|
2818
2828
|
const pricing = getModelPricing(data.message.model, modelPricing);
|
|
2819
|
-
if (pricing) return calculateCostFromTokens(data.message.usage, pricing);
|
|
2829
|
+
if (pricing != null) return calculateCostFromTokens(data.message.usage, pricing);
|
|
2820
2830
|
}
|
|
2821
2831
|
return 0;
|
|
2822
2832
|
}
|
|
2823
|
-
if (data.costUSD
|
|
2824
|
-
if (data.message.model) {
|
|
2833
|
+
if (data.costUSD != null) return data.costUSD;
|
|
2834
|
+
if (data.message.model != null) {
|
|
2825
2835
|
const pricing = getModelPricing(data.message.model, modelPricing);
|
|
2826
|
-
if (pricing) return calculateCostFromTokens(data.message.usage, pricing);
|
|
2836
|
+
if (pricing != null) return calculateCostFromTokens(data.message.usage, pricing);
|
|
2827
2837
|
}
|
|
2828
2838
|
return 0;
|
|
2829
|
-
}
|
|
2830
|
-
async function
|
|
2839
|
+
}
|
|
2840
|
+
async function loadDailyUsageData(options) {
|
|
2831
2841
|
const claudePath = options?.claudePath ?? getDefaultClaudePath();
|
|
2832
2842
|
const claudeDir = path.join(claudePath, "projects");
|
|
2833
2843
|
const files = await glob(["**/*.jsonl"], {
|
|
@@ -2835,9 +2845,9 @@ async function loadUsageData(options) {
|
|
|
2835
2845
|
absolute: true
|
|
2836
2846
|
});
|
|
2837
2847
|
if (files.length === 0) return [];
|
|
2838
|
-
const mode = options?.mode
|
|
2848
|
+
const mode = options?.mode ?? "auto";
|
|
2839
2849
|
const modelPricing = mode === "display" ? {} : await fetchModelPricing();
|
|
2840
|
-
const
|
|
2850
|
+
const allEntries = [];
|
|
2841
2851
|
for (const file of files) {
|
|
2842
2852
|
const content = await readFile(file, "utf-8");
|
|
2843
2853
|
const lines = content.trim().split("\n").filter((line) => line.length > 0);
|
|
@@ -2847,31 +2857,43 @@ async function loadUsageData(options) {
|
|
|
2847
2857
|
if (!result.success) continue;
|
|
2848
2858
|
const data = result.output;
|
|
2849
2859
|
const date = formatDate(data.timestamp);
|
|
2850
|
-
const existing = dailyMap.get(date) || {
|
|
2851
|
-
date,
|
|
2852
|
-
inputTokens: 0,
|
|
2853
|
-
outputTokens: 0,
|
|
2854
|
-
cacheCreationTokens: 0,
|
|
2855
|
-
cacheReadTokens: 0,
|
|
2856
|
-
totalCost: 0
|
|
2857
|
-
};
|
|
2858
|
-
existing.inputTokens += data.message.usage.input_tokens ?? 0;
|
|
2859
|
-
existing.outputTokens += data.message.usage.output_tokens ?? 0;
|
|
2860
|
-
existing.cacheCreationTokens += data.message.usage.cache_creation_input_tokens ?? 0;
|
|
2861
|
-
existing.cacheReadTokens += data.message.usage.cache_read_input_tokens ?? 0;
|
|
2862
2860
|
const cost = calculateCostForEntry(data, mode, modelPricing);
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2861
|
+
allEntries.push({
|
|
2862
|
+
data,
|
|
2863
|
+
date,
|
|
2864
|
+
cost
|
|
2865
|
+
});
|
|
2866
|
+
} catch {}
|
|
2866
2867
|
}
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
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
|
+
}
|
|
2872
2892
|
return true;
|
|
2873
2893
|
});
|
|
2874
|
-
|
|
2894
|
+
const sortOrder = options?.order ?? "desc";
|
|
2895
|
+
const sortedResults = sort(results);
|
|
2896
|
+
return sortOrder === "desc" ? sortedResults.desc((item) => new Date(item.date).getTime()) : sortedResults.asc((item) => new Date(item.date).getTime());
|
|
2875
2897
|
}
|
|
2876
2898
|
async function loadSessionData(options) {
|
|
2877
2899
|
const claudePath = options?.claudePath ?? getDefaultClaudePath();
|
|
@@ -2881,64 +2903,99 @@ async function loadSessionData(options) {
|
|
|
2881
2903
|
absolute: true
|
|
2882
2904
|
});
|
|
2883
2905
|
if (files.length === 0) return [];
|
|
2884
|
-
const mode = options?.mode
|
|
2906
|
+
const mode = options?.mode ?? "auto";
|
|
2885
2907
|
const modelPricing = mode === "display" ? {} : await fetchModelPricing();
|
|
2886
|
-
const
|
|
2908
|
+
const allEntries = [];
|
|
2887
2909
|
for (const file of files) {
|
|
2888
2910
|
const relativePath = path.relative(claudeDir, file);
|
|
2889
2911
|
const parts = relativePath.split(path.sep);
|
|
2890
|
-
const sessionId = parts[parts.length - 2];
|
|
2891
|
-
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";
|
|
2892
2915
|
const content = await readFile(file, "utf-8");
|
|
2893
2916
|
const lines = content.trim().split("\n").filter((line) => line.length > 0);
|
|
2894
|
-
let lastTimestamp = "";
|
|
2895
2917
|
for (const line of lines) try {
|
|
2896
2918
|
const parsed = JSON.parse(line);
|
|
2897
2919
|
const result = safeParse(UsageDataSchema, parsed);
|
|
2898
2920
|
if (!result.success) continue;
|
|
2899
2921
|
const data = result.output;
|
|
2900
|
-
const
|
|
2901
|
-
const existing = sessionMap.get(key) || {
|
|
2902
|
-
sessionId: sessionId || "unknown",
|
|
2903
|
-
projectPath: projectPath || "Unknown Project",
|
|
2904
|
-
inputTokens: 0,
|
|
2905
|
-
outputTokens: 0,
|
|
2906
|
-
cacheCreationTokens: 0,
|
|
2907
|
-
cacheReadTokens: 0,
|
|
2908
|
-
totalCost: 0,
|
|
2909
|
-
lastActivity: "",
|
|
2910
|
-
versions: [],
|
|
2911
|
-
versionSet: new Set()
|
|
2912
|
-
};
|
|
2913
|
-
existing.inputTokens += data.message.usage.input_tokens ?? 0;
|
|
2914
|
-
existing.outputTokens += data.message.usage.output_tokens ?? 0;
|
|
2915
|
-
existing.cacheCreationTokens += data.message.usage.cache_creation_input_tokens ?? 0;
|
|
2916
|
-
existing.cacheReadTokens += data.message.usage.cache_read_input_tokens ?? 0;
|
|
2922
|
+
const sessionKey = `${projectPath}/${sessionId}`;
|
|
2917
2923
|
const cost = calculateCostForEntry(data, mode, modelPricing);
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2924
|
+
allEntries.push({
|
|
2925
|
+
data,
|
|
2926
|
+
sessionKey,
|
|
2927
|
+
sessionId,
|
|
2928
|
+
projectPath,
|
|
2929
|
+
cost,
|
|
2930
|
+
timestamp: data.timestamp
|
|
2931
|
+
});
|
|
2932
|
+
} catch {}
|
|
2926
2933
|
}
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
return
|
|
2930
|
-
|
|
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),
|
|
2931
2949
|
versions: Array.from(versionSet).sort()
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
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
|
+
}
|
|
2938
2968
|
return true;
|
|
2939
2969
|
});
|
|
2940
|
-
|
|
2970
|
+
const sortOrder = options?.order ?? "desc";
|
|
2971
|
+
const sortedResults = sort(results);
|
|
2972
|
+
return sortOrder === "desc" ? sortedResults.desc((item) => new Date(item.lastActivity).getTime()) : sortedResults.asc((item) => new Date(item.lastActivity).getTime());
|
|
2973
|
+
}
|
|
2974
|
+
async function loadMonthlyUsageData(options) {
|
|
2975
|
+
const dailyData = await loadDailyUsageData(options);
|
|
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
|
+
}), {
|
|
2987
|
+
month,
|
|
2988
|
+
inputTokens: 0,
|
|
2989
|
+
outputTokens: 0,
|
|
2990
|
+
cacheCreationTokens: 0,
|
|
2991
|
+
cacheReadTokens: 0,
|
|
2992
|
+
totalCost: 0
|
|
2993
|
+
});
|
|
2994
|
+
}).filter((item) => item != null);
|
|
2995
|
+
const sortOrder = options?.order ?? "desc";
|
|
2996
|
+
const sortedMonthly = sort(monthlyArray);
|
|
2997
|
+
return sortOrder === "desc" ? sortedMonthly.desc((item) => item.month) : sortedMonthly.asc((item) => item.month);
|
|
2941
2998
|
}
|
|
2942
2999
|
|
|
2943
3000
|
//#endregion
|
|
2944
|
-
export { DailyUsageSchema, SessionUsageSchema, UsageDataSchema, __commonJS, __require, __toESM, calculateCostForEntry, formatDate, getDefaultClaudePath, glob,
|
|
3001
|
+
export { DailyUsageSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, __commonJS, __require, __toESM, calculateCostForEntry, formatDate, getDefaultClaudePath, glob, loadDailyUsageData, loadMonthlyUsageData, loadSessionData };
|