token-rats 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +109 -97
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5256,7 +5256,7 @@ var ApiClient = class {
|
|
|
5256
5256
|
// package.json
|
|
5257
5257
|
var package_default = {
|
|
5258
5258
|
name: "token-rats",
|
|
5259
|
-
version: "0.3.
|
|
5259
|
+
version: "0.3.1",
|
|
5260
5260
|
description: "Track Claude Code, Codex, and Cursor usage and compare AI subscriptions.",
|
|
5261
5261
|
license: "MIT",
|
|
5262
5262
|
type: "module",
|
|
@@ -5414,6 +5414,7 @@ function logoutCommand() {
|
|
|
5414
5414
|
// src/lib/collect.ts
|
|
5415
5415
|
import { createHash } from "node:crypto";
|
|
5416
5416
|
import * as fs4 from "node:fs";
|
|
5417
|
+
import { createInterface } from "node:readline";
|
|
5417
5418
|
|
|
5418
5419
|
// ../parsers/src/hash.ts
|
|
5419
5420
|
var FNV_PRIME = 16777619;
|
|
@@ -5437,30 +5438,24 @@ function computeDedupeKey(source, model, startedAt, inTokens, outTokens) {
|
|
|
5437
5438
|
}
|
|
5438
5439
|
|
|
5439
5440
|
// ../parsers/src/claude-code.ts
|
|
5440
|
-
function
|
|
5441
|
-
let text;
|
|
5442
|
-
if (typeof input === "string") {
|
|
5443
|
-
text = input;
|
|
5444
|
-
} else {
|
|
5445
|
-
text = new TextDecoder().decode(input instanceof ArrayBuffer ? new Uint8Array(input) : input);
|
|
5446
|
-
}
|
|
5441
|
+
function createClaudeCodeParser() {
|
|
5447
5442
|
const sessions = /* @__PURE__ */ new Map();
|
|
5448
5443
|
const messages = /* @__PURE__ */ new Map();
|
|
5449
|
-
|
|
5444
|
+
function push(rawLine) {
|
|
5450
5445
|
const line = rawLine.trim();
|
|
5451
|
-
if (line.length === 0)
|
|
5446
|
+
if (line.length === 0) return;
|
|
5452
5447
|
let event;
|
|
5453
5448
|
try {
|
|
5454
5449
|
event = JSON.parse(line);
|
|
5455
5450
|
} catch {
|
|
5456
|
-
|
|
5451
|
+
return;
|
|
5457
5452
|
}
|
|
5458
|
-
if (typeof event !== "object" || event === null)
|
|
5453
|
+
if (typeof event !== "object" || event === null) return;
|
|
5459
5454
|
const ev = event;
|
|
5460
5455
|
const sidCamel = ev.sessionId;
|
|
5461
5456
|
const sidSnake = ev.session_id;
|
|
5462
5457
|
const sessionId = typeof sidCamel === "string" ? sidCamel : typeof sidSnake === "string" ? sidSnake : null;
|
|
5463
|
-
if (!sessionId)
|
|
5458
|
+
if (!sessionId) return;
|
|
5464
5459
|
const rawTs = ev.timestamp;
|
|
5465
5460
|
let timestamp = 0;
|
|
5466
5461
|
if (typeof rawTs === "number" && Number.isFinite(rawTs)) {
|
|
@@ -5488,9 +5483,9 @@ function parseClaudeCode(input) {
|
|
|
5488
5483
|
if (acc.startedAt === 0 || timestamp < acc.startedAt) acc.startedAt = timestamp;
|
|
5489
5484
|
if (timestamp > acc.endedAt) acc.endedAt = timestamp;
|
|
5490
5485
|
}
|
|
5491
|
-
if (ev.type !== "assistant")
|
|
5486
|
+
if (ev.type !== "assistant") return;
|
|
5492
5487
|
const message = ev.message;
|
|
5493
|
-
if (typeof message !== "object" || message === null)
|
|
5488
|
+
if (typeof message !== "object" || message === null) return;
|
|
5494
5489
|
const msg = message;
|
|
5495
5490
|
if (typeof msg.model === "string" && msg.model.length > 0 && timestamp >= acc.modelAt) {
|
|
5496
5491
|
acc.model = msg.model;
|
|
@@ -5520,38 +5515,41 @@ function parseClaudeCode(input) {
|
|
|
5520
5515
|
if (key) messages.set(key, next);
|
|
5521
5516
|
}
|
|
5522
5517
|
}
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
const
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5518
|
+
function finish() {
|
|
5519
|
+
const results = [];
|
|
5520
|
+
for (const acc of sessions.values()) {
|
|
5521
|
+
const startedAt = acc.startedAt > 0 ? acc.startedAt : acc.endedAt;
|
|
5522
|
+
const endedAt = acc.endedAt > 0 ? acc.endedAt : acc.startedAt;
|
|
5523
|
+
if (startedAt <= 0 || endedAt <= 0) continue;
|
|
5524
|
+
const model = acc.model.length > 0 ? acc.model : "unknown";
|
|
5525
|
+
const costUsdCents = 0;
|
|
5526
|
+
const dedupeKey = computeDedupeKey(
|
|
5527
|
+
"claude-code",
|
|
5528
|
+
model,
|
|
5529
|
+
startedAt,
|
|
5530
|
+
acc.inTokens,
|
|
5531
|
+
acc.outTokens
|
|
5532
|
+
);
|
|
5533
|
+
results.push({
|
|
5534
|
+
id: `claude-code:${acc.sessionId}`,
|
|
5535
|
+
source: "claude-code",
|
|
5536
|
+
provider: "anthropic",
|
|
5537
|
+
client: "claude-code",
|
|
5538
|
+
channel: "cli",
|
|
5539
|
+
model,
|
|
5540
|
+
inTokens: acc.inTokens,
|
|
5541
|
+
outTokens: acc.outTokens,
|
|
5542
|
+
cacheReadTokens: acc.cacheReadTokens,
|
|
5543
|
+
cacheWriteTokens: acc.cacheWriteTokens,
|
|
5544
|
+
costUsdCents,
|
|
5545
|
+
startedAt,
|
|
5546
|
+
endedAt,
|
|
5547
|
+
dedupeKey
|
|
5548
|
+
});
|
|
5549
|
+
}
|
|
5550
|
+
return results;
|
|
5553
5551
|
}
|
|
5554
|
-
return
|
|
5552
|
+
return { push, finish };
|
|
5555
5553
|
}
|
|
5556
5554
|
function toNonNegInt(v) {
|
|
5557
5555
|
if (typeof v !== "number" || !Number.isFinite(v)) return 0;
|
|
@@ -5559,25 +5557,19 @@ function toNonNegInt(v) {
|
|
|
5559
5557
|
}
|
|
5560
5558
|
|
|
5561
5559
|
// ../parsers/src/codex.ts
|
|
5562
|
-
function
|
|
5563
|
-
let text;
|
|
5564
|
-
if (typeof input === "string") {
|
|
5565
|
-
text = input;
|
|
5566
|
-
} else {
|
|
5567
|
-
text = new TextDecoder().decode(input instanceof ArrayBuffer ? new Uint8Array(input) : input);
|
|
5568
|
-
}
|
|
5560
|
+
function createCodexParser() {
|
|
5569
5561
|
const sessions = /* @__PURE__ */ new Map();
|
|
5570
5562
|
let currentSessionId = null;
|
|
5571
|
-
|
|
5563
|
+
function push(rawLine) {
|
|
5572
5564
|
const line = rawLine.trim();
|
|
5573
|
-
if (line.length === 0)
|
|
5565
|
+
if (line.length === 0) return;
|
|
5574
5566
|
let event;
|
|
5575
5567
|
try {
|
|
5576
5568
|
event = JSON.parse(line);
|
|
5577
5569
|
} catch {
|
|
5578
|
-
|
|
5570
|
+
return;
|
|
5579
5571
|
}
|
|
5580
|
-
if (typeof event !== "object" || event === null)
|
|
5572
|
+
if (typeof event !== "object" || event === null) return;
|
|
5581
5573
|
const ev = event;
|
|
5582
5574
|
const ts = parseTimestamp(ev.timestamp);
|
|
5583
5575
|
const type = typeof ev.type === "string" ? ev.type : null;
|
|
@@ -5586,7 +5578,7 @@ function parseCodex(input) {
|
|
|
5586
5578
|
const id = typeof payload.id === "string" ? payload.id : null;
|
|
5587
5579
|
if (!id) {
|
|
5588
5580
|
currentSessionId = null;
|
|
5589
|
-
|
|
5581
|
+
return;
|
|
5590
5582
|
}
|
|
5591
5583
|
currentSessionId = id;
|
|
5592
5584
|
const metaTs = parseTimestamp(payload.timestamp) || ts;
|
|
@@ -5598,9 +5590,9 @@ function parseCodex(input) {
|
|
|
5598
5590
|
acc2.startedAt = metaTs;
|
|
5599
5591
|
}
|
|
5600
5592
|
if (metaTs > acc2.endedAt) acc2.endedAt = metaTs;
|
|
5601
|
-
|
|
5593
|
+
return;
|
|
5602
5594
|
}
|
|
5603
|
-
if (!currentSessionId)
|
|
5595
|
+
if (!currentSessionId) return;
|
|
5604
5596
|
const acc = upsert(sessions, currentSessionId);
|
|
5605
5597
|
if (ts > 0) {
|
|
5606
5598
|
if (acc.startedAt === 0) acc.startedAt = ts;
|
|
@@ -5611,19 +5603,19 @@ function parseCodex(input) {
|
|
|
5611
5603
|
acc.model = payload.model;
|
|
5612
5604
|
acc.modelAt = ts;
|
|
5613
5605
|
}
|
|
5614
|
-
|
|
5606
|
+
return;
|
|
5615
5607
|
}
|
|
5616
5608
|
if (type === "event_msg" && payload && payload.type === "token_count") {
|
|
5617
5609
|
const info2 = payload.info;
|
|
5618
|
-
if (typeof info2 !== "object" || info2 === null)
|
|
5610
|
+
if (typeof info2 !== "object" || info2 === null) return;
|
|
5619
5611
|
const total = info2.total_token_usage;
|
|
5620
|
-
if (typeof total !== "object" || total === null)
|
|
5612
|
+
if (typeof total !== "object" || total === null) return;
|
|
5621
5613
|
const t = total;
|
|
5622
5614
|
const inputTotal = toNonNegInt2(t.input_tokens);
|
|
5623
5615
|
const cachedInput = toNonNegInt2(t.cached_input_tokens);
|
|
5624
5616
|
const output = toNonNegInt2(t.output_tokens);
|
|
5625
5617
|
const reasoning = toNonNegInt2(t.reasoning_output_tokens);
|
|
5626
|
-
if (ts < acc.usageAt)
|
|
5618
|
+
if (ts < acc.usageAt) return;
|
|
5627
5619
|
acc.usageAt = ts;
|
|
5628
5620
|
acc.inTokens = Math.max(0, inputTotal - cachedInput);
|
|
5629
5621
|
acc.outTokens = output;
|
|
@@ -5631,32 +5623,41 @@ function parseCodex(input) {
|
|
|
5631
5623
|
acc.reasoningTokens = reasoning;
|
|
5632
5624
|
}
|
|
5633
5625
|
}
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
const
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5626
|
+
function finish() {
|
|
5627
|
+
const results = [];
|
|
5628
|
+
for (const acc of sessions.values()) {
|
|
5629
|
+
const startedAt = acc.startedAt > 0 ? acc.startedAt : acc.endedAt;
|
|
5630
|
+
const endedAt = acc.endedAt > 0 ? acc.endedAt : acc.startedAt;
|
|
5631
|
+
if (startedAt <= 0 || endedAt <= 0) continue;
|
|
5632
|
+
const model = acc.model.length > 0 ? acc.model : "unknown";
|
|
5633
|
+
const costUsdCents = 0;
|
|
5634
|
+
const dedupeKey = computeDedupeKey("codex", model, startedAt, acc.inTokens, acc.outTokens);
|
|
5635
|
+
results.push({
|
|
5636
|
+
id: `codex:${acc.sessionId}`,
|
|
5637
|
+
source: "codex",
|
|
5638
|
+
provider: "openai",
|
|
5639
|
+
client: acc.client || "codex-cli",
|
|
5640
|
+
channel: acc.channel,
|
|
5641
|
+
model,
|
|
5642
|
+
inTokens: acc.inTokens,
|
|
5643
|
+
outTokens: acc.outTokens,
|
|
5644
|
+
cacheReadTokens: acc.cacheReadTokens,
|
|
5645
|
+
reasoningTokens: acc.reasoningTokens,
|
|
5646
|
+
costUsdCents,
|
|
5647
|
+
startedAt,
|
|
5648
|
+
endedAt,
|
|
5649
|
+
dedupeKey
|
|
5650
|
+
});
|
|
5651
|
+
}
|
|
5652
|
+
return results;
|
|
5658
5653
|
}
|
|
5659
|
-
return
|
|
5654
|
+
return {
|
|
5655
|
+
push,
|
|
5656
|
+
finish,
|
|
5657
|
+
startFile() {
|
|
5658
|
+
currentSessionId = null;
|
|
5659
|
+
}
|
|
5660
|
+
};
|
|
5660
5661
|
}
|
|
5661
5662
|
function upsert(map, sessionId) {
|
|
5662
5663
|
let acc = map.get(sessionId);
|
|
@@ -5978,12 +5979,23 @@ function discoverCodexFiles() {
|
|
|
5978
5979
|
}
|
|
5979
5980
|
|
|
5980
5981
|
// src/lib/collect.ts
|
|
5981
|
-
async function
|
|
5982
|
-
|
|
5983
|
-
|
|
5982
|
+
async function parseSessionFiles(files, parser) {
|
|
5983
|
+
for (const file of files) {
|
|
5984
|
+
parser.startFile?.();
|
|
5985
|
+
const input = fs4.createReadStream(file, { encoding: "utf8" });
|
|
5986
|
+
const lines = createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
5987
|
+
try {
|
|
5988
|
+
for await (const line of lines) parser.push(line);
|
|
5989
|
+
} finally {
|
|
5990
|
+
lines.close();
|
|
5991
|
+
input.destroy();
|
|
5992
|
+
}
|
|
5984
5993
|
}
|
|
5985
|
-
|
|
5986
|
-
|
|
5994
|
+
return parser.finish();
|
|
5995
|
+
}
|
|
5996
|
+
async function collectSessions() {
|
|
5997
|
+
const claude = await parseSessionFiles(discoverClaudeCodeFiles(), createClaudeCodeParser());
|
|
5998
|
+
const codex = await parseSessionFiles(discoverCodexFiles(), createCodexParser());
|
|
5987
5999
|
const { rows } = await extractCursorGenerations();
|
|
5988
6000
|
const cursor = parseCursor(JSON.stringify(rows));
|
|
5989
6001
|
return [...claude, ...codex, ...cursor].map((record) => ({
|
|
@@ -6317,9 +6329,9 @@ function printHelp() {
|
|
|
6317
6329
|
--verbose Print file change events and upload detail
|
|
6318
6330
|
|
|
6319
6331
|
\x1B[1mPrivacy:\x1B[0m
|
|
6320
|
-
Token Rats reads
|
|
6321
|
-
|
|
6322
|
-
|
|
6332
|
+
Token Rats reads local logs and uploads usage metadata only.
|
|
6333
|
+
It does not upload prompts or completions.
|
|
6334
|
+
The parser source is in packages/parsers/.
|
|
6323
6335
|
|
|
6324
6336
|
\x1B[1mCursor notes:\x1B[0m
|
|
6325
6337
|
Cursor doesn't store token counts locally, so per-request tokens
|