Something went wrong. Try again.
Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163#!/usr/bin/env node// Sums Claude Code token usage and cost per model across .claude/sessions/**/*.jsonl.//// Token field mapping (Anthropic usage semantics):// uncachedInputTokens = input_tokens + cache_creation_input_tokens// cachedInputTokens = cache_read_input_tokens// inputTokens = cachedInputTokens + uncachedInputTokens// totalTokens = inputTokens + outputTokens//// Cost model (per-MTok prices as of 2026-07-05):// fresh input → base input rate// cache writes → 1.25x base (5m TTL) or 2x base (1h TTL),// from usage.cache_creation when present// cache reads (cached input) → 0.1x base input rate// output → output rate// The <synthetic> pseudo-model (harness-injected messages) is skipped.
const fs = require("node:fs");const path = require("node:path");const readline = require("node:readline");
const sessionsDir = path.join(process.cwd(), ".claude", "sessions");
// USD per million tokens: [input, output].// Sonnet 5 uses the introductory price ($2/$10), in effect through 2026-08-31// (sticker price is $3/$15).const PRICES = { "claude-fable-5": [10.0, 50.0], "claude-mythos-5": [10.0, 50.0], "claude-opus-4-8": [5.0, 25.0], "claude-opus-4-7": [5.0, 25.0], "claude-opus-4-6": [5.0, 25.0], "claude-opus-4-5": [5.0, 25.0], "claude-sonnet-5": [2.0, 10.0], "claude-sonnet-4-6": [3.0, 15.0], "claude-haiku-4-5": [1.0, 5.0],};
const CACHE_READ_MULT = 0.1;const CACHE_WRITE_5M_MULT = 1.25;const CACHE_WRITE_1H_MULT = 2.0;
function priceFor(model) { if (PRICES[model]) return PRICES[model]; // Match date-suffixed IDs like claude-haiku-4-5-20251001. for (const [id, price] of Object.entries(PRICES)) { if (model.startsWith(`${id}-`)) return price; } return null;}
function* jsonlFiles(dir) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) { yield* jsonlFiles(full); } else if (entry.isFile() && entry.name.endsWith(".jsonl")) { yield full; } }}
const round = (usd) => Math.round(usd * 100) / 100;
async function main() { if (!fs.existsSync(sessionsDir)) { console.error(`no such directory: ${sessionsDir}`); process.exit(1); }
const perModel = {};
for (const file of jsonlFiles(sessionsDir)) { const rl = readline.createInterface({ input: fs.createReadStream(file), crlfDelay: Infinity, });
for await (const line of rl) { if (line.trim() === "") continue;
let entry; try { entry = JSON.parse(line); } catch { continue; // skip malformed lines }
const usage = entry?.message?.usage; const model = entry?.message?.model; if (!usage || !model || model === "<synthetic>") continue;
const stats = (perModel[model] ??= { tokens: { fresh: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0, output: 0, }, }); const t = stats.tokens;
const cacheWrite = usage.cache_creation_input_tokens ?? 0; // TTL breakdown when recorded; older entries without it are treated as // 5m writes (the cheaper assumption). const write1h = usage.cache_creation?.ephemeral_1h_input_tokens ?? 0;
t.fresh += usage.input_tokens ?? 0; t.cacheWrite1h += write1h; t.cacheWrite5m += cacheWrite - write1h; t.cacheRead += usage.cache_read_input_tokens ?? 0; t.output += usage.output_tokens ?? 0; } }
const report = {}; let grandTotal = 0; for (const [model, { tokens: t }] of Object.entries(perModel)) { const uncachedInput = t.fresh + t.cacheWrite5m + t.cacheWrite1h; const entry = { inputTokens: t.cacheRead + uncachedInput, cachedInputTokens: t.cacheRead, uncachedInputTokens: uncachedInput, outputTokens: t.output, totalTokens: t.cacheRead + uncachedInput + t.output, };
const price = priceFor(model); if (price) { const [inputRate, outputRate] = price.map((p) => p / 1_000_000); const uncachedInputCost = t.fresh * inputRate + t.cacheWrite5m * inputRate * CACHE_WRITE_5M_MULT + t.cacheWrite1h * inputRate * CACHE_WRITE_1H_MULT; const cachedInputCost = t.cacheRead * inputRate * CACHE_READ_MULT; const outputCost = t.output * outputRate; entry.uncachedInputCost = round(uncachedInputCost); entry.cachedInputCost = round(cachedInputCost); entry.outputCost = round(outputCost); entry.totalCost = round(uncachedInputCost + cachedInputCost + outputCost); grandTotal += uncachedInputCost + cachedInputCost + outputCost; } else { console.error(`warning: no pricing known for model ${model}`); entry.uncachedInputCost = null; entry.cachedInputCost = null; entry.outputCost = null; entry.totalCost = null; }
report[model] = entry; }
report.totalCostUSD = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(grandTotal);
console.log(JSON.stringify(report, null, 2));}
main();