tokmon 0.20.5 → 0.20.6

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.
@@ -18,7 +18,7 @@ import {
18
18
  systemTimezone,
19
19
  time,
20
20
  tokens
21
- } from "./chunk-EHIQHGJL.js";
21
+ } from "./chunk-YWJT3OG4.js";
22
22
  import {
23
23
  COLOR_PALETTE,
24
24
  DEFAULTS,
@@ -3235,7 +3235,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3235
3235
  if (webStartingRef.current) return;
3236
3236
  webStartingRef.current = true;
3237
3237
  try {
3238
- const { startWebServer } = await import("./server-RM4ZXN6C.js");
3238
+ const { startWebServer } = await import("./server-TSTBMVTJ.js");
3239
3239
  const ctrl = await startWebServer({ config: cfg, log: false });
3240
3240
  webRef.current = ctrl;
3241
3241
  openUrl(ctrl.url);
@@ -8,7 +8,7 @@ import {
8
8
  detectProviders,
9
9
  fetchPeak,
10
10
  resolveTimezone
11
- } from "./chunk-EHIQHGJL.js";
11
+ } from "./chunk-YWJT3OG4.js";
12
12
  import {
13
13
  cacheDir,
14
14
  expandHome,
@@ -141,7 +141,7 @@ var dollars = (cents) => finite(cents) / 100;
141
141
  // src/providers/usage-core.ts
142
142
  var SPARK_DAYS = 14;
143
143
  var DAY_MS = 864e5;
144
- var CACHE_VERSION = 5;
144
+ var CACHE_VERSION = 6;
145
145
  var STABLE_AGE_MS = 5 * 6e4;
146
146
  var PRUNE_AGE_MS = 200 * DAY_MS;
147
147
  var memCache = /* @__PURE__ */ new Map();
@@ -673,6 +673,8 @@ async function cursorActivity(homeDir) {
673
673
  var BASE = "https://api2.cursor.sh/aiserver.v1.DashboardService";
674
674
  var USAGE_URL = `${BASE}/GetCurrentPeriodUsage`;
675
675
  var PLAN_URL = `${BASE}/GetPlanInfo`;
676
+ var CREDITS_URL = `${BASE}/GetCreditGrantsBalance`;
677
+ var STRIPE_URL = "https://cursor.com/api/auth/stripe";
676
678
  function cursorStateDb(homeDir) {
677
679
  const base = homeDir ?? homedir2();
678
680
  const tail = ["Cursor", "User", "globalStorage", "state.vscdb"];
@@ -709,6 +711,31 @@ function cleanStoredString(value) {
709
711
  const trimmed = value.trim().replace(/^"|"$/g, "");
710
712
  return trimmed || void 0;
711
713
  }
714
+ function numberValue(value) {
715
+ if (typeof value === "number" && Number.isFinite(value)) return value;
716
+ if (typeof value === "string" && value.trim()) {
717
+ const n = Number(value);
718
+ if (Number.isFinite(n)) return n;
719
+ }
720
+ return void 0;
721
+ }
722
+ function decodeBase64UrlJson(segment) {
723
+ try {
724
+ const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
725
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
726
+ return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
727
+ } catch {
728
+ return null;
729
+ }
730
+ }
731
+ function cursorSessionToken(accessToken) {
732
+ const payload = accessToken.includes(".") ? decodeBase64UrlJson(accessToken.split(".")[1]) : null;
733
+ const subject = typeof payload?.sub === "string" ? payload.sub : null;
734
+ if (!subject) return null;
735
+ const parts = subject.split("|");
736
+ const userId = (parts.length > 1 ? parts[1] : parts[0]).trim();
737
+ return userId ? `${userId}%3A%3A${accessToken}` : null;
738
+ }
712
739
  function identityFields(email, displayName) {
713
740
  return {
714
741
  email: email ?? null,
@@ -734,6 +761,43 @@ async function connectPost(url, token) {
734
761
  return null;
735
762
  }
736
763
  }
764
+ async function connectGetWithSession(url, token) {
765
+ const session = cursorSessionToken(token);
766
+ if (!session) return null;
767
+ try {
768
+ const res = await fetch(url, {
769
+ headers: {
770
+ "Cookie": `WorkosCursorSessionToken=${session}`,
771
+ "User-Agent": "tokmon"
772
+ },
773
+ signal: AbortSignal.timeout(1e4)
774
+ });
775
+ if (!res.ok) return { __status: res.status };
776
+ return await readJson(res);
777
+ } catch {
778
+ return null;
779
+ }
780
+ }
781
+ function onDemandSpendCents(su, limit, remaining) {
782
+ for (const raw of [su.individualUsed, su.pooledUsed, su.totalSpend]) {
783
+ const n = numberValue(raw);
784
+ if (n !== void 0 && n > 0) return n;
785
+ }
786
+ const inferred = Math.max(0, limit - remaining);
787
+ if (inferred > 0) return inferred;
788
+ return numberValue(su.individualUsed) ?? numberValue(su.pooledUsed) ?? numberValue(su.totalSpend) ?? 0;
789
+ }
790
+ function appendCredits(metrics, creditGrants, stripe) {
791
+ const stripeBalance = numberValue(stripe?.customerBalance);
792
+ const stripeBalanceCents = stripeBalance !== void 0 && stripeBalance < 0 ? Math.abs(stripeBalance) : 0;
793
+ const hasGrants = creditGrants?.hasCreditGrants === true;
794
+ const grantTotal = hasGrants ? numberValue(creditGrants?.totalCents) ?? 0 : 0;
795
+ const grantUsed = hasGrants ? numberValue(creditGrants?.usedCents) ?? 0 : 0;
796
+ const hasValidGrants = hasGrants && grantTotal > 0;
797
+ const total = (hasValidGrants ? grantTotal : 0) + stripeBalanceCents;
798
+ if (total <= 0) return;
799
+ metrics.push({ label: "Credits", used: dollars(Math.max(0, total - (hasValidGrants ? grantUsed : 0))), limit: null, format: { kind: "dollars" } });
800
+ }
737
801
  async function cursorBilling(account) {
738
802
  const [core, activity, spend] = await Promise.all([
739
803
  cursorBillingCore(account),
@@ -760,7 +824,7 @@ async function cursorBillingCore(account) {
760
824
  readState(db, "cursorAuth/cachedEmail"),
761
825
  readState(db, "cursorAuth/cachedName")
762
826
  ]);
763
- const token = tokenRes.value;
827
+ const token = cleanStoredString(tokenRes.value);
764
828
  const membership = membershipRes.value;
765
829
  const email = cleanStoredString(emailRes.value);
766
830
  const displayName = cleanStoredString(nameRes.value);
@@ -769,9 +833,11 @@ async function cursorBillingCore(account) {
769
833
  const error = tokenRes.status === "ok" ? "Not signed in \u2014 open Cursor" : sqliteStatusMessage(tokenRes.status);
770
834
  return { plan: planFallback, metrics: [], error, ...identityFields(email, displayName) };
771
835
  }
772
- const [usage, planInfo] = await Promise.all([
836
+ const [usage, planInfo, creditGrants, stripe] = await Promise.all([
773
837
  connectPost(USAGE_URL, token),
774
- connectPost(PLAN_URL, token)
838
+ connectPost(PLAN_URL, token),
839
+ connectPost(CREDITS_URL, token),
840
+ connectGetWithSession(STRIPE_URL, token)
775
841
  ]);
776
842
  if (!usage || usage.__status) {
777
843
  const expired = usage?.__status === 401 || usage?.__status === 403;
@@ -783,37 +849,63 @@ async function cursorBillingCore(account) {
783
849
  const pu = usage.planUsage ?? {};
784
850
  const metrics = [];
785
851
  const rawEnd = usage.billingCycleEnd;
786
- const endMs = typeof rawEnd === "string" && rawEnd.trim() ? Number(rawEnd) : NaN;
852
+ const endMs = numberValue(rawEnd) ?? NaN;
787
853
  const iso = msToIso(endMs);
788
854
  const resets = iso && endMs > 0 ? resetIn(iso) : null;
789
- if (finiteNumber(pu.totalPercentUsed) && finiteNumber(pu.limit)) {
790
- metrics.push(percentMetric("Usage", pu.totalPercentUsed, resets, true));
791
- const spentCents = finiteNumber(pu.totalSpend) ? pu.totalSpend : finiteNumber(pu.remaining) ? pu.limit - pu.remaining : pu.limit * (pu.totalPercentUsed / 100);
792
- if (Number.isFinite(spentCents)) {
855
+ appendCredits(metrics, creditGrants, stripe);
856
+ const limit = numberValue(pu.limit);
857
+ const planUsedCents = numberValue(pu.totalSpend) ?? (limit !== void 0 && numberValue(pu.remaining) !== void 0 ? limit - numberValue(pu.remaining) : void 0);
858
+ const computedPercent = limit !== void 0 && limit > 0 && planUsedCents !== void 0 ? planUsedCents / limit * 100 : void 0;
859
+ const totalPercentUsed = numberValue(pu.totalPercentUsed) ?? computedPercent;
860
+ const su = usage.spendLimitUsage;
861
+ const planLower = typeof planName === "string" ? planName.trim().toLowerCase() : "";
862
+ const pooledLimit = numberValue(su?.pooledLimit) ?? 0;
863
+ const isTeamAccount = planLower === "team" || String(su?.limitType ?? "").toLowerCase() === "team" || pooledLimit > 0;
864
+ if (isTeamAccount && limit !== void 0 && planUsedCents !== void 0) {
865
+ metrics.push({
866
+ label: "Usage",
867
+ used: dollars(Math.max(0, planUsedCents)),
868
+ limit: dollars(limit),
869
+ format: { kind: "dollars" },
870
+ resetsAt: resets,
871
+ primary: true
872
+ });
873
+ } else if (totalPercentUsed !== void 0) {
874
+ metrics.push(percentMetric("Usage", totalPercentUsed, resets, true));
875
+ if (limit !== void 0 && planUsedCents !== void 0) {
793
876
  metrics.push({
794
877
  label: "Spend",
795
- used: dollars(spentCents),
796
- limit: dollars(pu.limit),
878
+ used: dollars(Math.max(0, planUsedCents)),
879
+ limit: dollars(limit),
797
880
  format: { kind: "dollars" }
798
881
  });
799
882
  }
800
883
  }
801
- if (typeof pu.autoPercentUsed === "number" && Number.isFinite(pu.autoPercentUsed)) {
802
- metrics.push({ label: "Auto", used: pu.autoPercentUsed, limit: 100, format: { kind: "percent" } });
884
+ const autoPercentUsed = numberValue(pu.autoPercentUsed);
885
+ if (autoPercentUsed !== void 0) {
886
+ metrics.push({ label: "Auto", used: autoPercentUsed, limit: 100, format: { kind: "percent" } });
803
887
  }
804
- if (typeof pu.apiPercentUsed === "number" && Number.isFinite(pu.apiPercentUsed)) {
805
- metrics.push({ label: "API", used: pu.apiPercentUsed, limit: 100, format: { kind: "percent" } });
888
+ const apiPercentUsed = numberValue(pu.apiPercentUsed);
889
+ if (apiPercentUsed !== void 0) {
890
+ metrics.push({ label: "API", used: apiPercentUsed, limit: 100, format: { kind: "percent" } });
806
891
  }
807
- const su = usage.spendLimitUsage;
808
892
  if (su) {
809
- const pair = finiteNumber(su.individualLimit) && finiteNumber(su.individualRemaining) ? { limit: su.individualLimit, remaining: su.individualRemaining } : finiteNumber(su.pooledLimit) && finiteNumber(su.pooledRemaining) ? { limit: su.pooledLimit, remaining: su.pooledRemaining } : null;
893
+ const pair = numberValue(su.individualLimit) !== void 0 && numberValue(su.individualRemaining) !== void 0 ? { limit: numberValue(su.individualLimit), remaining: numberValue(su.individualRemaining) } : numberValue(su.pooledLimit) !== void 0 && numberValue(su.pooledRemaining) !== void 0 ? { limit: numberValue(su.pooledLimit), remaining: numberValue(su.pooledRemaining) } : null;
894
+ const spent = onDemandSpendCents(su, pair?.limit ?? 0, pair?.remaining ?? 0);
810
895
  if (pair && pair.limit > 0) {
811
896
  metrics.push({
812
897
  label: "On-demand",
813
- used: dollars(pair.limit - pair.remaining),
898
+ used: dollars(spent),
814
899
  limit: dollars(pair.limit),
815
900
  format: { kind: "dollars" }
816
901
  });
902
+ } else if (spent > 0) {
903
+ metrics.push({
904
+ label: "On-demand",
905
+ used: dollars(spent),
906
+ limit: null,
907
+ format: { kind: "dollars" }
908
+ });
817
909
  }
818
910
  }
819
911
  if (metrics.length === 0) {
@@ -949,6 +1041,11 @@ function costOf(model, u) {
949
1041
  function shortModel(model) {
950
1042
  return model.replace("claude-", "").replace(/-\d{8}$/, "");
951
1043
  }
1044
+ function timestampMs(value) {
1045
+ if (typeof value === "number" && Number.isFinite(value)) return value > 1e10 ? value : value * 1e3;
1046
+ if (typeof value === "string" && value.trim()) return new Date(value.trim()).getTime();
1047
+ return NaN;
1048
+ }
952
1049
  async function parseFile(path) {
953
1050
  const entries = [];
954
1051
  const input = createReadStream(path);
@@ -961,7 +1058,7 @@ async function parseFile(path) {
961
1058
  try {
962
1059
  const obj = JSON.parse(line.charCodeAt(0) === 65279 ? line.slice(1) : line);
963
1060
  if (obj.type !== "assistant" || !obj.message?.usage) continue;
964
- const ts = new Date(obj.timestamp ?? 0).getTime();
1061
+ const ts = timestampMs(obj.timestamp);
965
1062
  if (!Number.isFinite(ts)) continue;
966
1063
  const u = obj.message.usage;
967
1064
  const model = typeof obj.message.model === "string" && obj.message.model ? obj.message.model : "unknown";
@@ -1142,10 +1239,31 @@ function planLabel(auth) {
1142
1239
  return tier ? `${base} ${tier[1]}x` : base;
1143
1240
  }
1144
1241
  var pct = (used, resets, primary) => percentMetric("", used, resets ?? null, primary);
1242
+ function numberValue2(value) {
1243
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1244
+ if (typeof value === "string" && value.trim()) {
1245
+ const n = Number(value);
1246
+ if (Number.isFinite(n)) return n;
1247
+ }
1248
+ return void 0;
1249
+ }
1250
+ function boolValue(value) {
1251
+ if (typeof value === "boolean") return value;
1252
+ if (typeof value === "number") return value !== 0;
1253
+ if (typeof value === "string") return value.trim().toLowerCase() === "true" || value.trim() === "1";
1254
+ return false;
1255
+ }
1256
+ function resetFrom(value) {
1257
+ if (typeof value === "string" && value.trim()) return resetIn(value);
1258
+ const n = numberValue2(value);
1259
+ if (n === void 0) return null;
1260
+ const ms = Math.abs(n) < 1e10 ? n * 1e3 : n;
1261
+ return resetIn(new Date(ms).toISOString());
1262
+ }
1145
1263
  function usageMetric(label, window, primary) {
1146
- if (!window || typeof window.utilization !== "number" || !Number.isFinite(window.utilization)) return null;
1147
- const resets = typeof window.resets_at === "string" && window.resets_at.trim() ? resetIn(window.resets_at) : null;
1148
- return { ...pct(window.utilization, resets, primary), label };
1264
+ const used = numberValue2(window?.utilization);
1265
+ if (used === void 0) return null;
1266
+ return { ...pct(used, resetFrom(window?.resets_at), primary), label };
1149
1267
  }
1150
1268
  async function claudeBilling(account) {
1151
1269
  const [auth, identity] = await Promise.all([
@@ -1163,26 +1281,33 @@ async function claudeBilling(account) {
1163
1281
  },
1164
1282
  signal: AbortSignal.timeout(1e4)
1165
1283
  });
1166
- if (res.status === 429) return { plan, metrics: [], error: "Rate limited \u2014 retrying next poll", ...identityFields2(identity) };
1284
+ if (res.status === 429) {
1285
+ const retryAfter = numberValue2(res.headers.get("retry-after"));
1286
+ const retryText = retryAfter !== void 0 ? ` \u2014 retry in ~${Math.ceil(retryAfter / 60)}m` : " \u2014 retrying next poll";
1287
+ return { plan, metrics: [], error: `Rate limited${retryText}`, ...identityFields2(identity) };
1288
+ }
1167
1289
  if (res.status === 401) return { plan, metrics: [], error: "Token expired \u2014 restart Claude Code", ...identityFields2(identity) };
1168
1290
  if (!res.ok) return { plan, metrics: [], error: `API ${res.status}`, ...identityFields2(identity) };
1169
1291
  const data = await readJson(res);
1170
1292
  if (!data) return { plan, metrics: [], error: "Unexpected API response", ...identityFields2(identity) };
1171
1293
  const metrics = [];
1172
- const fiveHour = usageMetric("5h", data.five_hour, true);
1294
+ const fiveHour = usageMetric("Session", data.five_hour, true);
1173
1295
  if (fiveHour) metrics.push(fiveHour);
1174
- const sevenDay = usageMetric("Week", data.seven_day);
1296
+ const sevenDay = usageMetric("Weekly", data.seven_day);
1175
1297
  if (sevenDay) metrics.push(sevenDay);
1176
1298
  const sevenDaySonnet = usageMetric("Sonnet", data.seven_day_sonnet);
1177
1299
  if (sevenDaySonnet) metrics.push(sevenDaySonnet);
1178
- if (data.extra_usage?.is_enabled) {
1179
- const monthlyLimit = data.extra_usage.monthly_limit;
1180
- metrics.push({
1181
- label: "Extra",
1182
- used: finite(data.extra_usage.used_credits) / 100,
1183
- limit: typeof monthlyLimit === "number" && Number.isFinite(monthlyLimit) ? monthlyLimit / 100 : null,
1184
- format: { kind: "dollars", currency: data.extra_usage.currency ?? "USD" }
1185
- });
1300
+ if (boolValue(data.extra_usage?.is_enabled)) {
1301
+ const usedCredits = numberValue2(data.extra_usage?.used_credits);
1302
+ const monthlyLimit = numberValue2(data.extra_usage?.monthly_limit);
1303
+ if (usedCredits !== void 0 && (usedCredits > 0 || monthlyLimit !== void 0 && monthlyLimit > 0)) {
1304
+ metrics.push({
1305
+ label: "Extra",
1306
+ used: finite(usedCredits) / 100,
1307
+ limit: monthlyLimit !== void 0 && monthlyLimit > 0 ? monthlyLimit / 100 : null,
1308
+ format: { kind: "dollars", currency: data.extra_usage?.currency ?? "USD" }
1309
+ });
1310
+ }
1186
1311
  }
1187
1312
  return { plan, metrics, error: null, ...identityFields2(identity) };
1188
1313
  } catch {
@@ -1204,7 +1329,7 @@ var claudeProvider = {
1204
1329
  };
1205
1330
 
1206
1331
  // src/providers/codex/usage.ts
1207
- import { readdir as readdir2, stat as fsStat2, access as access3 } from "fs/promises";
1332
+ import { readdir as readdir2, stat as fsStat2, access as access3, open as openFile } from "fs/promises";
1208
1333
  import { createReadStream as createReadStream2 } from "fs";
1209
1334
  import { createInterface as createInterface2 } from "readline";
1210
1335
  import { join as join7 } from "path";
@@ -1238,6 +1363,11 @@ async function detectCodex(homeDir) {
1238
1363
  return true;
1239
1364
  } catch {
1240
1365
  }
1366
+ try {
1367
+ await access3(join7(home, "archived_sessions"));
1368
+ return true;
1369
+ } catch {
1370
+ }
1241
1371
  }
1242
1372
  return false;
1243
1373
  }
@@ -1262,7 +1392,7 @@ function priceFor2(model) {
1262
1392
  }
1263
1393
  function extractModel(obj) {
1264
1394
  const p = obj?.payload ?? obj;
1265
- return p?.model || p?.collaboration_mode?.settings?.model || p?.model_slug || p?.config?.model || p?.info?.model || null;
1395
+ return p?.model || p?.model_name || p?.collaboration_mode?.settings?.model || p?.model_slug || p?.config?.model || p?.info?.model || p?.info?.model_name || p?.info?.model_slug || p?.metadata?.model || p?.info?.metadata?.model || null;
1266
1396
  }
1267
1397
  function subtractClamped(cur, prev) {
1268
1398
  const sub = (a, b) => Math.max(0, (a ?? 0) - (b ?? 0));
@@ -1270,38 +1400,160 @@ function subtractClamped(cur, prev) {
1270
1400
  input_tokens: sub(cur.input_tokens, prev?.input_tokens),
1271
1401
  cached_input_tokens: sub(cur.cached_input_tokens, prev?.cached_input_tokens),
1272
1402
  output_tokens: sub(cur.output_tokens, prev?.output_tokens),
1273
- reasoning_output_tokens: sub(cur.reasoning_output_tokens, prev?.reasoning_output_tokens)
1403
+ reasoning_output_tokens: sub(cur.reasoning_output_tokens, prev?.reasoning_output_tokens),
1404
+ total_tokens: sub(cur.total_tokens, prev?.total_tokens)
1405
+ };
1406
+ }
1407
+ function tokenNumber(obj, keys) {
1408
+ for (const key of keys) {
1409
+ const raw = obj?.[key];
1410
+ const n = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() ? Number(raw) : NaN;
1411
+ if (Number.isFinite(n) && n >= 0) return Math.floor(n);
1412
+ }
1413
+ return void 0;
1414
+ }
1415
+ function normalizeUsage(obj) {
1416
+ if (!obj || typeof obj !== "object") return void 0;
1417
+ const input = tokenNumber(obj, ["input_tokens", "prompt_tokens", "input"]);
1418
+ const cached = tokenNumber(obj, ["cached_input_tokens", "cache_read_input_tokens", "cached_tokens"]);
1419
+ const output = tokenNumber(obj, ["output_tokens", "completion_tokens", "output"]);
1420
+ const reasoning = tokenNumber(obj, ["reasoning_output_tokens", "reasoning_tokens"]);
1421
+ let total = tokenNumber(obj, ["total_tokens"]);
1422
+ const hasUsage = [input, cached, output, reasoning, total].some((v) => v !== void 0);
1423
+ if (!hasUsage) return void 0;
1424
+ if (total === void 0 || total === 0 && (input ?? 0) + (output ?? 0) + (reasoning ?? 0) > 0) {
1425
+ total = (input ?? 0) + (output ?? 0) + (reasoning ?? 0);
1426
+ }
1427
+ return {
1428
+ input_tokens: input ?? 0,
1429
+ cached_input_tokens: cached ?? 0,
1430
+ output_tokens: output ?? 0,
1431
+ reasoning_output_tokens: reasoning ?? 0,
1432
+ total_tokens: total
1274
1433
  };
1275
1434
  }
1276
1435
  function eventSig(last, total) {
1277
- const f = (x) => x ? `${x.input_tokens ?? 0},${x.cached_input_tokens ?? 0},${x.output_tokens ?? 0},${x.reasoning_output_tokens ?? 0}` : "-";
1436
+ const f = (x) => x ? `${x.input_tokens ?? 0},${x.cached_input_tokens ?? 0},${x.output_tokens ?? 0},${x.reasoning_output_tokens ?? 0},${x.total_tokens ?? 0}` : "-";
1278
1437
  return `${f(last)}|${f(total)}`;
1279
1438
  }
1439
+ function timestampMs2(value) {
1440
+ if (typeof value === "number" && Number.isFinite(value)) return value > 1e10 ? value : value * 1e3;
1441
+ if (typeof value === "string" && value.trim()) return new Date(value.trim()).getTime();
1442
+ return NaN;
1443
+ }
1444
+ function timestampSecond(value) {
1445
+ if (typeof value === "string" && value.trim().length >= 19) return value.trim().slice(0, 19);
1446
+ const ts = timestampMs2(value);
1447
+ return Number.isFinite(ts) ? new Date(ts).toISOString().slice(0, 19) : null;
1448
+ }
1449
+ async function hasThreadSpawn(path) {
1450
+ let handle = null;
1451
+ try {
1452
+ handle = await openFile(path, "r");
1453
+ const buffer = Buffer.alloc(16 * 1024);
1454
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
1455
+ return buffer.subarray(0, bytesRead).includes("thread_spawn");
1456
+ } catch {
1457
+ return false;
1458
+ } finally {
1459
+ await handle?.close().catch(() => {
1460
+ });
1461
+ }
1462
+ }
1463
+ async function detectReplaySecond(path) {
1464
+ if (!await hasThreadSpawn(path)) return null;
1465
+ let first = null;
1466
+ const input = createReadStream2(path);
1467
+ input.on("error", () => {
1468
+ });
1469
+ const rl = createInterface2({ input, crlfDelay: Infinity });
1470
+ try {
1471
+ for await (const rawLine of rl) {
1472
+ if (!rawLine.includes("token_count")) continue;
1473
+ try {
1474
+ const line = rawLine.charCodeAt(0) === 65279 ? rawLine.slice(1) : rawLine;
1475
+ const obj = JSON.parse(line);
1476
+ if ((obj?.payload?.type ?? obj?.type) !== "token_count") continue;
1477
+ const info = obj?.payload?.info;
1478
+ if (!normalizeUsage(info?.last_token_usage) && !normalizeUsage(info?.total_token_usage)) continue;
1479
+ const second = timestampSecond(obj.timestamp ?? obj?.payload?.timestamp);
1480
+ if (!second) continue;
1481
+ if (!first) first = second;
1482
+ else return first === second ? second : null;
1483
+ } catch {
1484
+ }
1485
+ }
1486
+ } catch {
1487
+ }
1488
+ return null;
1489
+ }
1490
+ function findUsage(obj) {
1491
+ return normalizeUsage(obj?.usage) ?? normalizeUsage(obj?.payload?.usage) ?? normalizeUsage(obj?.payload?.info?.usage) ?? normalizeUsage(obj?.result?.usage) ?? normalizeUsage(obj?.response?.usage) ?? normalizeUsage(obj?.token_usage) ?? normalizeUsage(obj);
1492
+ }
1493
+ function findTimestamp(obj) {
1494
+ return timestampMs2(obj?.timestamp ?? obj?.payload?.timestamp ?? obj?.created_at ?? obj?.createdAt ?? obj?.time);
1495
+ }
1280
1496
  async function parseFile2(path) {
1281
1497
  const entries = [];
1282
- let model = "unknown";
1498
+ let model = "gpt-5";
1283
1499
  let prevTotal = null;
1284
1500
  let prevSig = null;
1501
+ const replaySecond = await detectReplaySecond(path);
1502
+ let skipReplay = replaySecond !== null;
1285
1503
  const input = createReadStream2(path);
1286
1504
  input.on("error", () => {
1287
1505
  });
1288
1506
  const rl = createInterface2({ input, crlfDelay: Infinity });
1289
1507
  try {
1290
1508
  for await (const rawLine of rl) {
1291
- if (!rawLine.includes("token_count") && !rawLine.includes("turn_context")) continue;
1509
+ if (!rawLine.includes("token_count") && !rawLine.includes("turn_context") && !rawLine.includes('"usage"') && !rawLine.includes("input_tokens") && !rawLine.includes("prompt_tokens")) continue;
1292
1510
  try {
1293
1511
  const line = rawLine.charCodeAt(0) === 65279 ? rawLine.slice(1) : rawLine;
1294
1512
  const obj = JSON.parse(line);
1295
1513
  const payloadType = obj?.payload?.type ?? obj?.type;
1296
1514
  if (payloadType === "turn_context") {
1297
- const m = extractModel(obj);
1298
- if (typeof m === "string" && m.trim()) model = m;
1515
+ const m2 = extractModel(obj);
1516
+ if (typeof m2 === "string" && m2.trim()) model = m2;
1517
+ continue;
1518
+ }
1519
+ if (payloadType !== "token_count") {
1520
+ const usage = findUsage(obj);
1521
+ if (!usage) continue;
1522
+ const m2 = extractModel(obj);
1523
+ if (typeof m2 === "string" && m2.trim()) model = m2;
1524
+ const ts2 = findTimestamp(obj);
1525
+ if (!Number.isFinite(ts2)) continue;
1526
+ const inputTotal2 = safeNum(usage.input_tokens);
1527
+ const cached2 = Math.min(safeNum(usage.cached_input_tokens), inputTotal2);
1528
+ const inputTokens2 = inputTotal2 - cached2;
1529
+ const output2 = safeNum(usage.output_tokens);
1530
+ if (inputTokens2 + output2 + cached2 === 0) continue;
1531
+ const p2 = priceFor2(model);
1532
+ entries.push({
1533
+ id: `${ts2}|${model}|${inputTotal2}|${cached2}|${output2}|${safeNum(usage.reasoning_output_tokens)}|${safeNum(usage.total_tokens)}`,
1534
+ ts: ts2,
1535
+ model,
1536
+ cost: inputTokens2 * p2.in + cached2 * p2.cr + output2 * p2.out,
1537
+ input: inputTokens2,
1538
+ output: output2,
1539
+ cacheCreate: 0,
1540
+ cacheRead: cached2,
1541
+ cacheSavings: cached2 * (p2.in - p2.cr)
1542
+ });
1299
1543
  continue;
1300
1544
  }
1301
- if (payloadType !== "token_count") continue;
1302
1545
  const info = obj?.payload?.info;
1303
- const total = info?.total_token_usage;
1304
- const last = info?.last_token_usage;
1546
+ const total = normalizeUsage(info?.total_token_usage);
1547
+ const last = normalizeUsage(info?.last_token_usage);
1548
+ const tsValue = obj.timestamp ?? obj?.payload?.timestamp;
1549
+ if (skipReplay && replaySecond) {
1550
+ const second = timestampSecond(tsValue);
1551
+ if (second === replaySecond) {
1552
+ if (total) prevTotal = total;
1553
+ continue;
1554
+ }
1555
+ if (second) skipReplay = false;
1556
+ }
1305
1557
  const sig = eventSig(last, total);
1306
1558
  if (sig === prevSig) continue;
1307
1559
  prevSig = sig;
@@ -1312,8 +1564,10 @@ async function parseFile2(path) {
1312
1564
  }
1313
1565
  if (total) prevTotal = total;
1314
1566
  if (!d) continue;
1315
- const ts = new Date(obj.timestamp ?? obj?.payload?.timestamp ?? 0).getTime();
1567
+ const ts = timestampMs2(tsValue);
1316
1568
  if (!Number.isFinite(ts)) continue;
1569
+ const m = extractModel(obj);
1570
+ if (typeof m === "string" && m.trim()) model = m;
1317
1571
  const inputTotal = safeNum(d.input_tokens);
1318
1572
  const cached = Math.min(safeNum(d.cached_input_tokens), inputTotal);
1319
1573
  const inputTokens = inputTotal - cached;
@@ -1321,6 +1575,7 @@ async function parseFile2(path) {
1321
1575
  if (inputTokens + output + cached === 0) continue;
1322
1576
  const p = priceFor2(model);
1323
1577
  entries.push({
1578
+ id: `${ts}|${model}|${inputTotal}|${cached}|${output}|${safeNum(d.reasoning_output_tokens)}|${safeNum(d.total_tokens)}`,
1324
1579
  ts,
1325
1580
  model,
1326
1581
  cost: inputTokens * p.in + cached * p.cr + output * p.out,
@@ -1342,28 +1597,28 @@ async function loadEntries2(since, homeDir) {
1342
1597
  const seen = /* @__PURE__ */ new Set();
1343
1598
  const seenIno = /* @__PURE__ */ new Set();
1344
1599
  for (const home of codexHomes(homeDir)) {
1345
- const dir = join7(home, "sessions");
1346
- let listing;
1347
- try {
1348
- listing = await readdir2(dir, { recursive: true });
1349
- } catch {
1350
- continue;
1351
- }
1352
- for (const f of listing) {
1353
- if (!f.endsWith(".jsonl") || !f.includes("rollout-")) continue;
1354
- const path = join7(dir, f);
1355
- if (seen.has(path)) continue;
1356
- seen.add(path);
1600
+ for (const dir of [join7(home, "sessions"), join7(home, "archived_sessions")]) {
1601
+ let listing;
1357
1602
  try {
1358
- const s = await fsStat2(path);
1359
- if (s.mtimeMs < since) continue;
1360
- if (s.ino && process.platform !== "win32") {
1361
- const idn = `${s.dev}:${s.ino}`;
1362
- if (seenIno.has(idn)) continue;
1363
- seenIno.add(idn);
1364
- }
1365
- files.push({ path, mtimeMs: s.mtimeMs, size: s.size });
1603
+ listing = await readdir2(dir, { recursive: true });
1366
1604
  } catch {
1605
+ continue;
1606
+ }
1607
+ for (const f of listing) {
1608
+ if (!f.endsWith(".jsonl")) continue;
1609
+ const path = join7(dir, f);
1610
+ if (seen.has(path)) continue;
1611
+ seen.add(path);
1612
+ try {
1613
+ const s = await fsStat2(path);
1614
+ if (s.ino && process.platform !== "win32") {
1615
+ const idn = `${s.dev}:${s.ino}`;
1616
+ if (seenIno.has(idn)) continue;
1617
+ seenIno.add(idn);
1618
+ }
1619
+ files.push({ path, mtimeMs: s.mtimeMs, size: s.size });
1620
+ } catch {
1621
+ }
1367
1622
  }
1368
1623
  }
1369
1624
  }
@@ -1384,8 +1639,9 @@ import { createReadStream as createReadStream3 } from "fs";
1384
1639
  import { createInterface as createInterface3 } from "readline";
1385
1640
  import { join as join8 } from "path";
1386
1641
  var USAGE_URL2 = "https://chatgpt.com/backend-api/wham/usage";
1642
+ var RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
1387
1643
  var CREDIT_USD_RATE = 0.04;
1388
- function decodeBase64UrlJson(segment) {
1644
+ function decodeBase64UrlJson2(segment) {
1389
1645
  try {
1390
1646
  const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
1391
1647
  const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
@@ -1410,7 +1666,7 @@ function chatGptPlanLabel(planType) {
1410
1666
  }
1411
1667
  function identityFromIdToken(idToken) {
1412
1668
  if (typeof idToken !== "string" || !idToken.includes(".")) return {};
1413
- const payload = decodeBase64UrlJson(idToken.split(".")[1]);
1669
+ const payload = decodeBase64UrlJson2(idToken.split(".")[1]);
1414
1670
  if (!payload || typeof payload !== "object") return {};
1415
1671
  const email = typeof payload.email === "string" && payload.email.trim() ? payload.email.trim() : void 0;
1416
1672
  const displayName = typeof payload.name === "string" && payload.name.trim() ? payload.name.trim() : typeof payload.given_name === "string" && payload.given_name.trim() ? payload.given_name.trim() : void 0;
@@ -1461,14 +1717,95 @@ function planLabel2(planType) {
1461
1717
  if (p === "pro") return "Pro 20x";
1462
1718
  return planType.charAt(0).toUpperCase() + planType.slice(1);
1463
1719
  }
1464
- function resetFrom(window) {
1720
+ function numberValue3(value) {
1721
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1722
+ if (typeof value === "string" && value.trim()) {
1723
+ const n = Number(value);
1724
+ if (Number.isFinite(n)) return n;
1725
+ }
1726
+ return void 0;
1727
+ }
1728
+ function resetDateMs(window) {
1465
1729
  if (!window) return null;
1466
- let iso = null;
1467
- if (typeof window.reset_at === "number") iso = msToIso(window.reset_at * 1e3);
1468
- else if (typeof window.resets_at === "number") iso = msToIso(window.resets_at * 1e3);
1469
- else if (typeof window.reset_after_seconds === "number") iso = msToIso(Date.now() + window.reset_after_seconds * 1e3);
1730
+ const absolute = numberValue3(window.reset_at ?? window.resets_at);
1731
+ if (absolute !== void 0) return absolute > 1e10 ? absolute : absolute * 1e3;
1732
+ const after = numberValue3(window.reset_after_seconds);
1733
+ if (after !== void 0) return Date.now() + after * 1e3;
1734
+ for (const key of ["reset_at", "resets_at"]) {
1735
+ const raw = window[key];
1736
+ if (typeof raw === "string" && raw.trim()) {
1737
+ const parsed = new Date(raw).getTime();
1738
+ if (Number.isFinite(parsed)) return parsed;
1739
+ }
1740
+ }
1741
+ return null;
1742
+ }
1743
+ function resetFrom2(window) {
1744
+ const ms = resetDateMs(window);
1745
+ const iso = ms === null ? null : msToIso(ms);
1470
1746
  return iso ? resetIn(iso) : null;
1471
1747
  }
1748
+ function normalizedUsedPercent(window, percent) {
1749
+ const used = numberValue3(percent);
1750
+ if (used === void 0) return void 0;
1751
+ const periodSeconds = numberValue3(window?.limit_window_seconds ?? window?.period_seconds ?? window?.window_seconds);
1752
+ const resetMs = resetDateMs(window);
1753
+ if (periodSeconds && resetMs) {
1754
+ const elapsedMs = periodSeconds * 1e3 - Math.max(0, resetMs - Date.now());
1755
+ if (elapsedMs <= 6e4 && used <= 1) return 0;
1756
+ }
1757
+ return Math.max(0, used);
1758
+ }
1759
+ function metricLabelForAdditional(window) {
1760
+ const name = String(window?.limit_name ?? window?.name ?? window?.metered_feature ?? window?.feature ?? "").toLowerCase();
1761
+ if (!name.includes("spark")) return null;
1762
+ const seconds = numberValue3(window?.limit_window_seconds ?? window?.period_seconds ?? window?.window_seconds);
1763
+ return name.includes("week") || seconds !== void 0 && seconds >= 6 * 24 * 60 * 60 ? "Spark Weekly" : "Spark";
1764
+ }
1765
+ function additionalRateLimits(rl) {
1766
+ const raw = rl?.additional_rate_limits ?? rl?.additionalRateLimits ?? rl?.additional;
1767
+ if (Array.isArray(raw)) return raw;
1768
+ if (raw && typeof raw === "object") return Object.values(raw);
1769
+ return [];
1770
+ }
1771
+ function appendWindowMetrics(metrics, rl, headerPct) {
1772
+ const primary = rl?.primary_window ?? rl?.primary ?? null;
1773
+ const secondary = rl?.secondary_window ?? rl?.secondary ?? null;
1774
+ const primaryPct = normalizedUsedPercent(primary, primary?.used_percent ?? primary?.percent_used ?? headerPct?.("x-codex-primary-used-percent"));
1775
+ const secondaryPct = normalizedUsedPercent(secondary, secondary?.used_percent ?? secondary?.percent_used ?? headerPct?.("x-codex-secondary-used-percent"));
1776
+ if (primaryPct !== void 0) metrics.push(percentMetric("Session", primaryPct, resetFrom2(primary), true));
1777
+ if (secondaryPct !== void 0) metrics.push(percentMetric("Weekly", secondaryPct, resetFrom2(secondary)));
1778
+ for (const item of additionalRateLimits(rl)) {
1779
+ const label = metricLabelForAdditional(item);
1780
+ if (!label) continue;
1781
+ const used = normalizedUsedPercent(item, item?.used_percent ?? item?.percent_used);
1782
+ if (used === void 0) continue;
1783
+ metrics.push(percentMetric(label, used, resetFrom2(item)));
1784
+ }
1785
+ }
1786
+ function appendCredits2(metrics, source) {
1787
+ const balance = numberValue3(source?.credits?.balance ?? source?.credit_balance);
1788
+ if (balance !== void 0 && balance >= 0) {
1789
+ metrics.push({ label: "Credits", used: Math.floor(balance) * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
1790
+ }
1791
+ }
1792
+ async function fetchResetCredits(headers) {
1793
+ try {
1794
+ const res = await fetch(RESET_CREDITS_URL, {
1795
+ headers: {
1796
+ ...headers,
1797
+ "OpenAI-Beta": "codex-1",
1798
+ "originator": "Codex Desktop"
1799
+ },
1800
+ signal: AbortSignal.timeout(1e4)
1801
+ });
1802
+ if (!res.ok) return void 0;
1803
+ const data = await readJson(res);
1804
+ return numberValue3(data?.available_count ?? data?.available ?? data?.remaining);
1805
+ } catch {
1806
+ return void 0;
1807
+ }
1808
+ }
1472
1809
  async function liveBilling(auth) {
1473
1810
  try {
1474
1811
  const headers = {
@@ -1481,23 +1818,18 @@ async function liveBilling(auth) {
1481
1818
  if (!res.ok) return null;
1482
1819
  const data = await readJson(res);
1483
1820
  if (!data) return null;
1484
- const metrics = [];
1485
- const rl = data.rate_limit ?? null;
1486
- const primary = rl?.primary_window ?? null;
1487
- const secondary = rl?.secondary_window ?? null;
1488
1821
  const headerPct = (name) => {
1489
1822
  const h = res.headers.get(name);
1490
1823
  if (h === null || h.trim() === "") return void 0;
1491
1824
  const n = Number(h);
1492
1825
  return Number.isFinite(n) ? n : void 0;
1493
1826
  };
1494
- const primaryPct = headerPct("x-codex-primary-used-percent") ?? primary?.used_percent;
1495
- const secondaryPct = headerPct("x-codex-secondary-used-percent") ?? secondary?.used_percent;
1496
- if (typeof primaryPct === "number" && Number.isFinite(primaryPct)) metrics.push(percentMetric("5h", primaryPct, resetFrom(primary), true));
1497
- if (typeof secondaryPct === "number" && Number.isFinite(secondaryPct)) metrics.push(percentMetric("Week", secondaryPct, resetFrom(secondary)));
1498
- const balance = data?.credits?.balance;
1499
- if (typeof balance === "number" && Number.isFinite(balance) && balance >= 0) {
1500
- metrics.push({ label: "Credits", used: balance * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
1827
+ const metrics = [];
1828
+ appendWindowMetrics(metrics, data.rate_limit ?? data, headerPct);
1829
+ appendCredits2(metrics, data);
1830
+ const resetCredits = await fetchResetCredits(headers) ?? numberValue3(data?.rate_limit_reset_credits?.available_count ?? data?.rate_limit_reset_credits?.available);
1831
+ if (resetCredits !== void 0 && resetCredits >= 0) {
1832
+ metrics.push({ label: "Rate Limit Resets", used: resetCredits, limit: null, format: { kind: "count", suffix: "available" } });
1501
1833
  }
1502
1834
  if (metrics.length === 0) return null;
1503
1835
  return { plan: auth.plan ?? planLabel2(data.plan_type), metrics, error: null, ...identityFields3(auth) };
@@ -1549,15 +1881,11 @@ async function snapshotBilling(homeDir, auth = null) {
1549
1881
  }
1550
1882
  if (!last) return null;
1551
1883
  const metrics = [];
1552
- if (typeof last.primary?.used_percent === "number" && Number.isFinite(last.primary.used_percent)) {
1553
- metrics.push(percentMetric("5h", last.primary.used_percent, resetFrom(last.primary), true));
1554
- }
1555
- if (typeof last.secondary?.used_percent === "number" && Number.isFinite(last.secondary.used_percent)) {
1556
- metrics.push(percentMetric("Week", last.secondary.used_percent, resetFrom(last.secondary)));
1557
- }
1558
- const balance = last?.credits?.balance;
1559
- if (typeof balance === "number" && Number.isFinite(balance) && balance >= 0) {
1560
- metrics.push({ label: "Credits", used: balance * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
1884
+ appendWindowMetrics(metrics, last.rate_limit ?? last);
1885
+ appendCredits2(metrics, last);
1886
+ const resetCredits = numberValue3(last?.rate_limit_reset_credits?.available_count ?? last?.rate_limit_reset_credits?.available);
1887
+ if (resetCredits !== void 0 && resetCredits >= 0) {
1888
+ metrics.push({ label: "Rate Limit Resets", used: resetCredits, limit: null, format: { kind: "count", suffix: "available" } });
1561
1889
  }
1562
1890
  if (metrics.length === 0) return null;
1563
1891
  return { plan: auth?.plan ?? planLabel2(last.plan_type), metrics, error: null, ...identityFields3(auth) };
@@ -1771,6 +2099,11 @@ async function detectPi(homeDir) {
1771
2099
  return false;
1772
2100
  }
1773
2101
  }
2102
+ function timestampMs3(value) {
2103
+ if (typeof value === "number" && Number.isFinite(value)) return value > 1e10 ? value : value * 1e3;
2104
+ if (typeof value === "string" && value.trim()) return new Date(value.trim()).getTime();
2105
+ return NaN;
2106
+ }
1774
2107
  async function parseFile3(path) {
1775
2108
  const entries = [];
1776
2109
  const rl = createInterface4({ input: createReadStream4(path), crlfDelay: Infinity });
@@ -1783,7 +2116,7 @@ async function parseFile3(path) {
1783
2116
  const msg = obj.message;
1784
2117
  if (msg?.role !== "assistant" || !msg?.usage) continue;
1785
2118
  const u = msg.usage;
1786
- const ts = new Date(obj.timestamp ?? msg.timestamp ?? 0).getTime();
2119
+ const ts = timestampMs3(obj.timestamp ?? msg.timestamp);
1787
2120
  if (!Number.isFinite(ts)) continue;
1788
2121
  const input = safeNum(u.input);
1789
2122
  const output = safeNum(u.output);
@@ -1887,7 +2220,7 @@ async function detectOpencode(homeDir) {
1887
2220
  async function loadEntries4(since, homeDir) {
1888
2221
  const db = await findDb(homeDir);
1889
2222
  if (!db) return [];
1890
- const sql = "SELECT time_created AS ts, json_extract(data,'$.modelID') AS model, json_extract(data,'$.cost') AS cost, json_extract(data,'$.tokens.input') AS input, json_extract(data,'$.tokens.output') AS output, json_extract(data,'$.tokens.reasoning') AS reasoning, json_extract(data,'$.tokens.cache.read') AS cacheRead, json_extract(data,'$.tokens.cache.write') AS cacheWrite FROM message WHERE json_valid(data) AND json_extract(data,'$.role')='assistant' AND json_type(data,'$.tokens')='object' AND time_created >= ?;";
2223
+ const sql = "SELECT CASE WHEN time_created < 10000000000 THEN time_created * 1000 ELSE time_created END AS ts, json_extract(data,'$.modelID') AS model, json_extract(data,'$.cost') AS cost, json_extract(data,'$.tokens.input') AS input, json_extract(data,'$.tokens.output') AS output, json_extract(data,'$.tokens.reasoning') AS reasoning, json_extract(data,'$.tokens.cache.read') AS cacheRead, json_extract(data,'$.tokens.cache.write') AS cacheWrite FROM message WHERE json_valid(data) AND json_extract(data,'$.role')='assistant' AND json_type(data,'$.tokens')='object' AND (CASE WHEN time_created < 10000000000 THEN time_created * 1000 ELSE time_created END) >= ?;";
1891
2224
  const res = await runSqlite(db, sql, [Math.floor(since)]);
1892
2225
  if (res.status !== "ok") return [];
1893
2226
  const entries = [];
@@ -1895,7 +2228,7 @@ async function loadEntries4(since, homeDir) {
1895
2228
  const ts = finitePositive(row.ts);
1896
2229
  if (!ts) continue;
1897
2230
  const input = finitePositive(row.input);
1898
- const output = finitePositive(row.output);
2231
+ const output = finitePositive(row.output) + finitePositive(row.reasoning);
1899
2232
  const cacheRead = finitePositive(row.cacheRead);
1900
2233
  const cacheCreate = finitePositive(row.cacheWrite);
1901
2234
  if (input + output + cacheRead + cacheCreate === 0) continue;
@@ -2156,13 +2489,35 @@ function redactToken(token) {
2156
2489
  function resetDate(value) {
2157
2490
  return typeof value === "string" && value.trim() ? resetIn(value) : null;
2158
2491
  }
2492
+ function numberValue4(value) {
2493
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2494
+ if (typeof value === "string" && value.trim()) {
2495
+ const n = Number(value);
2496
+ if (Number.isFinite(n)) return n;
2497
+ }
2498
+ return void 0;
2499
+ }
2500
+ function boolValue2(value) {
2501
+ if (typeof value === "boolean") return value;
2502
+ if (typeof value === "number") return value !== 0;
2503
+ if (typeof value === "string") {
2504
+ const s = value.trim().toLowerCase();
2505
+ if (s === "true" || s === "1") return true;
2506
+ if (s === "false" || s === "0") return false;
2507
+ }
2508
+ return void 0;
2509
+ }
2159
2510
  function percentMetric2(label, snapshot, reset, primary) {
2160
- if (!snapshot || typeof snapshot.percent_remaining !== "number" || !Number.isFinite(snapshot.percent_remaining)) return null;
2161
- if (snapshot.unlimited === true || snapshot.entitlement === 0) return null;
2162
- const used = Math.min(100, Math.max(0, 100 - snapshot.percent_remaining));
2511
+ if (!snapshot) return null;
2512
+ const entitlement = numberValue4(snapshot.entitlement);
2513
+ const remaining = numberValue4(snapshot.remaining);
2514
+ if (boolValue2(snapshot.unlimited) === true || entitlement === -1 || remaining === -1 || entitlement === 0) return null;
2515
+ const percentRemaining = numberValue4(snapshot.percent_remaining);
2516
+ const used = percentRemaining !== void 0 ? 100 - percentRemaining : entitlement !== void 0 && entitlement > 0 && remaining !== void 0 ? 100 - remaining / entitlement * 100 : void 0;
2517
+ if (used === void 0) return null;
2163
2518
  return {
2164
2519
  label,
2165
- used,
2520
+ used: Math.min(100, Math.max(0, used)),
2166
2521
  limit: 100,
2167
2522
  format: { kind: "percent" },
2168
2523
  resetsAt: reset,
@@ -2170,15 +2525,26 @@ function percentMetric2(label, snapshot, reset, primary) {
2170
2525
  };
2171
2526
  }
2172
2527
  function countMetric(label, remaining, total, reset) {
2173
- if (typeof remaining !== "number" || typeof total !== "number" || !Number.isFinite(remaining) || !Number.isFinite(total) || total <= 0) return null;
2528
+ const remainingCount = numberValue4(remaining);
2529
+ const totalCount = numberValue4(total);
2530
+ if (remainingCount === void 0 || totalCount === void 0 || totalCount <= 0) return null;
2174
2531
  return {
2175
2532
  label,
2176
- used: Math.max(0, total - remaining),
2177
- limit: total,
2533
+ used: Math.max(0, totalCount - remainingCount),
2534
+ limit: totalCount,
2178
2535
  format: { kind: "count" },
2179
2536
  resetsAt: reset
2180
2537
  };
2181
2538
  }
2539
+ function overageMetric(snapshot) {
2540
+ if (!snapshot || boolValue2(snapshot.overage_permitted) !== true) return null;
2541
+ return {
2542
+ label: "Extra",
2543
+ used: Math.max(0, numberValue4(snapshot.overage_count) ?? 0),
2544
+ limit: null,
2545
+ format: { kind: "count" }
2546
+ };
2547
+ }
2182
2548
  async function fetchUsage(token) {
2183
2549
  try {
2184
2550
  const res = await fetch(USAGE_URL3, {
@@ -2213,18 +2579,24 @@ async function copilotBilling(account) {
2213
2579
  const metrics = [];
2214
2580
  const quotaReset = resetDate(data.quota_reset_date);
2215
2581
  const snapshots = data.quota_snapshots;
2216
- const premium = percentMetric2("Premium", snapshots?.premium_interactions, quotaReset, true);
2582
+ const premium = percentMetric2("Credits", snapshots?.premium_interactions, quotaReset, true);
2217
2583
  if (premium) metrics.push(premium);
2584
+ const overage = overageMetric(snapshots?.premium_interactions);
2585
+ if (overage) metrics.push(overage);
2218
2586
  const chat = percentMetric2("Chat", snapshots?.chat, quotaReset);
2219
2587
  if (chat) metrics.push(chat);
2220
- if (data.limited_user_quotas && data.monthly_quotas) {
2588
+ const completionsSnapshot = percentMetric2("Completions", snapshots?.completions, quotaReset);
2589
+ if (completionsSnapshot) metrics.push(completionsSnapshot);
2590
+ if (metrics.length === 0 && data.limited_user_quotas && data.monthly_quotas) {
2221
2591
  const reset = resetDate(data.limited_user_reset_date);
2222
2592
  const limitedChat = countMetric("Chat", data.limited_user_quotas.chat, data.monthly_quotas.chat, reset);
2223
2593
  if (limitedChat) metrics.push(limitedChat);
2224
2594
  const completions = countMetric("Completions", data.limited_user_quotas.completions, data.monthly_quotas.completions, reset);
2225
2595
  if (completions) metrics.push(completions);
2226
2596
  }
2227
- if (metrics.length === 0) return { plan, metrics: [], error: "No usage data" };
2597
+ if (metrics.length === 0) {
2598
+ return { plan, metrics: [], error: boolValue2(data.token_based_billing) === true ? null : "No usage data" };
2599
+ }
2228
2600
  return { plan, metrics, error: null };
2229
2601
  }
2230
2602
 
@@ -2589,11 +2961,11 @@ async function fetchCloudCodeQuota(token, expiredMessage = "Token expired") {
2589
2961
  function normalizeLabel(label) {
2590
2962
  return label.replace(/\s*\([^)]*\)\s*$/, "").trim();
2591
2963
  }
2592
- function poolLabel(label) {
2964
+ function poolLabel(label, fullGeminiLabels = false) {
2593
2965
  const lower = normalizeLabel(label).toLowerCase();
2594
2966
  if (lower.includes("claude")) return "Claude";
2595
- if (lower.includes("gemini") && lower.includes("pro")) return "Pro";
2596
- if (lower.includes("gemini") && lower.includes("flash")) return "Flash";
2967
+ if (lower.includes("gemini") && lower.includes("pro")) return fullGeminiLabels ? "Gemini Pro" : "Pro";
2968
+ if (lower.includes("gemini") && lower.includes("flash")) return fullGeminiLabels ? "Gemini Flash" : "Flash";
2597
2969
  if (lower.includes("gemini")) return "Gemini";
2598
2970
  return normalizeLabel(label) || "Other";
2599
2971
  }
@@ -2609,11 +2981,11 @@ function sortKey(label) {
2609
2981
  if (lower.includes("claude")) return `1b_${label}`;
2610
2982
  return `2_${label}`;
2611
2983
  }
2612
- function cloudCodeBucketsToMetrics(buckets) {
2984
+ function cloudCodeBucketsToMetrics(buckets, options = {}) {
2613
2985
  const pooled = /* @__PURE__ */ new Map();
2614
2986
  for (const bucket of buckets) {
2615
2987
  if (!Number.isFinite(bucket.remainingFraction)) continue;
2616
- const label = poolLabel(bucket.modelId);
2988
+ const label = poolLabel(bucket.modelId, options.fullGeminiLabels === true);
2617
2989
  const existing = pooled.get(label);
2618
2990
  if (!existing || bucket.remainingFraction < existing.remainingFraction) {
2619
2991
  pooled.set(label, { ...bucket, modelId: label });
@@ -2690,7 +3062,7 @@ async function antigravityBilling(account) {
2690
3062
  if (!token) return { plan: null, metrics: [], error: cloudCodeSqliteError(status) };
2691
3063
  const quota = await fetchCloudCodeQuota(token, "Token expired \u2014 open Antigravity");
2692
3064
  if (!quota.ok) return { plan: quota.plan, metrics: [], error: quota.error };
2693
- return { plan: quota.plan, metrics: cloudCodeBucketsToMetrics(quota.buckets), error: null };
3065
+ return { plan: quota.plan, metrics: cloudCodeBucketsToMetrics(quota.buckets, { fullGeminiLabels: true }), error: null };
2694
3066
  } catch {
2695
3067
  return { plan: null, metrics: [], error: "Antigravity billing unavailable" };
2696
3068
  }
@@ -2890,7 +3262,7 @@ async function hasGeminiChatSessions(homeDir) {
2890
3262
  }
2891
3263
  return listing.some((path) => /(^|[\\/])chats[\\/]session-.*\.jsonl$/.test(path));
2892
3264
  }
2893
- function decodeBase64UrlJson2(segment) {
3265
+ function decodeBase64UrlJson3(segment) {
2894
3266
  try {
2895
3267
  const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
2896
3268
  const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
@@ -2900,7 +3272,7 @@ function decodeBase64UrlJson2(segment) {
2900
3272
  }
2901
3273
  }
2902
3274
  function geminiIdentity(creds) {
2903
- const tokenPayload = typeof creds?.id_token === "string" && creds.id_token.includes(".") ? decodeBase64UrlJson2(creds.id_token.split(".")[1]) : null;
3275
+ const tokenPayload = typeof creds?.id_token === "string" && creds.id_token.includes(".") ? decodeBase64UrlJson3(creds.id_token.split(".")[1]) : null;
2904
3276
  const email = typeof creds?.email === "string" && creds.email.trim() || typeof tokenPayload?.email === "string" && tokenPayload.email.trim() || null;
2905
3277
  const displayName = typeof creds?.displayName === "string" && creds.displayName.trim() || typeof creds?.name === "string" && creds.name.trim() || typeof tokenPayload?.name === "string" && tokenPayload.name.trim() || null;
2906
3278
  return { email, displayName };
@@ -3044,7 +3416,7 @@ function labelForClaudeHome(homeDir) {
3044
3416
  const raw = basename(homeDir).replace(/^\.claude[_-]?/, "").replace(/[_-]+/g, " ").trim();
3045
3417
  return raw ? `Claude ${raw}` : "Claude";
3046
3418
  }
3047
- function decodeBase64UrlJson3(segment) {
3419
+ function decodeBase64UrlJson4(segment) {
3048
3420
  try {
3049
3421
  const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
3050
3422
  const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
@@ -3062,7 +3434,7 @@ function readCodexIdentity(homeDir) {
3062
3434
  const parsed = JSON.parse(readFileSync(path, "utf-8"));
3063
3435
  const idToken = parsed?.tokens?.id_token;
3064
3436
  if (typeof idToken !== "string" || !idToken.includes(".")) continue;
3065
- const payload = decodeBase64UrlJson3(idToken.split(".")[1]);
3437
+ const payload = decodeBase64UrlJson4(idToken.split(".")[1]);
3066
3438
  if (!payload || typeof payload !== "object") continue;
3067
3439
  return {
3068
3440
  email: typeof payload?.email === "string" && payload.email.trim() ? payload.email.trim() : void 0,
package/dist/cli.js CHANGED
@@ -14,12 +14,12 @@ process.emitWarning = ((warning, ...rest) => {
14
14
  var args = process.argv.slice(2);
15
15
  var subcommand = args[0]?.toLowerCase();
16
16
  if (subcommand === "__daemon") {
17
- const { runDaemon } = await import("./daemon-HEBPH6PG.js");
17
+ const { runDaemon } = await import("./daemon-ZUWJQEKA.js");
18
18
  await runDaemon(args.slice(1), { foreground: false });
19
19
  process.exit(typeof process.exitCode === "number" ? process.exitCode : 0);
20
20
  }
21
21
  if (subcommand === "serve" || subcommand === "web") {
22
- const { runDaemon } = await import("./daemon-HEBPH6PG.js");
22
+ const { runDaemon } = await import("./daemon-ZUWJQEKA.js");
23
23
  await runDaemon(args.slice(1), { foreground: true });
24
24
  process.exit(typeof process.exitCode === "number" ? process.exitCode : 0);
25
25
  }
@@ -68,5 +68,5 @@ setGlyphs(resolveGlyphs({
68
68
  }));
69
69
  var daemon = await attachOrSpawn();
70
70
  var mode = daemon.kind === "spawned" ? "connected" : "degraded";
71
- var { bootstrapInk } = await import("./bootstrap-ink-MVH5QEVR.js");
71
+ var { bootstrapInk } = await import("./bootstrap-ink-WWSVQO6D.js");
72
72
  await bootstrapInk({ interval, config, daemon, mode });
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  appVersion,
4
4
  startWebServer
5
- } from "./chunk-YYATNY5E.js";
5
+ } from "./chunk-E6YT66ST.js";
6
6
  import {
7
7
  flushDisk
8
- } from "./chunk-EHIQHGJL.js";
8
+ } from "./chunk-YWJT3OG4.js";
9
9
  import {
10
10
  cacheDir,
11
11
  loadConfig
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startWebServer
4
- } from "./chunk-YYATNY5E.js";
5
- import "./chunk-EHIQHGJL.js";
4
+ } from "./chunk-E6YT66ST.js";
5
+ import "./chunk-YWJT3OG4.js";
6
6
  import "./chunk-XQEJ4WQ5.js";
7
7
  export {
8
8
  startWebServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.20.5",
3
+ "version": "0.20.6",
4
4
  "description": "Terminal + web dashboard for Claude Code, Codex, Cursor, opencode, pi, Copilot, Gemini & more — usage, limits, and costs",
5
5
  "type": "module",
6
6
  "bin": {