tokmon 0.21.0 → 0.22.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.
@@ -6,10 +6,10 @@ import {
6
6
  expandHome,
7
7
  isValidTimezone,
8
8
  slugify
9
- } from "./chunk-7MFZMI5C.js";
9
+ } from "./chunk-E25RUTXB.js";
10
10
 
11
11
  // src/providers/usage-core.ts
12
- import { readFile, writeFile, rename, mkdir } from "fs/promises";
12
+ import { readFile, writeFile, rename, mkdir, readdir, unlink } from "fs/promises";
13
13
  import { join } from "path";
14
14
 
15
15
  // src/tz.ts
@@ -149,16 +149,30 @@ var dollars = (cents) => finite(cents) / 100;
149
149
  // src/providers/usage-core.ts
150
150
  var SPARK_DAYS = 14;
151
151
  var DAY_MS = 864e5;
152
- var CACHE_VERSION = 7;
153
- var STABLE_AGE_MS = 5 * 6e4;
154
- var PRUNE_AGE_MS = 200 * DAY_MS;
152
+ var CACHE_VERSION = 8;
153
+ var PRUNE_AGE_MS = 230 * DAY_MS;
155
154
  var memCache = /* @__PURE__ */ new Map();
156
155
  var diskLoaded = false;
156
+ var diskLoadPromise = null;
157
157
  var dirty = false;
158
158
  var flushTimer = null;
159
159
  function cacheFile() {
160
160
  return join(cacheDir(), `usage-v${CACHE_VERSION}.json`);
161
161
  }
162
+ async function cleanupOldCacheFiles() {
163
+ try {
164
+ const dir = cacheDir();
165
+ const names = await readdir(dir);
166
+ await Promise.all(names.map(async (name) => {
167
+ const version = /^usage-v(\d+)\.json$/.exec(name);
168
+ const oldVersion = version ? Number(version[1]) !== CACHE_VERSION : false;
169
+ const tmp = /^usage-v\d+\.json\..*\.tmp$/.test(name);
170
+ if (oldVersion || tmp) await unlink(join(dir, name)).catch(() => {
171
+ });
172
+ }));
173
+ } catch {
174
+ }
175
+ }
162
176
  function encode(mtimeMs, size, entries) {
163
177
  const mods = [];
164
178
  const idx = /* @__PURE__ */ new Map();
@@ -197,23 +211,33 @@ function decode(s) {
197
211
  }
198
212
  async function ensureDiskLoaded() {
199
213
  if (diskLoaded) return;
200
- diskLoaded = true;
201
- try {
202
- const obj = JSON.parse(await readFile(cacheFile(), "utf-8"));
203
- for (const [path, s] of Object.entries(obj)) {
204
- if (s && typeof s.m === "number" && Array.isArray(s.rows) && Array.isArray(s.mods)) {
205
- memCache.set(path, { mtimeMs: s.m, size: typeof s.s === "number" ? s.s : -1, entries: decode(s) });
214
+ if (!diskLoadPromise) {
215
+ diskLoadPromise = (async () => {
216
+ try {
217
+ const obj = JSON.parse(await readFile(cacheFile(), "utf-8"));
218
+ for (const [path, s] of Object.entries(obj)) {
219
+ if (s && typeof s.m === "number" && Array.isArray(s.rows) && Array.isArray(s.mods)) {
220
+ memCache.set(path, { mtimeMs: s.m, size: typeof s.s === "number" ? s.s : -1, entries: decode(s) });
221
+ }
222
+ }
223
+ } catch {
224
+ } finally {
225
+ await cleanupOldCacheFiles();
226
+ diskLoaded = true;
206
227
  }
207
- }
208
- } catch {
228
+ })().finally(() => {
229
+ diskLoadPromise = null;
230
+ });
209
231
  }
232
+ await diskLoadPromise;
210
233
  }
211
234
  async function flushDisk() {
212
235
  if (!dirty) return;
236
+ dirty = false;
213
237
  const now = Date.now();
214
238
  const obj = {};
215
239
  for (const [path, v] of memCache) {
216
- if (now - v.mtimeMs > STABLE_AGE_MS && now - v.mtimeMs < PRUNE_AGE_MS) {
240
+ if (now - v.mtimeMs < PRUNE_AGE_MS) {
217
241
  obj[path] = encode(v.mtimeMs, v.size, v.entries);
218
242
  }
219
243
  }
@@ -222,8 +246,8 @@ async function flushDisk() {
222
246
  const tmp = `${cacheFile()}.${process.pid}.tmp`;
223
247
  await writeFile(tmp, JSON.stringify(obj));
224
248
  await rename(tmp, cacheFile());
225
- dirty = false;
226
249
  } catch {
250
+ dirty = true;
227
251
  }
228
252
  }
229
253
  function scheduleFlush() {
@@ -231,9 +255,29 @@ function scheduleFlush() {
231
255
  flushTimer = setTimeout(() => {
232
256
  flushTimer = null;
233
257
  void flushDisk();
234
- }, 4e3);
258
+ }, 3e4);
235
259
  flushTimer.unref?.();
236
260
  }
261
+ async function walkFiles(root) {
262
+ const files = [];
263
+ const stack = [""];
264
+ while (stack.length > 0) {
265
+ const rel = stack.pop() ?? "";
266
+ const dir = rel ? join(root, rel) : root;
267
+ let entries;
268
+ try {
269
+ entries = await readdir(dir, { withFileTypes: true });
270
+ } catch {
271
+ continue;
272
+ }
273
+ for (const entry of entries) {
274
+ const child = rel ? join(rel, entry.name) : entry.name;
275
+ if (entry.isDirectory()) stack.push(child);
276
+ else if (entry.isFile()) files.push(child);
277
+ }
278
+ }
279
+ return files;
280
+ }
237
281
  async function mapLimit(items, limit, fn) {
238
282
  let i = 0;
239
283
  const worker = async () => {
@@ -251,7 +295,7 @@ async function loadCachedEntries(files, parse, since) {
251
295
  const entries = await parse(f.path);
252
296
  c = { mtimeMs: f.mtimeMs, size: f.size, entries };
253
297
  memCache.set(f.path, c);
254
- if (Date.now() - f.mtimeMs > STABLE_AGE_MS) dirty = true;
298
+ dirty = true;
255
299
  }
256
300
  chunks.push(c.entries);
257
301
  } catch {
@@ -289,17 +333,14 @@ function cleanEntry(e) {
289
333
  };
290
334
  }
291
335
  function dedupe(entries) {
292
- const seen = /* @__PURE__ */ new Set();
293
- const out = [];
336
+ const byKey = /* @__PURE__ */ new Map();
294
337
  for (const raw of entries) {
295
338
  const e = cleanEntry(raw);
296
339
  if (e.ts <= 0) continue;
297
340
  const k = e.id ?? `${e.ts} ${e.model} ${e.input} ${e.output} ${e.cacheCreate} ${e.cacheRead} ${e.cost}`;
298
- if (seen.has(k)) continue;
299
- seen.add(k);
300
- out.push(e);
341
+ byKey.set(k, e);
301
342
  }
302
- return out;
343
+ return [...byKey.values()];
303
344
  }
304
345
  function summarize(entries, tz) {
305
346
  const now = Date.now();
@@ -992,7 +1033,7 @@ async function cursorUsageTable(tz, homeDir) {
992
1033
  }
993
1034
 
994
1035
  // src/providers/claude/usage.ts
995
- import { readdir, stat as fsStat, access as access2 } from "fs/promises";
1036
+ import { stat as fsStat, access as access2 } from "fs/promises";
996
1037
  import { createReadStream } from "fs";
997
1038
  import { createInterface } from "readline";
998
1039
  import { join as join5, isAbsolute } from "path";
@@ -1004,7 +1045,8 @@ var PRICING = {
1004
1045
  "claude-opus-4": { i: 5e-6, o: 25e-6, cc: 625e-8, cr: 5e-7 },
1005
1046
  "claude-3-opus": { i: 15e-6, o: 75e-6, cc: 1875e-8, cr: 15e-7 },
1006
1047
  "claude-sonnet-4": { i: 3e-6, o: 15e-6, cc: 375e-8, cr: 3e-7 },
1007
- "claude-sonnet-5": { i: 3e-6, o: 15e-6, cc: 375e-8, cr: 3e-7 },
1048
+ // intro pricing through 2026-08-31 revert to 3/15/3.75/0.3 after.
1049
+ "claude-sonnet-5": { i: 2e-6, o: 1e-5, cc: 25e-7, cr: 2e-7 },
1008
1050
  "claude-haiku-4": { i: 1e-6, o: 5e-6, cc: 125e-8, cr: 1e-7 },
1009
1051
  "claude-fable-5": { i: 1e-5, o: 5e-5, cc: 125e-7, cr: 1e-6 }
1010
1052
  };
@@ -1019,7 +1061,8 @@ function claudeConfigDirs(homeDir) {
1019
1061
  const xdg = envDir("XDG_CONFIG_HOME");
1020
1062
  if (xdg) {
1021
1063
  dirs.push(join5(xdg, "claude"));
1022
- } else if (process.platform !== "win32") {
1064
+ }
1065
+ if (process.platform !== "win32") {
1023
1066
  dirs.push(join5(home, ".config", "claude"));
1024
1067
  }
1025
1068
  const appData = envDir("APPDATA");
@@ -1054,9 +1097,10 @@ function priceFor(model) {
1054
1097
  }
1055
1098
  return ZERO_PRICE;
1056
1099
  }
1057
- function costOf(model, u) {
1100
+ function costOf(model, u, cacheCreate5m, cacheCreate1h, hasCacheCreateSplit) {
1058
1101
  const p = priceFor(model);
1059
- return safeNum(u.input_tokens) * p.i + safeNum(u.output_tokens) * p.o + safeNum(u.cache_creation_input_tokens) * p.cc + safeNum(u.cache_read_input_tokens) * p.cr;
1102
+ const cacheCreateCost = hasCacheCreateSplit ? cacheCreate5m * p.cc + cacheCreate1h * (2 * p.i) : safeNum(u.cache_creation_input_tokens) * p.cc;
1103
+ return safeNum(u.input_tokens) * p.i + safeNum(u.output_tokens) * p.o + cacheCreateCost + safeNum(u.cache_read_input_tokens) * p.cr;
1060
1104
  }
1061
1105
  function shortModel(model) {
1062
1106
  return model.replace("claude-", "").replace(/-\d{8}$/, "");
@@ -1084,7 +1128,10 @@ async function parseFile(path) {
1084
1128
  const model = typeof obj.message.model === "string" && obj.message.model ? obj.message.model : "unknown";
1085
1129
  const inputTokens = safeNum(u.input_tokens);
1086
1130
  const output = safeNum(u.output_tokens);
1087
- const cacheCreate = safeNum(u.cache_creation_input_tokens);
1131
+ const hasCacheCreateSplit = u.cache_creation?.ephemeral_5m_input_tokens !== void 0 || u.cache_creation?.ephemeral_1h_input_tokens !== void 0;
1132
+ const cacheCreate5m = safeNum(u.cache_creation?.ephemeral_5m_input_tokens);
1133
+ const cacheCreate1h = safeNum(u.cache_creation?.ephemeral_1h_input_tokens);
1134
+ const cacheCreate = hasCacheCreateSplit ? cacheCreate5m + cacheCreate1h : safeNum(u.cache_creation_input_tokens);
1088
1135
  const cacheRead = safeNum(u.cache_read_input_tokens);
1089
1136
  if (inputTokens + output + cacheCreate + cacheRead === 0) continue;
1090
1137
  const p = priceFor(model);
@@ -1093,7 +1140,7 @@ async function parseFile(path) {
1093
1140
  id: msgId ? msgId + (obj.requestId ? ":" + obj.requestId : "") : void 0,
1094
1141
  ts,
1095
1142
  model: shortModel(model),
1096
- cost: costOf(model, u),
1143
+ cost: costOf(model, u, cacheCreate5m, cacheCreate1h, hasCacheCreateSplit),
1097
1144
  input: inputTokens,
1098
1145
  output,
1099
1146
  cacheCreate,
@@ -1112,12 +1159,7 @@ async function loadEntries(since, homeDir) {
1112
1159
  const seen = /* @__PURE__ */ new Set();
1113
1160
  const seenIno = /* @__PURE__ */ new Set();
1114
1161
  for (const dir of getClaudeDirs(homeDir)) {
1115
- let listing;
1116
- try {
1117
- listing = await readdir(dir, { recursive: true });
1118
- } catch {
1119
- continue;
1120
- }
1162
+ const listing = await walkFiles(dir);
1121
1163
  for (const f of listing) {
1122
1164
  if (!f.endsWith(".jsonl")) continue;
1123
1165
  const path = join5(dir, f);
@@ -1148,7 +1190,7 @@ async function claudeTable(tz, homeDir) {
1148
1190
  }
1149
1191
 
1150
1192
  // src/providers/claude/billing.ts
1151
- import { readFile as readFile2 } from "fs/promises";
1193
+ import { access as access3, readFile as readFile2 } from "fs/promises";
1152
1194
  import { join as join7 } from "path";
1153
1195
  import { homedir as homedir5 } from "os";
1154
1196
 
@@ -1172,6 +1214,23 @@ async function readMacKeychainRaw(service) {
1172
1214
  return null;
1173
1215
  }
1174
1216
  }
1217
+ async function readMacKeychainFileRaw(service, keychainPath) {
1218
+ if (process.platform !== "darwin") return null;
1219
+ try {
1220
+ await execFile2("security", ["unlock-keychain", "-p", "", keychainPath], { timeout: 5e3 });
1221
+ const { stdout } = await execFile2("security", [
1222
+ "find-generic-password",
1223
+ "-s",
1224
+ service,
1225
+ "-w",
1226
+ keychainPath
1227
+ ], { timeout: 5e3 });
1228
+ const raw = stdout.trim();
1229
+ return raw || null;
1230
+ } catch {
1231
+ return null;
1232
+ }
1233
+ }
1175
1234
  function unwrapGoKeyringBase64(raw) {
1176
1235
  if (!raw.startsWith(GO_KEYRING_PREFIX)) return raw;
1177
1236
  return Buffer.from(raw.slice(GO_KEYRING_PREFIX.length), "base64").toString("utf-8");
@@ -1237,13 +1296,29 @@ async function readMacKeychain() {
1237
1296
  const raw = await readMacKeychainRaw("Claude Code-credentials");
1238
1297
  return raw ? parseAuth(raw) : null;
1239
1298
  }
1299
+ async function readHomeKeychain(homeDir) {
1300
+ if (process.platform !== "darwin") return null;
1301
+ const keychainPath = join7(homeDir, "Library", "Keychains", "login.keychain-db");
1302
+ try {
1303
+ await access3(keychainPath);
1304
+ } catch {
1305
+ return null;
1306
+ }
1307
+ const raw = await readMacKeychainFileRaw("Claude Code-credentials", keychainPath);
1308
+ return raw ? parseAuth(raw) : null;
1309
+ }
1240
1310
  async function authCandidates(homeDir) {
1241
1311
  const expandedHomeDir = homeDir ? expandHome(homeDir) : void 0;
1242
1312
  const isDefault = !expandedHomeDir || expandedHomeDir === homedir5();
1243
1313
  const out = [];
1244
1314
  const file = await readCredentialsFile(isDefault ? void 0 : expandedHomeDir);
1245
1315
  const keychain = process.platform === "darwin" ? await readMacKeychain() : null;
1246
- const ordered = isDefault ? [keychain && { auth: keychain, shared: true }, file && { auth: file, shared: false }] : [file && { auth: file, shared: false }, keychain && { auth: keychain, shared: true }];
1316
+ const homeKeychain = !isDefault && expandedHomeDir ? await readHomeKeychain(expandedHomeDir) : null;
1317
+ const ordered = isDefault ? [keychain && { auth: keychain, shared: true }, file && { auth: file, shared: false }] : [
1318
+ homeKeychain && { auth: homeKeychain, shared: false },
1319
+ file && { auth: file, shared: false },
1320
+ keychain && { auth: keychain, shared: true }
1321
+ ];
1247
1322
  for (const c of ordered) if (c) out.push(c);
1248
1323
  return out;
1249
1324
  }
@@ -1282,9 +1357,11 @@ async function getAuth(homeDir, expectedUuid) {
1282
1357
  const candidates = await authCandidates(homeDir);
1283
1358
  let wrongAccountEmail;
1284
1359
  let sawExpired = false;
1360
+ let sawExpiredOwn = false;
1285
1361
  for (const { auth, shared } of candidates) {
1286
1362
  if (auth.expiresAt !== void 0 && auth.expiresAt < Date.now() - 6e4) {
1287
1363
  sawExpired = true;
1364
+ if (!shared) sawExpiredOwn = true;
1288
1365
  continue;
1289
1366
  }
1290
1367
  if (!shared || !expectedUuid) return { auth };
@@ -1294,6 +1371,7 @@ async function getAuth(homeDir, expectedUuid) {
1294
1371
  if (identity.accountUuid === expectedUuid) return { auth };
1295
1372
  wrongAccountEmail = identity.email;
1296
1373
  }
1374
+ if (sawExpiredOwn) return { auth: null, expired: true };
1297
1375
  if (wrongAccountEmail !== void 0) return { auth: null, wrongAccountEmail };
1298
1376
  return { auth: null, expired: sawExpired };
1299
1377
  }
@@ -1315,14 +1393,83 @@ function resetFrom(value) {
1315
1393
  if (typeof value === "string" && value.trim()) return resetIn(value);
1316
1394
  const n = numberValue(value);
1317
1395
  if (n === void 0) return null;
1318
- const ms = Math.abs(n) < 1e10 ? n * 1e3 : n;
1319
- return resetIn(new Date(ms).toISOString());
1396
+ const iso = msToIso(Math.abs(n) < 1e10 ? n * 1e3 : n);
1397
+ return iso ? resetIn(iso) : null;
1320
1398
  }
1321
1399
  function usageMetric(label, window, primary) {
1322
1400
  const used = numberValue(window?.utilization);
1323
1401
  if (used === void 0) return null;
1324
1402
  return { ...pct(used, resetFrom(window?.resets_at), primary), label };
1325
1403
  }
1404
+ function recordValue(value) {
1405
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1406
+ }
1407
+ function nonEmptyString(value) {
1408
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1409
+ }
1410
+ function titleCaseWords(value) {
1411
+ return value.split("_").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1412
+ }
1413
+ function limitLabel(entry) {
1414
+ const scope = recordValue(entry.scope);
1415
+ const model = recordValue(scope?.model);
1416
+ const displayName = nonEmptyString(model?.display_name);
1417
+ if (displayName) return displayName;
1418
+ const kind = nonEmptyString(entry.kind);
1419
+ const normalizedKind = kind?.toLowerCase();
1420
+ if (normalizedKind === "session") return "Session";
1421
+ if (normalizedKind === "weekly_all") return "Weekly";
1422
+ const source = kind ?? nonEmptyString(entry.group);
1423
+ return source ? titleCaseWords(source) : null;
1424
+ }
1425
+ function limitIsSession(entry) {
1426
+ const o = recordValue(entry);
1427
+ if (!o) return false;
1428
+ return nonEmptyString(o.group)?.toLowerCase() === "session" || nonEmptyString(o.kind)?.toLowerCase() === "session";
1429
+ }
1430
+ function limitMetric(entry, primary) {
1431
+ const o = recordValue(entry);
1432
+ if (!o) return null;
1433
+ const used = numberValue(o.percent);
1434
+ if (used === void 0) return null;
1435
+ const label = limitLabel(o);
1436
+ if (!label) return null;
1437
+ return percentMetric(label, used, resetFrom(o.resets_at), primary);
1438
+ }
1439
+ function limitMetrics(limits) {
1440
+ if (!Array.isArray(limits)) return [];
1441
+ const metrics = [];
1442
+ let sawSession = false;
1443
+ for (const entry of limits) {
1444
+ const primary = limitIsSession(entry) && !sawSession;
1445
+ const metric = limitMetric(entry, primary ? true : void 0);
1446
+ if (limitIsSession(entry)) sawSession = true;
1447
+ if (metric) metrics.push(metric);
1448
+ }
1449
+ return metrics;
1450
+ }
1451
+ function usageLabelFromKey(key) {
1452
+ if (key === "five_hour") return "Session";
1453
+ if (key === "seven_day") return "Weekly";
1454
+ if (key.startsWith("seven_day_")) return titleCaseWords(key.slice("seven_day_".length));
1455
+ return titleCaseWords(key);
1456
+ }
1457
+ function topLevelUsageMetrics(data) {
1458
+ const metrics = [];
1459
+ for (const [key, value] of Object.entries(data)) {
1460
+ if (key === "extra_usage" || key === "spend") continue;
1461
+ const window = recordValue(value);
1462
+ if (!window || numberValue(window.utilization) === void 0) continue;
1463
+ const metric = usageMetric(usageLabelFromKey(key), window, key === "five_hour" ? true : void 0);
1464
+ if (metric) metrics.push(metric);
1465
+ }
1466
+ return metrics;
1467
+ }
1468
+ function decimalScale(value) {
1469
+ const n = numberValue(value);
1470
+ const places = n !== void 0 && Number.isInteger(n) ? Math.min(4, Math.max(0, n)) : 2;
1471
+ return 10 ** places;
1472
+ }
1326
1473
  async function claudeBilling(account) {
1327
1474
  const identity = readClaudeIdentity(account.homeDir);
1328
1475
  const { auth, wrongAccountEmail, expired } = await getAuth(account.homeDir, identity.accountUuid);
@@ -1348,22 +1495,18 @@ async function claudeBilling(account) {
1348
1495
  if (res.status === 401) return { plan, metrics: [], error: "Token expired \u2014 run claude to refresh", ...identityFields(identity) };
1349
1496
  if (!res.ok) return { plan, metrics: [], error: `API ${res.status}`, ...identityFields(identity) };
1350
1497
  const data = await readJson(res);
1351
- if (!data) return { plan, metrics: [], error: "Unexpected API response", ...identityFields(identity) };
1352
- const metrics = [];
1353
- const fiveHour = usageMetric("Session", data.five_hour, true);
1354
- if (fiveHour) metrics.push(fiveHour);
1355
- const sevenDay = usageMetric("Weekly", data.seven_day);
1356
- if (sevenDay) metrics.push(sevenDay);
1357
- const sevenDaySonnet = usageMetric("Sonnet", data.seven_day_sonnet);
1358
- if (sevenDaySonnet) metrics.push(sevenDaySonnet);
1498
+ if (!data || typeof data !== "object" || Array.isArray(data)) return { plan, metrics: [], error: "Unexpected API response", ...identityFields(identity) };
1499
+ const metrics = limitMetrics(data.limits);
1500
+ if (metrics.length === 0) metrics.push(...topLevelUsageMetrics(data));
1359
1501
  if (boolValue(data.extra_usage?.is_enabled)) {
1360
1502
  const usedCredits = numberValue(data.extra_usage?.used_credits);
1361
1503
  const monthlyLimit = numberValue(data.extra_usage?.monthly_limit);
1362
1504
  if (usedCredits !== void 0 && (usedCredits > 0 || monthlyLimit !== void 0 && monthlyLimit > 0)) {
1505
+ const scale = decimalScale(data.extra_usage?.decimal_places);
1363
1506
  metrics.push({
1364
1507
  label: "Extra",
1365
- used: finite(usedCredits) / 100,
1366
- limit: monthlyLimit !== void 0 && monthlyLimit > 0 ? monthlyLimit / 100 : null,
1508
+ used: finite(usedCredits) / scale,
1509
+ limit: monthlyLimit !== void 0 && monthlyLimit > 0 ? monthlyLimit / scale : null,
1367
1510
  format: { kind: "dollars", currency: data.extra_usage?.currency ?? "USD" }
1368
1511
  });
1369
1512
  }
@@ -1388,7 +1531,7 @@ var claudeProvider = {
1388
1531
  };
1389
1532
 
1390
1533
  // src/providers/codex/usage.ts
1391
- import { readdir as readdir2, stat as fsStat2, access as access3, open as openFile } from "fs/promises";
1534
+ import { stat as fsStat2, access as access4, open as openFile } from "fs/promises";
1392
1535
  import { createReadStream as createReadStream2 } from "fs";
1393
1536
  import { createInterface as createInterface2 } from "readline";
1394
1537
  import { join as join8 } from "path";
@@ -1398,6 +1541,11 @@ var PRICING2 = {
1398
1541
  "gpt-5.5": { in: 5e-6, cr: 5e-7, out: 3e-5 },
1399
1542
  "gpt-5.4-codex": { in: 25e-7, cr: 25e-8, out: 15e-6 },
1400
1543
  "gpt-5.4": { in: 25e-7, cr: 25e-8, out: 15e-6 },
1544
+ "gpt-5.3-codex": { in: 175e-8, cr: 175e-9, out: 14e-6 },
1545
+ "gpt-5.3": { in: 175e-8, cr: 175e-9, out: 14e-6 },
1546
+ "gpt-5.2-codex": { in: 175e-8, cr: 175e-9, out: 14e-6 },
1547
+ "gpt-5.2": { in: 175e-8, cr: 175e-9, out: 14e-6 },
1548
+ "gpt-5.1": { in: 125e-8, cr: 125e-9, out: 1e-5 },
1401
1549
  "gpt-5-codex": { in: 125e-8, cr: 125e-9, out: 1e-5 },
1402
1550
  "gpt-5-mini": { in: 25e-8, cr: 25e-9, out: 2e-6 },
1403
1551
  "gpt-5-nano": { in: 5e-8, cr: 5e-9, out: 4e-7 },
@@ -1418,12 +1566,12 @@ function codexHomes(homeDir) {
1418
1566
  async function detectCodex(homeDir) {
1419
1567
  for (const home of codexHomes(homeDir)) {
1420
1568
  try {
1421
- await access3(join8(home, "sessions"));
1569
+ await access4(join8(home, "sessions"));
1422
1570
  return true;
1423
1571
  } catch {
1424
1572
  }
1425
1573
  try {
1426
- await access3(join8(home, "archived_sessions"));
1574
+ await access4(join8(home, "archived_sessions"));
1427
1575
  return true;
1428
1576
  } catch {
1429
1577
  }
@@ -1658,12 +1806,7 @@ async function loadEntries2(since, homeDir) {
1658
1806
  const seenIno = /* @__PURE__ */ new Set();
1659
1807
  for (const home of codexHomes(homeDir)) {
1660
1808
  for (const dir of [join8(home, "sessions"), join8(home, "archived_sessions")]) {
1661
- let listing;
1662
- try {
1663
- listing = await readdir2(dir, { recursive: true });
1664
- } catch {
1665
- continue;
1666
- }
1809
+ const listing = await walkFiles(dir);
1667
1810
  for (const f of listing) {
1668
1811
  if (!f.endsWith(".jsonl")) continue;
1669
1812
  const path = join8(dir, f);
@@ -1671,6 +1814,7 @@ async function loadEntries2(since, homeDir) {
1671
1814
  seen.add(path);
1672
1815
  try {
1673
1816
  const s = await fsStat2(path);
1817
+ if (s.mtimeMs < since) continue;
1674
1818
  if (s.ino && process.platform !== "win32") {
1675
1819
  const idn = `${s.dev}:${s.ino}`;
1676
1820
  if (seenIno.has(idn)) continue;
@@ -1694,7 +1838,7 @@ async function codexTable(tz, homeDir) {
1694
1838
  }
1695
1839
 
1696
1840
  // src/providers/codex/billing.ts
1697
- import { readFile as readFile3, readdir as readdir3, stat as fsStat3 } from "fs/promises";
1841
+ import { readFile as readFile3, readdir as readdir2, stat as fsStat3 } from "fs/promises";
1698
1842
  import { createReadStream as createReadStream3 } from "fs";
1699
1843
  import { createInterface as createInterface3 } from "readline";
1700
1844
  import { join as join9 } from "path";
@@ -1885,7 +2029,7 @@ async function newestRolloutFiles(homeDir) {
1885
2029
  const dir = join9(home, "sessions");
1886
2030
  let listing;
1887
2031
  try {
1888
- listing = await readdir3(dir, { recursive: true });
2032
+ listing = await readdir2(dir, { recursive: true });
1889
2033
  } catch {
1890
2034
  continue;
1891
2035
  }
@@ -2133,7 +2277,7 @@ var cursorProvider = {
2133
2277
  };
2134
2278
 
2135
2279
  // src/providers/pi/usage.ts
2136
- import { readdir as readdir4, stat as fsStat4, access as access4 } from "fs/promises";
2280
+ import { stat as fsStat4, access as access5 } from "fs/promises";
2137
2281
  import { createReadStream as createReadStream4 } from "fs";
2138
2282
  import { createInterface as createInterface4 } from "readline";
2139
2283
  import { join as join10 } from "path";
@@ -2143,7 +2287,7 @@ function piSessionsDir(homeDir) {
2143
2287
  }
2144
2288
  async function detectPi(homeDir) {
2145
2289
  try {
2146
- await access4(piSessionsDir(homeDir));
2290
+ await access5(piSessionsDir(homeDir));
2147
2291
  return true;
2148
2292
  } catch {
2149
2293
  return false;
@@ -2196,12 +2340,7 @@ async function loadEntries3(since, homeDir) {
2196
2340
  const dir = piSessionsDir(homeDir);
2197
2341
  const files = [];
2198
2342
  const seenIno = /* @__PURE__ */ new Set();
2199
- let listing;
2200
- try {
2201
- listing = await readdir4(dir, { recursive: true });
2202
- } catch {
2203
- return [];
2204
- }
2343
+ const listing = await walkFiles(dir);
2205
2344
  for (const f of listing) {
2206
2345
  if (!f.endsWith(".jsonl")) continue;
2207
2346
  const path = join10(dir, f);
@@ -2239,7 +2378,7 @@ var piProvider = {
2239
2378
  };
2240
2379
 
2241
2380
  // src/providers/opencode/usage.ts
2242
- import { access as access5 } from "fs/promises";
2381
+ import { access as access6 } from "fs/promises";
2243
2382
  import { join as join11 } from "path";
2244
2383
  import { homedir as homedir8 } from "os";
2245
2384
  function opencodeDbPaths(homeDir) {
@@ -2257,7 +2396,7 @@ function opencodeDbPaths(homeDir) {
2257
2396
  async function findDb(homeDir) {
2258
2397
  for (const p of opencodeDbPaths(homeDir)) {
2259
2398
  try {
2260
- await access5(p);
2399
+ await access6(p);
2261
2400
  return p;
2262
2401
  } catch {
2263
2402
  }
@@ -2315,7 +2454,7 @@ var opencodeProvider = {
2315
2454
  };
2316
2455
 
2317
2456
  // src/providers/copilot/billing.ts
2318
- import { access as access6, readFile as readFile4, readdir as readdir5 } from "fs/promises";
2457
+ import { access as access7, readFile as readFile4, readdir as readdir3 } from "fs/promises";
2319
2458
  import { join as join12 } from "path";
2320
2459
  import { homedir as homedir9 } from "os";
2321
2460
  var USAGE_URL3 = "https://api.github.com/copilot_internal/user";
@@ -2348,7 +2487,7 @@ async function detectCopilot(homeDir) {
2348
2487
  }
2349
2488
  for (const path of candidates) {
2350
2489
  try {
2351
- await access6(path);
2490
+ await access7(path);
2352
2491
  return true;
2353
2492
  } catch {
2354
2493
  }
@@ -2425,7 +2564,7 @@ async function loadTokenFromVsCode(homeDir) {
2425
2564
  join12(userDir, "globalStorage", "state.vscdb")
2426
2565
  ];
2427
2566
  try {
2428
- for (const dirent of await readdir5(join12(userDir, "globalStorage"), { withFileTypes: true })) {
2567
+ for (const dirent of await readdir3(join12(userDir, "globalStorage"), { withFileTypes: true })) {
2429
2568
  if (dirent.isDirectory() && dirent.name.toLowerCase().includes("github")) {
2430
2569
  candidates.push(join12(userDir, "globalStorage", dirent.name, "auth.json"));
2431
2570
  }
@@ -2565,12 +2704,12 @@ var copilotProvider = {
2565
2704
  };
2566
2705
 
2567
2706
  // src/providers/antigravity/billing.ts
2568
- import { access as access7, readdir as readdir7 } from "fs/promises";
2707
+ import { access as access8, readdir as readdir5 } from "fs/promises";
2569
2708
  import { join as join14 } from "path";
2570
2709
  import { homedir as homedir11 } from "os";
2571
2710
 
2572
2711
  // src/providers/cloud-code/auth.ts
2573
- import { readFile as readFile5, readdir as readdir6, realpath, stat } from "fs/promises";
2712
+ import { readFile as readFile5, readdir as readdir4, realpath, stat } from "fs/promises";
2574
2713
  import { spawnSync } from "child_process";
2575
2714
  import { homedir as homedir10 } from "os";
2576
2715
  import { dirname, join as join13 } from "path";
@@ -2722,7 +2861,7 @@ async function resolveBundleDir(candidate) {
2722
2861
  async function scanBundleDir(dir) {
2723
2862
  let entries;
2724
2863
  try {
2725
- entries = await readdir6(dir);
2864
+ entries = await readdir4(dir);
2726
2865
  } catch {
2727
2866
  return null;
2728
2867
  }
@@ -2967,7 +3106,7 @@ function cloudCodeSqliteError(status) {
2967
3106
  // src/providers/antigravity/billing.ts
2968
3107
  async function exists(path) {
2969
3108
  try {
2970
- await access7(path);
3109
+ await access8(path);
2971
3110
  return true;
2972
3111
  } catch {
2973
3112
  return false;
@@ -2989,7 +3128,7 @@ async function antigravityStateDb(homeDir) {
2989
3128
  join14(support, "Antigravity", ...tail)
2990
3129
  ];
2991
3130
  try {
2992
- const entries = await readdir7(support, { withFileTypes: true });
3131
+ const entries = await readdir5(support, { withFileTypes: true });
2993
3132
  const matches = entries.filter((e) => e.isDirectory() && e.name.includes("Antigravity")).map((e) => join14(support, e.name, ...tail));
2994
3133
  return firstExisting([...exact, ...matches]);
2995
3134
  } catch {
@@ -3037,12 +3176,12 @@ var antigravityProvider = {
3037
3176
  };
3038
3177
 
3039
3178
  // src/providers/gemini/billing.ts
3040
- import { access as access8, readFile as readFile6, readdir as readdir9 } from "fs/promises";
3179
+ import { access as access9, readFile as readFile7, readdir as readdir6 } from "fs/promises";
3041
3180
  import { join as join16 } from "path";
3042
3181
  import { homedir as homedir13 } from "os";
3043
3182
 
3044
3183
  // src/providers/gemini/usage.ts
3045
- import { readdir as readdir8, stat as fsStat5 } from "fs/promises";
3184
+ import { readFile as readFile6, stat as fsStat5 } from "fs/promises";
3046
3185
  import { createReadStream as createReadStream5 } from "fs";
3047
3186
  import { createInterface as createInterface5 } from "readline";
3048
3187
  import { join as join15 } from "path";
@@ -3053,8 +3192,8 @@ var PRICING3 = {
3053
3192
  "gemini-3-pro-preview": { in: 2e-6, out: 12e-6, cr: 2e-7 },
3054
3193
  "gemini-3-pro": { in: 2e-6, out: 12e-6, cr: 2e-7 },
3055
3194
  "gemini-3.5-flash": { in: 15e-7, out: 9e-6, cr: 15e-8 },
3056
- "gemini-3-flash-preview": { in: 15e-7, out: 9e-6, cr: 15e-8 },
3057
- "gemini-3-flash": { in: 15e-7, out: 9e-6, cr: 15e-8 },
3195
+ "gemini-3-flash-preview": { in: 5e-7, out: 3e-6, cr: 5e-8 },
3196
+ "gemini-3-flash": { in: 5e-7, out: 3e-6, cr: 5e-8 },
3058
3197
  "gemini-2.5-flash-lite": { in: 1e-7, out: 4e-7, cr: 1e-8 },
3059
3198
  "gemini-3.1-flash-lite": { in: 1e-7, out: 4e-7, cr: 1e-8 },
3060
3199
  "gemini-2.5-flash": { in: 3e-7, out: 25e-7, cr: 3e-8 },
@@ -3081,52 +3220,90 @@ function shortModel2(model) {
3081
3220
  function isGeminiSessionFile(path) {
3082
3221
  return /(^|[\\/])chats[\\/]session-.*\.jsonl$/.test(path) || /(^|[\\/])chats[\\/]session-.*\.json$/.test(path);
3083
3222
  }
3084
- async function parseFile4(path) {
3223
+ function entryFromObject(obj) {
3224
+ if (obj.sessionId && obj.kind || obj.$set || obj.$rewindTo) return null;
3225
+ if (obj.type !== "gemini" || !obj.tokens) return null;
3226
+ const ts = Date.parse(obj.timestamp ?? "");
3227
+ if (!Number.isFinite(ts)) return null;
3228
+ const t = obj.tokens;
3229
+ const input = Math.max(0, safeNum(t.input) + safeNum(t.tool) - safeNum(t.cached));
3230
+ const output = safeNum(t.output) + safeNum(t.thoughts);
3231
+ const cacheRead = safeNum(t.cached);
3232
+ if (input + output + cacheRead === 0) return null;
3233
+ const model = typeof obj.model === "string" && obj.model ? obj.model : "unknown";
3234
+ const p = priceFor3(model);
3235
+ return {
3236
+ id: typeof obj.id === "string" ? obj.id : void 0,
3237
+ ts,
3238
+ model: shortModel2(model),
3239
+ cost: input * p.in + cacheRead * p.cr + output * p.out,
3240
+ input,
3241
+ output,
3242
+ cacheCreate: 0,
3243
+ cacheRead,
3244
+ cacheSavings: cacheRead * (p.in - p.cr)
3245
+ };
3246
+ }
3247
+ function entriesFromJson(value) {
3248
+ const entries = [];
3249
+ const visit = (v) => {
3250
+ if (Array.isArray(v)) {
3251
+ for (const item of v) visit(item);
3252
+ return;
3253
+ }
3254
+ if (!v || typeof v !== "object") return;
3255
+ const obj = v;
3256
+ const entry = entryFromObject(obj);
3257
+ if (entry) {
3258
+ entries.push(entry);
3259
+ return;
3260
+ }
3261
+ for (const key of ["events", "messages", "entries", "records"]) {
3262
+ const nested = obj[key];
3263
+ if (Array.isArray(nested)) visit(nested);
3264
+ }
3265
+ };
3266
+ visit(value);
3267
+ return entries;
3268
+ }
3269
+ async function parseLineFile(path) {
3085
3270
  const entries = [];
3086
- const rl = createInterface5({ input: createReadStream5(path), crlfDelay: Infinity });
3087
- for await (const line of rl) {
3271
+ const input = createReadStream5(path);
3272
+ input.on("error", () => {
3273
+ });
3274
+ const rl = createInterface5({ input, crlfDelay: Infinity });
3275
+ try {
3276
+ for await (const line of rl) {
3277
+ try {
3278
+ const obj = JSON.parse(line.charCodeAt(0) === 65279 ? line.slice(1) : line);
3279
+ const entry = entryFromObject(obj);
3280
+ if (entry) entries.push(entry);
3281
+ } catch {
3282
+ }
3283
+ }
3284
+ } catch {
3285
+ }
3286
+ return entries;
3287
+ }
3288
+ async function parseFile4(path) {
3289
+ if (path.endsWith(".json")) {
3088
3290
  try {
3089
- const obj = JSON.parse(line.charCodeAt(0) === 65279 ? line.slice(1) : line);
3090
- if (obj.sessionId && obj.kind || obj.$set || obj.$rewindTo) continue;
3091
- if (obj.type !== "gemini" || !obj.tokens) continue;
3092
- const ts = Date.parse(obj.timestamp ?? "");
3093
- if (!Number.isFinite(ts)) continue;
3094
- const t = obj.tokens;
3095
- const input = Math.max(0, safeNum(t.input) + safeNum(t.tool) - safeNum(t.cached));
3096
- const output = safeNum(t.output) + safeNum(t.thoughts);
3097
- const cacheRead = safeNum(t.cached);
3098
- if (input + output + cacheRead === 0) continue;
3099
- const model = typeof obj.model === "string" && obj.model ? obj.model : "unknown";
3100
- const p = priceFor3(model);
3101
- entries.push({
3102
- id: typeof obj.id === "string" ? obj.id : void 0,
3103
- ts,
3104
- model: shortModel2(model),
3105
- cost: input * p.in + cacheRead * p.cr + output * p.out,
3106
- input,
3107
- output,
3108
- cacheCreate: 0,
3109
- cacheRead,
3110
- cacheSavings: cacheRead * (p.in - p.cr)
3111
- });
3291
+ const raw = await readFile6(path, "utf-8");
3292
+ return entriesFromJson(JSON.parse(raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw));
3112
3293
  } catch {
3113
3294
  }
3114
3295
  }
3115
- return entries;
3296
+ return parseLineFile(path);
3116
3297
  }
3117
3298
  async function loadEntries5(since, homeDir) {
3118
3299
  const files = [];
3119
3300
  const seen = /* @__PURE__ */ new Set();
3120
3301
  const seenIno = /* @__PURE__ */ new Set();
3121
- let listing;
3122
- try {
3123
- listing = await readdir8(geminiTmpDir(homeDir), { recursive: true });
3124
- } catch {
3125
- return [];
3126
- }
3302
+ const root = geminiTmpDir(homeDir);
3303
+ const listing = await walkFiles(root);
3127
3304
  for (const f of listing) {
3128
3305
  if (!isGeminiSessionFile(f)) continue;
3129
- const path = join15(geminiTmpDir(homeDir), f);
3306
+ const path = join15(root, f);
3130
3307
  if (seen.has(path)) continue;
3131
3308
  seen.add(path);
3132
3309
  try {
@@ -3170,7 +3347,7 @@ function authTypeFromSettings(settings) {
3170
3347
  }
3171
3348
  async function authMethodFromSettings(homeDir) {
3172
3349
  try {
3173
- const raw = await readFile6(join16(geminiDir(homeDir), "settings.json"), "utf8");
3350
+ const raw = await readFile7(join16(geminiDir(homeDir), "settings.json"), "utf8");
3174
3351
  return authTypeFromSettings(JSON.parse(raw));
3175
3352
  } catch {
3176
3353
  return "none";
@@ -3178,12 +3355,12 @@ async function authMethodFromSettings(homeDir) {
3178
3355
  }
3179
3356
  async function hasGeminiApiKeyFile(homeDir) {
3180
3357
  try {
3181
- await access8(join16(geminiDir(homeDir), "api_key"));
3358
+ await access9(join16(geminiDir(homeDir), "api_key"));
3182
3359
  return true;
3183
3360
  } catch {
3184
3361
  }
3185
3362
  try {
3186
- const env = await readFile6(join16(geminiDir(homeDir), ".env"), "utf8");
3363
+ const env = await readFile7(join16(geminiDir(homeDir), ".env"), "utf8");
3187
3364
  return /^\s*GEMINI_API_KEY\s*=/m.test(env);
3188
3365
  } catch {
3189
3366
  return false;
@@ -3204,7 +3381,7 @@ async function noOAuthAuthMessage(homeDir) {
3204
3381
  async function detectGemini(homeDir) {
3205
3382
  let oauthOk = false;
3206
3383
  try {
3207
- await access8(geminiCredsPath(homeDir));
3384
+ await access9(geminiCredsPath(homeDir));
3208
3385
  oauthOk = true;
3209
3386
  } catch {
3210
3387
  }
@@ -3213,7 +3390,7 @@ async function detectGemini(homeDir) {
3213
3390
  async function hasGeminiChatSessions(homeDir) {
3214
3391
  let listing;
3215
3392
  try {
3216
- listing = await readdir9(geminiTmpDir(homeDir), { recursive: true });
3393
+ listing = await readdir6(geminiTmpDir(homeDir), { recursive: true });
3217
3394
  } catch {
3218
3395
  return false;
3219
3396
  }
@@ -3227,7 +3404,7 @@ function geminiIdentity(creds) {
3227
3404
  }
3228
3405
  async function readGeminiCreds(path) {
3229
3406
  try {
3230
- return JSON.parse(await readFile6(path, "utf8"));
3407
+ return JSON.parse(await readFile7(path, "utf8"));
3231
3408
  } catch {
3232
3409
  return null;
3233
3410
  }
@@ -3577,6 +3754,83 @@ function accountsByProvider(accounts) {
3577
3754
  return groups;
3578
3755
  }
3579
3756
 
3757
+ // src/shared/colors.ts
3758
+ var FALLBACK_HEX = "#8d9090";
3759
+ var NAMED_HEX = {
3760
+ green: "#6caa71",
3761
+ greenBright: "#79be7e",
3762
+ cyan: "#7ccbcd",
3763
+ cyanBright: "#84dde0",
3764
+ blue: "#6d96b4",
3765
+ blueBright: "#67b5ed",
3766
+ magenta: "#bd7bcd",
3767
+ magentaBright: "#d389e5",
3768
+ yellow: "#c4ac62",
3769
+ yellowBright: "#d9c074",
3770
+ red: "#b45648",
3771
+ redBright: "#cf6a5a",
3772
+ white: "#dee5eb",
3773
+ whiteBright: "#f3f5f5",
3774
+ gray: FALLBACK_HEX,
3775
+ grey: FALLBACK_HEX
3776
+ };
3777
+ var PROVIDER_HEX = {
3778
+ claude: NAMED_HEX.green,
3779
+ codex: NAMED_HEX.cyan,
3780
+ cursor: NAMED_HEX.magenta,
3781
+ copilot: NAMED_HEX.white,
3782
+ pi: NAMED_HEX.blue,
3783
+ opencode: NAMED_HEX.yellow,
3784
+ antigravity: NAMED_HEX.red,
3785
+ gemini: NAMED_HEX.greenBright
3786
+ };
3787
+ function namedHex(name) {
3788
+ if (!name) return FALLBACK_HEX;
3789
+ if (name.startsWith("#")) return name;
3790
+ return NAMED_HEX[name] ?? FALLBACK_HEX;
3791
+ }
3792
+ function colorHex(accountColor, providerColorName) {
3793
+ if (accountColor) {
3794
+ if (accountColor.startsWith("#")) return accountColor;
3795
+ const named = NAMED_HEX[accountColor];
3796
+ if (named) return named;
3797
+ }
3798
+ return namedHex(providerColorName);
3799
+ }
3800
+ var MODEL_PALETTE = [
3801
+ "#00d7ff",
3802
+ "#00d787",
3803
+ "#e6b450",
3804
+ "#d75f87",
3805
+ "#5f87ff",
3806
+ "#af87ff",
3807
+ "#5fd7a7",
3808
+ "#ff8787",
3809
+ "#d7af5f",
3810
+ "#87d7ff",
3811
+ "#d787d7",
3812
+ "#9ee493",
3813
+ "#ffb454",
3814
+ "#7aa2f7",
3815
+ "#bb9af7"
3816
+ ];
3817
+ var modelColorCache = /* @__PURE__ */ new Map();
3818
+ function modelColor(name) {
3819
+ const cached = modelColorCache.get(name);
3820
+ if (cached) return cached;
3821
+ let hash = 0;
3822
+ for (let i = 0; i < name.length; i++) hash = hash * 31 + name.charCodeAt(i) >>> 0;
3823
+ const color = MODEL_PALETTE[hash % MODEL_PALETTE.length];
3824
+ modelColorCache.set(name, color);
3825
+ return color;
3826
+ }
3827
+ var TOKEN_BUCKET = {
3828
+ input: NAMED_HEX.blue,
3829
+ output: NAMED_HEX.green,
3830
+ cacheCreate: NAMED_HEX.yellow,
3831
+ cacheRead: NAMED_HEX.cyan
3832
+ };
3833
+
3580
3834
  // src/peak.ts
3581
3835
  async function fetchPeak() {
3582
3836
  try {
@@ -3710,6 +3964,9 @@ export {
3710
3964
  detectProviders,
3711
3965
  buildAccounts,
3712
3966
  accountsByProvider,
3967
+ namedHex,
3968
+ colorHex,
3969
+ modelColor,
3713
3970
  fetchPeak,
3714
3971
  TOKMON_WS_PATH,
3715
3972
  TOKMON_WS_METHODS,