tokmon 0.20.6 → 0.22.0

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-XQEJ4WQ5.js";
9
+ } from "./chunk-7MFZMI5C.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
@@ -126,6 +126,14 @@ function finitePositiveCoerced(value) {
126
126
  const n = Number(value);
127
127
  return Number.isFinite(n) && n > 0 ? n : 0;
128
128
  }
129
+ function numberValue(value) {
130
+ if (typeof value === "number" && Number.isFinite(value)) return value;
131
+ if (typeof value === "string" && value.trim()) {
132
+ const n = Number(value);
133
+ if (Number.isFinite(n)) return n;
134
+ }
135
+ return void 0;
136
+ }
129
137
  function percentMetric(label, used, resetsAt, primary) {
130
138
  return {
131
139
  label,
@@ -141,16 +149,30 @@ var dollars = (cents) => finite(cents) / 100;
141
149
  // src/providers/usage-core.ts
142
150
  var SPARK_DAYS = 14;
143
151
  var DAY_MS = 864e5;
144
- var CACHE_VERSION = 6;
145
- var STABLE_AGE_MS = 5 * 6e4;
146
- var PRUNE_AGE_MS = 200 * DAY_MS;
152
+ var CACHE_VERSION = 8;
153
+ var PRUNE_AGE_MS = 230 * DAY_MS;
147
154
  var memCache = /* @__PURE__ */ new Map();
148
155
  var diskLoaded = false;
156
+ var diskLoadPromise = null;
149
157
  var dirty = false;
150
158
  var flushTimer = null;
151
159
  function cacheFile() {
152
160
  return join(cacheDir(), `usage-v${CACHE_VERSION}.json`);
153
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
+ }
154
176
  function encode(mtimeMs, size, entries) {
155
177
  const mods = [];
156
178
  const idx = /* @__PURE__ */ new Map();
@@ -189,23 +211,33 @@ function decode(s) {
189
211
  }
190
212
  async function ensureDiskLoaded() {
191
213
  if (diskLoaded) return;
192
- diskLoaded = true;
193
- try {
194
- const obj = JSON.parse(await readFile(cacheFile(), "utf-8"));
195
- for (const [path, s] of Object.entries(obj)) {
196
- if (s && typeof s.m === "number" && Array.isArray(s.rows) && Array.isArray(s.mods)) {
197
- 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;
198
227
  }
199
- }
200
- } catch {
228
+ })().finally(() => {
229
+ diskLoadPromise = null;
230
+ });
201
231
  }
232
+ await diskLoadPromise;
202
233
  }
203
234
  async function flushDisk() {
204
235
  if (!dirty) return;
236
+ dirty = false;
205
237
  const now = Date.now();
206
238
  const obj = {};
207
239
  for (const [path, v] of memCache) {
208
- if (now - v.mtimeMs > STABLE_AGE_MS && now - v.mtimeMs < PRUNE_AGE_MS) {
240
+ if (now - v.mtimeMs < PRUNE_AGE_MS) {
209
241
  obj[path] = encode(v.mtimeMs, v.size, v.entries);
210
242
  }
211
243
  }
@@ -214,8 +246,8 @@ async function flushDisk() {
214
246
  const tmp = `${cacheFile()}.${process.pid}.tmp`;
215
247
  await writeFile(tmp, JSON.stringify(obj));
216
248
  await rename(tmp, cacheFile());
217
- dirty = false;
218
249
  } catch {
250
+ dirty = true;
219
251
  }
220
252
  }
221
253
  function scheduleFlush() {
@@ -223,9 +255,29 @@ function scheduleFlush() {
223
255
  flushTimer = setTimeout(() => {
224
256
  flushTimer = null;
225
257
  void flushDisk();
226
- }, 4e3);
258
+ }, 3e4);
227
259
  flushTimer.unref?.();
228
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
+ }
229
281
  async function mapLimit(items, limit, fn) {
230
282
  let i = 0;
231
283
  const worker = async () => {
@@ -243,7 +295,7 @@ async function loadCachedEntries(files, parse, since) {
243
295
  const entries = await parse(f.path);
244
296
  c = { mtimeMs: f.mtimeMs, size: f.size, entries };
245
297
  memCache.set(f.path, c);
246
- if (Date.now() - f.mtimeMs > STABLE_AGE_MS) dirty = true;
298
+ dirty = true;
247
299
  }
248
300
  chunks.push(c.entries);
249
301
  } catch {
@@ -252,6 +304,15 @@ async function loadCachedEntries(files, parse, since) {
252
304
  if (dirty) scheduleFlush();
253
305
  return dedupe(chunks.flat().filter((e) => e.ts >= since));
254
306
  }
307
+ function lastDayKeys(now, tz, n) {
308
+ const keys = [];
309
+ let cursor = now;
310
+ for (let i = 0; i < n; i++) {
311
+ keys.unshift(dayKey(cursor, tz));
312
+ cursor = startOfDay(cursor, tz) - DAY_MS / 2;
313
+ }
314
+ return keys;
315
+ }
255
316
  function dashboardSince(tz) {
256
317
  const now = Date.now();
257
318
  return Math.min(startOfMonth(now, tz), startOfWeek(now, tz), now - SPARK_DAYS * DAY_MS);
@@ -272,17 +333,14 @@ function cleanEntry(e) {
272
333
  };
273
334
  }
274
335
  function dedupe(entries) {
275
- const seen = /* @__PURE__ */ new Set();
276
- const out = [];
336
+ const byKey = /* @__PURE__ */ new Map();
277
337
  for (const raw of entries) {
278
338
  const e = cleanEntry(raw);
279
339
  if (e.ts <= 0) continue;
280
- const k = e.id ?? `${e.ts} ${e.model} ${e.input} ${e.output} ${e.cacheCreate} ${e.cacheRead}`;
281
- if (seen.has(k)) continue;
282
- seen.add(k);
283
- out.push(e);
340
+ const k = e.id ?? `${e.ts} ${e.model} ${e.input} ${e.output} ${e.cacheCreate} ${e.cacheRead} ${e.cost}`;
341
+ byKey.set(k, e);
284
342
  }
285
- return out;
343
+ return [...byKey.values()];
286
344
  }
287
345
  function summarize(entries, tz) {
288
346
  const now = Date.now();
@@ -316,8 +374,7 @@ function summarize(entries, tz) {
316
374
  const hrs = Math.max((now - oldestToday) / 36e5, 1 / 60);
317
375
  const rawBurnRate = hadToday ? today.cost / hrs : 0;
318
376
  const burnRate = Number.isFinite(rawBurnRate) ? rawBurnRate : 0;
319
- const series = [];
320
- for (let i = SPARK_DAYS - 1; i >= 0; i--) series.push(byDay.get(dayKey(now - i * DAY_MS, tz)) ?? 0);
377
+ const series = lastDayKeys(now, tz, SPARK_DAYS).map((k) => byDay.get(k) ?? 0);
321
378
  return { today, week, month, burnRate, series };
322
379
  }
323
380
  function groupBy(entries, keyFn) {
@@ -515,6 +572,33 @@ async function readJson(res) {
515
572
  }
516
573
  }
517
574
 
575
+ // src/providers/_shared/identity.ts
576
+ function identityFields(identity) {
577
+ return {
578
+ email: identity?.email ?? null,
579
+ displayName: identity?.displayName ?? null
580
+ };
581
+ }
582
+
583
+ // src/providers/_shared/jwt.ts
584
+ function decodeBase64UrlJson(segment) {
585
+ try {
586
+ const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
587
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
588
+ return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
589
+ } catch {
590
+ return null;
591
+ }
592
+ }
593
+ function identityFromIdToken(idToken) {
594
+ if (typeof idToken !== "string" || !idToken.includes(".")) return {};
595
+ const payload = decodeBase64UrlJson(idToken.split(".")[1]);
596
+ if (!payload || typeof payload !== "object") return {};
597
+ const email = typeof payload.email === "string" && payload.email.trim() ? payload.email.trim() : void 0;
598
+ 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;
599
+ return { email, displayName, payload };
600
+ }
601
+
518
602
  // src/providers/_shared/time.ts
519
603
  function msToIso(ms) {
520
604
  return Number.isFinite(ms) && Math.abs(ms) <= 864e13 ? new Date(ms).toISOString() : null;
@@ -636,20 +720,17 @@ function sqliteStatusMessage(status) {
636
720
 
637
721
  // src/providers/cursor/activity.ts
638
722
  var DAY_MS2 = 864e5;
723
+ var HOUR_MS = 36e5;
639
724
  function trackingDb(homeDir) {
640
725
  return join3(homeDir ?? homedir(), ".cursor", "ai-tracking", "ai-code-tracking.db");
641
726
  }
642
- function localDayKey(ms) {
643
- const d = new Date(ms);
644
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
645
- }
646
- async function cursorActivity(homeDir) {
727
+ async function cursorActivity(tz, homeDir) {
647
728
  const db = trackingDb(homeDir);
648
729
  try {
649
730
  const now = Date.now();
650
731
  const res = await runSqlite(
651
732
  db,
652
- `SELECT date(createdAt/1000,'unixepoch','localtime') AS d, count(*) AS c FROM ai_code_hashes WHERE source!='human' AND createdAt >= ${Math.floor(now - 30 * DAY_MS2)} GROUP BY d;`
733
+ `SELECT createdAt/${HOUR_MS} AS h, count(*) AS c FROM ai_code_hashes WHERE source!='human' AND createdAt >= ${Math.floor(now - 30 * DAY_MS2)} GROUP BY h;`
653
734
  );
654
735
  if (res.status !== "ok") return null;
655
736
  const byDay = /* @__PURE__ */ new Map();
@@ -657,11 +738,13 @@ async function cursorActivity(homeDir) {
657
738
  for (const row of res.rows) {
658
739
  const raw = Number(row.c);
659
740
  const n = Number.isFinite(raw) && raw > 0 ? raw : 0;
660
- byDay.set(String(row.d), n);
741
+ const hour = Number(row.h);
742
+ if (!Number.isFinite(hour)) continue;
743
+ const key = dayKey(hour * HOUR_MS + HOUR_MS / 2, tz);
744
+ byDay.set(key, (byDay.get(key) ?? 0) + n);
661
745
  month += n;
662
746
  }
663
- const series = [];
664
- for (let i = SPARK_DAYS - 1; i >= 0; i--) series.push(byDay.get(localDayKey(now - i * DAY_MS2)) ?? 0);
747
+ const series = lastDayKeys(now, tz, SPARK_DAYS).map((k) => byDay.get(k) ?? 0);
665
748
  if (month === 0 && series.every((v) => v === 0)) return null;
666
749
  return { series, summary: `${tokens(month)} lines` };
667
750
  } catch {
@@ -711,23 +794,6 @@ function cleanStoredString(value) {
711
794
  const trimmed = value.trim().replace(/^"|"$/g, "");
712
795
  return trimmed || void 0;
713
796
  }
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
797
  function cursorSessionToken(accessToken) {
732
798
  const payload = accessToken.includes(".") ? decodeBase64UrlJson(accessToken.split(".")[1]) : null;
733
799
  const subject = typeof payload?.sub === "string" ? payload.sub : null;
@@ -736,12 +802,6 @@ function cursorSessionToken(accessToken) {
736
802
  const userId = (parts.length > 1 ? parts[1] : parts[0]).trim();
737
803
  return userId ? `${userId}%3A%3A${accessToken}` : null;
738
804
  }
739
- function identityFields(email, displayName) {
740
- return {
741
- email: email ?? null,
742
- displayName: displayName ?? null
743
- };
744
- }
745
805
  async function connectPost(url, token) {
746
806
  try {
747
807
  const res = await fetch(url, {
@@ -798,10 +858,10 @@ function appendCredits(metrics, creditGrants, stripe) {
798
858
  if (total <= 0) return;
799
859
  metrics.push({ label: "Credits", used: dollars(Math.max(0, total - (hasValidGrants ? grantUsed : 0))), limit: null, format: { kind: "dollars" } });
800
860
  }
801
- async function cursorBilling(account) {
861
+ async function cursorBilling(account, tz) {
802
862
  const [core, activity, spend] = await Promise.all([
803
863
  cursorBillingCore(account),
804
- cursorActivity(account.homeDir),
864
+ cursorActivity(tz, account.homeDir),
805
865
  cursorModelSpend(account.homeDir)
806
866
  ]);
807
867
  let merged = activity;
@@ -831,7 +891,7 @@ async function cursorBillingCore(account) {
831
891
  const planFallback = membership ? membership.charAt(0).toUpperCase() + membership.slice(1) : null;
832
892
  if (!token) {
833
893
  const error = tokenRes.status === "ok" ? "Not signed in \u2014 open Cursor" : sqliteStatusMessage(tokenRes.status);
834
- return { plan: planFallback, metrics: [], error, ...identityFields(email, displayName) };
894
+ return { plan: planFallback, metrics: [], error, ...identityFields({ email, displayName }) };
835
895
  }
836
896
  const [usage, planInfo, creditGrants, stripe] = await Promise.all([
837
897
  connectPost(USAGE_URL, token),
@@ -841,7 +901,7 @@ async function cursorBillingCore(account) {
841
901
  ]);
842
902
  if (!usage || usage.__status) {
843
903
  const expired = usage?.__status === 401 || usage?.__status === 403;
844
- return { plan: planFallback, metrics: [], error: expired ? "Token expired \u2014 re-open Cursor" : "Cursor API error", ...identityFields(email, displayName) };
904
+ return { plan: planFallback, metrics: [], error: expired ? "Token expired \u2014 re-open Cursor" : "Cursor API error", ...identityFields({ email, displayName }) };
845
905
  }
846
906
  const planName = planInfo?.planInfo?.planName ?? planFallback;
847
907
  const price = planInfo?.planInfo?.price;
@@ -852,7 +912,6 @@ async function cursorBillingCore(account) {
852
912
  const endMs = numberValue(rawEnd) ?? NaN;
853
913
  const iso = msToIso(endMs);
854
914
  const resets = iso && endMs > 0 ? resetIn(iso) : null;
855
- appendCredits(metrics, creditGrants, stripe);
856
915
  const limit = numberValue(pu.limit);
857
916
  const planUsedCents = numberValue(pu.totalSpend) ?? (limit !== void 0 && numberValue(pu.remaining) !== void 0 ? limit - numberValue(pu.remaining) : void 0);
858
917
  const computedPercent = limit !== void 0 && limit > 0 && planUsedCents !== void 0 ? planUsedCents / limit * 100 : void 0;
@@ -908,10 +967,11 @@ async function cursorBillingCore(account) {
908
967
  });
909
968
  }
910
969
  }
970
+ appendCredits(metrics, creditGrants, stripe);
911
971
  if (metrics.length === 0) {
912
- return { plan, metrics: [], error: usage.enabled === false ? "No active subscription" : "No usage data", ...identityFields(email, displayName) };
972
+ return { plan, metrics: [], error: usage.enabled === false ? "No active subscription" : "No usage data", ...identityFields({ email, displayName }) };
913
973
  }
914
- return { plan, metrics, error: null, ...identityFields(email, displayName) };
974
+ return { plan, metrics, error: null, ...identityFields({ email, displayName }) };
915
975
  }
916
976
 
917
977
  // src/providers/cursor/composer.ts
@@ -973,7 +1033,7 @@ async function cursorUsageTable(tz, homeDir) {
973
1033
  }
974
1034
 
975
1035
  // src/providers/claude/usage.ts
976
- import { readdir, stat as fsStat, access as access2 } from "fs/promises";
1036
+ import { stat as fsStat, access as access2 } from "fs/promises";
977
1037
  import { createReadStream } from "fs";
978
1038
  import { createInterface } from "readline";
979
1039
  import { join as join5, isAbsolute } from "path";
@@ -985,6 +1045,8 @@ var PRICING = {
985
1045
  "claude-opus-4": { i: 5e-6, o: 25e-6, cc: 625e-8, cr: 5e-7 },
986
1046
  "claude-3-opus": { i: 15e-6, o: 75e-6, cc: 1875e-8, cr: 15e-7 },
987
1047
  "claude-sonnet-4": { 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 },
988
1050
  "claude-haiku-4": { i: 1e-6, o: 5e-6, cc: 125e-8, cr: 1e-7 },
989
1051
  "claude-fable-5": { i: 1e-5, o: 5e-5, cc: 125e-7, cr: 1e-6 }
990
1052
  };
@@ -999,7 +1061,8 @@ function claudeConfigDirs(homeDir) {
999
1061
  const xdg = envDir("XDG_CONFIG_HOME");
1000
1062
  if (xdg) {
1001
1063
  dirs.push(join5(xdg, "claude"));
1002
- } else if (process.platform !== "win32") {
1064
+ }
1065
+ if (process.platform !== "win32") {
1003
1066
  dirs.push(join5(home, ".config", "claude"));
1004
1067
  }
1005
1068
  const appData = envDir("APPDATA");
@@ -1034,9 +1097,10 @@ function priceFor(model) {
1034
1097
  }
1035
1098
  return ZERO_PRICE;
1036
1099
  }
1037
- function costOf(model, u) {
1100
+ function costOf(model, u, cacheCreate5m, cacheCreate1h, hasCacheCreateSplit) {
1038
1101
  const p = priceFor(model);
1039
- 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;
1040
1104
  }
1041
1105
  function shortModel(model) {
1042
1106
  return model.replace("claude-", "").replace(/-\d{8}$/, "");
@@ -1064,7 +1128,10 @@ async function parseFile(path) {
1064
1128
  const model = typeof obj.message.model === "string" && obj.message.model ? obj.message.model : "unknown";
1065
1129
  const inputTokens = safeNum(u.input_tokens);
1066
1130
  const output = safeNum(u.output_tokens);
1067
- 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);
1068
1135
  const cacheRead = safeNum(u.cache_read_input_tokens);
1069
1136
  if (inputTokens + output + cacheCreate + cacheRead === 0) continue;
1070
1137
  const p = priceFor(model);
@@ -1073,7 +1140,7 @@ async function parseFile(path) {
1073
1140
  id: msgId ? msgId + (obj.requestId ? ":" + obj.requestId : "") : void 0,
1074
1141
  ts,
1075
1142
  model: shortModel(model),
1076
- cost: costOf(model, u),
1143
+ cost: costOf(model, u, cacheCreate5m, cacheCreate1h, hasCacheCreateSplit),
1077
1144
  input: inputTokens,
1078
1145
  output,
1079
1146
  cacheCreate,
@@ -1092,12 +1159,7 @@ async function loadEntries(since, homeDir) {
1092
1159
  const seen = /* @__PURE__ */ new Set();
1093
1160
  const seenIno = /* @__PURE__ */ new Set();
1094
1161
  for (const dir of getClaudeDirs(homeDir)) {
1095
- let listing;
1096
- try {
1097
- listing = await readdir(dir, { recursive: true });
1098
- } catch {
1099
- continue;
1100
- }
1162
+ const listing = await walkFiles(dir);
1101
1163
  for (const f of listing) {
1102
1164
  if (!f.endsWith(".jsonl")) continue;
1103
1165
  const path = join5(dir, f);
@@ -1128,9 +1190,9 @@ async function claudeTable(tz, homeDir) {
1128
1190
  }
1129
1191
 
1130
1192
  // src/providers/claude/billing.ts
1131
- import { readFile as readFile2 } from "fs/promises";
1132
- import { join as join6 } from "path";
1133
- import { homedir as homedir4 } from "os";
1193
+ import { access as access3, readFile as readFile2 } from "fs/promises";
1194
+ import { join as join7 } from "path";
1195
+ import { homedir as homedir5 } from "os";
1134
1196
 
1135
1197
  // src/providers/_shared/keychain.ts
1136
1198
  import { execFile as execFileCb2 } from "child_process";
@@ -1152,12 +1214,32 @@ async function readMacKeychainRaw(service) {
1152
1214
  return null;
1153
1215
  }
1154
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
+ }
1155
1234
  function unwrapGoKeyringBase64(raw) {
1156
1235
  if (!raw.startsWith(GO_KEYRING_PREFIX)) return raw;
1157
1236
  return Buffer.from(raw.slice(GO_KEYRING_PREFIX.length), "base64").toString("utf-8");
1158
1237
  }
1159
1238
 
1160
- // src/providers/claude/billing.ts
1239
+ // src/providers/claude/identity.ts
1240
+ import { readFileSync } from "fs";
1241
+ import { join as join6 } from "path";
1242
+ import { homedir as homedir4 } from "os";
1161
1243
  function titleWords(value) {
1162
1244
  return value.split(/[_\s-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join(" ");
1163
1245
  }
@@ -1168,25 +1250,22 @@ function claudeOrgPlanLabel(orgType) {
1168
1250
  const label = titleWords(stripped);
1169
1251
  return label ? `Claude ${label}` : null;
1170
1252
  }
1171
- async function readClaudeIdentity(homeDir) {
1253
+ function readClaudeIdentity(homeDir) {
1172
1254
  const base = homeDir ? expandHome(homeDir) : homedir4();
1173
1255
  try {
1174
- const parsed = JSON.parse(await readFile2(join6(base, ".claude.json"), "utf-8"));
1256
+ const parsed = JSON.parse(readFileSync(join6(base, ".claude.json"), "utf-8"));
1175
1257
  const oauth = parsed?.oauthAccount;
1176
1258
  const email = typeof oauth?.emailAddress === "string" && oauth.emailAddress.trim() ? oauth.emailAddress.trim() : void 0;
1177
1259
  const displayName = typeof oauth?.displayName === "string" && oauth.displayName.trim() ? oauth.displayName.trim() : void 0;
1178
1260
  const plan = claudeOrgPlanLabel(parsed?.organizationType);
1179
- return { email, displayName, plan: plan ?? void 0 };
1261
+ const accountUuid = typeof oauth?.accountUuid === "string" && oauth.accountUuid.trim() ? oauth.accountUuid.trim() : void 0;
1262
+ return { email, displayName, plan: plan ?? void 0, accountUuid };
1180
1263
  } catch {
1181
1264
  return {};
1182
1265
  }
1183
1266
  }
1184
- function identityFields2(identity) {
1185
- return {
1186
- email: identity.email ?? null,
1187
- displayName: identity.displayName ?? null
1188
- };
1189
- }
1267
+
1268
+ // src/providers/claude/billing.ts
1190
1269
  function parseAuth(raw) {
1191
1270
  try {
1192
1271
  const creds = JSON.parse(raw);
@@ -1196,7 +1275,8 @@ function parseAuth(raw) {
1196
1275
  return {
1197
1276
  token,
1198
1277
  subscriptionType: typeof o.subscriptionType === "string" ? o.subscriptionType : void 0,
1199
- rateLimitTier: typeof o.rateLimitTier === "string" ? o.rateLimitTier : void 0
1278
+ rateLimitTier: typeof o.rateLimitTier === "string" ? o.rateLimitTier : void 0,
1279
+ expiresAt: typeof o.expiresAt === "number" && Number.isFinite(o.expiresAt) ? o.expiresAt : void 0
1200
1280
  };
1201
1281
  } catch {
1202
1282
  return null;
@@ -1205,7 +1285,7 @@ function parseAuth(raw) {
1205
1285
  async function readCredentialsFile(homeDir) {
1206
1286
  for (const dir of claudeConfigDirs(homeDir)) {
1207
1287
  try {
1208
- const auth = parseAuth(await readFile2(join6(dir, ".credentials.json"), "utf-8"));
1288
+ const auth = parseAuth(await readFile2(join7(dir, ".credentials.json"), "utf-8"));
1209
1289
  if (auth) return auth;
1210
1290
  } catch {
1211
1291
  }
@@ -1216,20 +1296,84 @@ async function readMacKeychain() {
1216
1296
  const raw = await readMacKeychainRaw("Claude Code-credentials");
1217
1297
  return raw ? parseAuth(raw) : null;
1218
1298
  }
1219
- async function getAuth(homeDir) {
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
+ }
1310
+ async function authCandidates(homeDir) {
1220
1311
  const expandedHomeDir = homeDir ? expandHome(homeDir) : void 0;
1221
- const isDefault = !expandedHomeDir || expandedHomeDir === homedir4();
1222
- if (isDefault) {
1223
- if (process.platform === "darwin") {
1224
- const auth = await readMacKeychain();
1225
- if (auth) return auth;
1312
+ const isDefault = !expandedHomeDir || expandedHomeDir === homedir5();
1313
+ const out = [];
1314
+ const file = await readCredentialsFile(isDefault ? void 0 : expandedHomeDir);
1315
+ const keychain = process.platform === "darwin" ? await readMacKeychain() : null;
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
+ ];
1322
+ for (const c of ordered) if (c) out.push(c);
1323
+ return out;
1324
+ }
1325
+ var tokenIdentityCache = /* @__PURE__ */ new Map();
1326
+ async function tokenIdentity(token) {
1327
+ if (tokenIdentityCache.has(token)) return tokenIdentityCache.get(token);
1328
+ try {
1329
+ const res = await fetch("https://api.anthropic.com/api/oauth/profile", {
1330
+ headers: {
1331
+ "Authorization": `Bearer ${token}`,
1332
+ "anthropic-beta": "oauth-2025-04-20",
1333
+ "User-Agent": "tokmon"
1334
+ },
1335
+ signal: AbortSignal.timeout(1e4)
1336
+ });
1337
+ if (res.status === 401 || res.status === 403) {
1338
+ tokenIdentityCache.set(token, null);
1339
+ return null;
1226
1340
  }
1227
- return readCredentialsFile(void 0);
1341
+ if (!res.ok) return void 0;
1342
+ const data = await readJson(res);
1343
+ const uuid = data?.account?.uuid;
1344
+ if (typeof uuid !== "string" || !uuid) return void 0;
1345
+ const identity = {
1346
+ accountUuid: uuid,
1347
+ email: typeof data?.account?.email === "string" ? data.account.email : null
1348
+ };
1349
+ if (tokenIdentityCache.size > 64) tokenIdentityCache.clear();
1350
+ tokenIdentityCache.set(token, identity);
1351
+ return identity;
1352
+ } catch {
1353
+ return void 0;
1228
1354
  }
1229
- const fileAuth = await readCredentialsFile(expandedHomeDir);
1230
- if (fileAuth) return fileAuth;
1231
- if (process.platform === "darwin") return readMacKeychain();
1232
- return null;
1355
+ }
1356
+ async function getAuth(homeDir, expectedUuid) {
1357
+ const candidates = await authCandidates(homeDir);
1358
+ let wrongAccountEmail;
1359
+ let sawExpired = false;
1360
+ let sawExpiredOwn = false;
1361
+ for (const { auth, shared } of candidates) {
1362
+ if (auth.expiresAt !== void 0 && auth.expiresAt < Date.now() - 6e4) {
1363
+ sawExpired = true;
1364
+ if (!shared) sawExpiredOwn = true;
1365
+ continue;
1366
+ }
1367
+ if (!shared || !expectedUuid) return { auth };
1368
+ const identity = await tokenIdentity(auth.token);
1369
+ if (identity === void 0) return { auth };
1370
+ if (identity === null) continue;
1371
+ if (identity.accountUuid === expectedUuid) return { auth };
1372
+ wrongAccountEmail = identity.email;
1373
+ }
1374
+ if (sawExpiredOwn) return { auth: null, expired: true };
1375
+ if (wrongAccountEmail !== void 0) return { auth: null, wrongAccountEmail };
1376
+ return { auth: null, expired: sawExpired };
1233
1377
  }
1234
1378
  function planLabel(auth) {
1235
1379
  const sub = auth.subscriptionType;
@@ -1239,14 +1383,6 @@ function planLabel(auth) {
1239
1383
  return tier ? `${base} ${tier[1]}x` : base;
1240
1384
  }
1241
1385
  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
1386
  function boolValue(value) {
1251
1387
  if (typeof value === "boolean") return value;
1252
1388
  if (typeof value === "number") return value !== 0;
@@ -1255,22 +1391,92 @@ function boolValue(value) {
1255
1391
  }
1256
1392
  function resetFrom(value) {
1257
1393
  if (typeof value === "string" && value.trim()) return resetIn(value);
1258
- const n = numberValue2(value);
1394
+ const n = numberValue(value);
1259
1395
  if (n === void 0) return null;
1260
- const ms = Math.abs(n) < 1e10 ? n * 1e3 : n;
1261
- return resetIn(new Date(ms).toISOString());
1396
+ const iso = msToIso(Math.abs(n) < 1e10 ? n * 1e3 : n);
1397
+ return iso ? resetIn(iso) : null;
1262
1398
  }
1263
1399
  function usageMetric(label, window, primary) {
1264
- const used = numberValue2(window?.utilization);
1400
+ const used = numberValue(window?.utilization);
1265
1401
  if (used === void 0) return null;
1266
1402
  return { ...pct(used, resetFrom(window?.resets_at), primary), label };
1267
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
+ }
1268
1473
  async function claudeBilling(account) {
1269
- const [auth, identity] = await Promise.all([
1270
- getAuth(account.homeDir),
1271
- readClaudeIdentity(account.homeDir)
1272
- ]);
1273
- if (!auth) return { plan: identity.plan ?? null, metrics: [], error: "No OAuth token \u2014 run claude and log in", ...identityFields2(identity) };
1474
+ const identity = readClaudeIdentity(account.homeDir);
1475
+ const { auth, wrongAccountEmail, expired } = await getAuth(account.homeDir, identity.accountUuid);
1476
+ if (!auth) {
1477
+ const error = wrongAccountEmail !== void 0 ? `Signed in as ${wrongAccountEmail ?? "another account"} \u2014 run claude in this home to refresh` : expired ? "Token expired \u2014 run claude to refresh" : "No OAuth token \u2014 run claude and log in";
1478
+ return { plan: identity.plan ?? null, metrics: [], error, ...identityFields(identity) };
1479
+ }
1274
1480
  const plan = identity.plan ?? planLabel(auth);
1275
1481
  try {
1276
1482
  const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
@@ -1282,36 +1488,32 @@ async function claudeBilling(account) {
1282
1488
  signal: AbortSignal.timeout(1e4)
1283
1489
  });
1284
1490
  if (res.status === 429) {
1285
- const retryAfter = numberValue2(res.headers.get("retry-after"));
1491
+ const retryAfter = numberValue(res.headers.get("retry-after"));
1286
1492
  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) };
1493
+ return { plan, metrics: [], error: `Rate limited${retryText}`, ...identityFields(identity) };
1288
1494
  }
1289
- if (res.status === 401) return { plan, metrics: [], error: "Token expired \u2014 restart Claude Code", ...identityFields2(identity) };
1290
- if (!res.ok) return { plan, metrics: [], error: `API ${res.status}`, ...identityFields2(identity) };
1495
+ if (res.status === 401) return { plan, metrics: [], error: "Token expired \u2014 run claude to refresh", ...identityFields(identity) };
1496
+ if (!res.ok) return { plan, metrics: [], error: `API ${res.status}`, ...identityFields(identity) };
1291
1497
  const data = await readJson(res);
1292
- if (!data) return { plan, metrics: [], error: "Unexpected API response", ...identityFields2(identity) };
1293
- const metrics = [];
1294
- const fiveHour = usageMetric("Session", data.five_hour, true);
1295
- if (fiveHour) metrics.push(fiveHour);
1296
- const sevenDay = usageMetric("Weekly", data.seven_day);
1297
- if (sevenDay) metrics.push(sevenDay);
1298
- const sevenDaySonnet = usageMetric("Sonnet", data.seven_day_sonnet);
1299
- 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));
1300
1501
  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);
1502
+ const usedCredits = numberValue(data.extra_usage?.used_credits);
1503
+ const monthlyLimit = numberValue(data.extra_usage?.monthly_limit);
1303
1504
  if (usedCredits !== void 0 && (usedCredits > 0 || monthlyLimit !== void 0 && monthlyLimit > 0)) {
1505
+ const scale = decimalScale(data.extra_usage?.decimal_places);
1304
1506
  metrics.push({
1305
1507
  label: "Extra",
1306
- used: finite(usedCredits) / 100,
1307
- limit: monthlyLimit !== void 0 && monthlyLimit > 0 ? monthlyLimit / 100 : null,
1508
+ used: finite(usedCredits) / scale,
1509
+ limit: monthlyLimit !== void 0 && monthlyLimit > 0 ? monthlyLimit / scale : null,
1308
1510
  format: { kind: "dollars", currency: data.extra_usage?.currency ?? "USD" }
1309
1511
  });
1310
1512
  }
1311
1513
  }
1312
- return { plan, metrics, error: null, ...identityFields2(identity) };
1514
+ return { plan, metrics, error: null, ...identityFields(identity) };
1313
1515
  } catch {
1314
- return { plan, metrics: [], error: "Network error", ...identityFields2(identity) };
1516
+ return { plan, metrics: [], error: "Network error", ...identityFields(identity) };
1315
1517
  }
1316
1518
  }
1317
1519
 
@@ -1329,42 +1531,47 @@ var claudeProvider = {
1329
1531
  };
1330
1532
 
1331
1533
  // src/providers/codex/usage.ts
1332
- 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";
1333
1535
  import { createReadStream as createReadStream2 } from "fs";
1334
1536
  import { createInterface as createInterface2 } from "readline";
1335
- import { join as join7 } from "path";
1336
- import { homedir as homedir5 } from "os";
1537
+ import { join as join8 } from "path";
1538
+ import { homedir as homedir6 } from "os";
1337
1539
  var PRICING2 = {
1338
1540
  "gpt-5.5-codex": { in: 5e-6, cr: 5e-7, out: 3e-5 },
1339
1541
  "gpt-5.5": { in: 5e-6, cr: 5e-7, out: 3e-5 },
1340
1542
  "gpt-5.4-codex": { in: 25e-7, cr: 25e-8, out: 15e-6 },
1341
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 },
1342
1549
  "gpt-5-codex": { in: 125e-8, cr: 125e-9, out: 1e-5 },
1343
1550
  "gpt-5-mini": { in: 25e-8, cr: 25e-9, out: 2e-6 },
1344
1551
  "gpt-5-nano": { in: 5e-8, cr: 5e-9, out: 4e-7 },
1345
1552
  "gpt-5": { in: 125e-8, cr: 125e-9, out: 1e-5 },
1346
1553
  "o4-mini": { in: 11e-7, cr: 275e-9, out: 44e-7 }
1347
1554
  };
1348
- var ZERO_PRICE2 = { in: 0, cr: 0, out: 0 };
1555
+ var FALLBACK_PRICE = PRICING2["gpt-5.5"];
1349
1556
  var PRICE_KEYS2 = Object.keys(PRICING2).sort((a, b) => b.length - a.length);
1350
1557
  function codexHomes(homeDir) {
1351
- if (homeDir) return [.../* @__PURE__ */ new Set([join7(homeDir, ".codex"), homeDir])];
1558
+ if (homeDir) return [.../* @__PURE__ */ new Set([join8(homeDir, ".codex"), homeDir])];
1352
1559
  const homes = [];
1353
1560
  const codexHome = envDir("CODEX_HOME");
1354
1561
  if (codexHome) homes.push(codexHome);
1355
- homes.push(join7(homedir5(), ".codex"));
1356
- homes.push(join7(homedir5(), ".config", "codex"));
1562
+ homes.push(join8(homedir6(), ".codex"));
1563
+ homes.push(join8(homedir6(), ".config", "codex"));
1357
1564
  return [...new Set(homes)];
1358
1565
  }
1359
1566
  async function detectCodex(homeDir) {
1360
1567
  for (const home of codexHomes(homeDir)) {
1361
1568
  try {
1362
- await access3(join7(home, "sessions"));
1569
+ await access4(join8(home, "sessions"));
1363
1570
  return true;
1364
1571
  } catch {
1365
1572
  }
1366
1573
  try {
1367
- await access3(join7(home, "archived_sessions"));
1574
+ await access4(join8(home, "archived_sessions"));
1368
1575
  return true;
1369
1576
  } catch {
1370
1577
  }
@@ -1376,7 +1583,8 @@ function modelKeyMatches(model, key) {
1376
1583
  while (idx >= 0) {
1377
1584
  const before = idx === 0 ? "" : model[idx - 1];
1378
1585
  const rest = model.slice(idx + key.length);
1379
- if ((!before || !/[a-z0-9-]/.test(before)) && (rest === "" || rest[0] === "-" || !/[a-z0-9]/.test(rest[0]))) {
1586
+ const versionContinues = rest[0] === "." && /\d/.test(rest[1] ?? "");
1587
+ if ((!before || !/[a-z0-9-]/.test(before)) && !versionContinues && (rest === "" || rest[0] === "-" || !/[a-z0-9]/.test(rest[0]))) {
1380
1588
  return true;
1381
1589
  }
1382
1590
  idx = model.indexOf(key, idx + key.length);
@@ -1388,7 +1596,7 @@ function priceFor2(model) {
1388
1596
  for (const key of PRICE_KEYS2) {
1389
1597
  if (modelKeyMatches(m, key)) return PRICING2[key];
1390
1598
  }
1391
- return ZERO_PRICE2;
1599
+ return FALLBACK_PRICE;
1392
1600
  }
1393
1601
  function extractModel(obj) {
1394
1602
  const p = obj?.payload ?? obj;
@@ -1597,20 +1805,16 @@ async function loadEntries2(since, homeDir) {
1597
1805
  const seen = /* @__PURE__ */ new Set();
1598
1806
  const seenIno = /* @__PURE__ */ new Set();
1599
1807
  for (const home of codexHomes(homeDir)) {
1600
- for (const dir of [join7(home, "sessions"), join7(home, "archived_sessions")]) {
1601
- let listing;
1602
- try {
1603
- listing = await readdir2(dir, { recursive: true });
1604
- } catch {
1605
- continue;
1606
- }
1808
+ for (const dir of [join8(home, "sessions"), join8(home, "archived_sessions")]) {
1809
+ const listing = await walkFiles(dir);
1607
1810
  for (const f of listing) {
1608
1811
  if (!f.endsWith(".jsonl")) continue;
1609
- const path = join7(dir, f);
1812
+ const path = join8(dir, f);
1610
1813
  if (seen.has(path)) continue;
1611
1814
  seen.add(path);
1612
1815
  try {
1613
1816
  const s = await fsStat2(path);
1817
+ if (s.mtimeMs < since) continue;
1614
1818
  if (s.ino && process.platform !== "win32") {
1615
1819
  const idn = `${s.dev}:${s.ino}`;
1616
1820
  if (seenIno.has(idn)) continue;
@@ -1634,22 +1838,13 @@ async function codexTable(tz, homeDir) {
1634
1838
  }
1635
1839
 
1636
1840
  // src/providers/codex/billing.ts
1637
- 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";
1638
1842
  import { createReadStream as createReadStream3 } from "fs";
1639
1843
  import { createInterface as createInterface3 } from "readline";
1640
- import { join as join8 } from "path";
1844
+ import { join as join9 } from "path";
1641
1845
  var USAGE_URL2 = "https://chatgpt.com/backend-api/wham/usage";
1642
1846
  var RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
1643
1847
  var CREDIT_USD_RATE = 0.04;
1644
- function decodeBase64UrlJson2(segment) {
1645
- try {
1646
- const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
1647
- const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
1648
- return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
1649
- } catch {
1650
- return null;
1651
- }
1652
- }
1653
1848
  function chatGptPlanLabel(planType) {
1654
1849
  if (typeof planType !== "string" || !planType.trim()) return null;
1655
1850
  const p = planType.trim().toLowerCase();
@@ -1664,28 +1859,19 @@ function chatGptPlanLabel(planType) {
1664
1859
  if (labels[p]) return labels[p];
1665
1860
  return `ChatGPT ${p.charAt(0).toUpperCase()}${p.slice(1)}`;
1666
1861
  }
1667
- function identityFromIdToken(idToken) {
1668
- if (typeof idToken !== "string" || !idToken.includes(".")) return {};
1669
- const payload = decodeBase64UrlJson2(idToken.split(".")[1]);
1670
- if (!payload || typeof payload !== "object") return {};
1671
- const email = typeof payload.email === "string" && payload.email.trim() ? payload.email.trim() : void 0;
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;
1862
+ function codexIdentity(idToken) {
1863
+ const { email, displayName, payload } = identityFromIdToken(idToken);
1864
+ if (!payload) return {};
1673
1865
  const plan = chatGptPlanLabel(payload["https://api.openai.com/auth"]?.chatgpt_plan_type);
1674
1866
  return { email, displayName, plan: plan ?? void 0 };
1675
1867
  }
1676
- function identityFields3(auth) {
1677
- return {
1678
- email: auth?.email ?? null,
1679
- displayName: auth?.displayName ?? null
1680
- };
1681
- }
1682
1868
  async function readAuthFile(home) {
1683
1869
  try {
1684
- const raw = await readFile3(join8(home, "auth.json"), "utf-8");
1870
+ const raw = await readFile3(join9(home, "auth.json"), "utf-8");
1685
1871
  const auth = JSON.parse(raw);
1686
1872
  const accessToken = auth?.tokens?.access_token;
1687
1873
  if (!accessToken) return null;
1688
- return { accessToken, accountId: auth?.tokens?.account_id, ...identityFromIdToken(auth?.tokens?.id_token) };
1874
+ return { accessToken, accountId: auth?.tokens?.account_id, ...codexIdentity(auth?.tokens?.id_token) };
1689
1875
  } catch {
1690
1876
  return null;
1691
1877
  }
@@ -1697,7 +1883,7 @@ async function readKeychainAuth() {
1697
1883
  const auth = JSON.parse(raw);
1698
1884
  const accessToken = auth?.tokens?.access_token;
1699
1885
  if (!accessToken) return null;
1700
- return { accessToken, accountId: auth?.tokens?.account_id, ...identityFromIdToken(auth?.tokens?.id_token) };
1886
+ return { accessToken, accountId: auth?.tokens?.account_id, ...codexIdentity(auth?.tokens?.id_token) };
1701
1887
  } catch {
1702
1888
  return null;
1703
1889
  }
@@ -1707,7 +1893,7 @@ async function getAuth2(homeDir) {
1707
1893
  const auth = await readAuthFile(home);
1708
1894
  if (auth) return auth;
1709
1895
  }
1710
- if (process.platform === "darwin") return readKeychainAuth();
1896
+ if (!homeDir && process.platform === "darwin") return readKeychainAuth();
1711
1897
  return null;
1712
1898
  }
1713
1899
  function planLabel2(planType) {
@@ -1717,19 +1903,11 @@ function planLabel2(planType) {
1717
1903
  if (p === "pro") return "Pro 20x";
1718
1904
  return planType.charAt(0).toUpperCase() + planType.slice(1);
1719
1905
  }
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
1906
  function resetDateMs(window) {
1729
1907
  if (!window) return null;
1730
- const absolute = numberValue3(window.reset_at ?? window.resets_at);
1908
+ const absolute = numberValue(window.reset_at ?? window.resets_at);
1731
1909
  if (absolute !== void 0) return absolute > 1e10 ? absolute : absolute * 1e3;
1732
- const after = numberValue3(window.reset_after_seconds);
1910
+ const after = numberValue(window.reset_after_seconds);
1733
1911
  if (after !== void 0) return Date.now() + after * 1e3;
1734
1912
  for (const key of ["reset_at", "resets_at"]) {
1735
1913
  const raw = window[key];
@@ -1745,10 +1923,16 @@ function resetFrom2(window) {
1745
1923
  const iso = ms === null ? null : msToIso(ms);
1746
1924
  return iso ? resetIn(iso) : null;
1747
1925
  }
1926
+ function windowSeconds(window) {
1927
+ const seconds = numberValue(window?.limit_window_seconds ?? window?.period_seconds ?? window?.window_seconds);
1928
+ if (seconds !== void 0) return seconds;
1929
+ const minutes = numberValue(window?.limit_window_minutes ?? window?.window_minutes ?? window?.period_minutes);
1930
+ return minutes === void 0 ? void 0 : minutes * 60;
1931
+ }
1748
1932
  function normalizedUsedPercent(window, percent) {
1749
- const used = numberValue3(percent);
1933
+ const used = numberValue(percent);
1750
1934
  if (used === void 0) return void 0;
1751
- const periodSeconds = numberValue3(window?.limit_window_seconds ?? window?.period_seconds ?? window?.window_seconds);
1935
+ const periodSeconds = windowSeconds(window);
1752
1936
  const resetMs = resetDateMs(window);
1753
1937
  if (periodSeconds && resetMs) {
1754
1938
  const elapsedMs = periodSeconds * 1e3 - Math.max(0, resetMs - Date.now());
@@ -1759,7 +1943,7 @@ function normalizedUsedPercent(window, percent) {
1759
1943
  function metricLabelForAdditional(window) {
1760
1944
  const name = String(window?.limit_name ?? window?.name ?? window?.metered_feature ?? window?.feature ?? "").toLowerCase();
1761
1945
  if (!name.includes("spark")) return null;
1762
- const seconds = numberValue3(window?.limit_window_seconds ?? window?.period_seconds ?? window?.window_seconds);
1946
+ const seconds = windowSeconds(window);
1763
1947
  return name.includes("week") || seconds !== void 0 && seconds >= 6 * 24 * 60 * 60 ? "Spark Weekly" : "Spark";
1764
1948
  }
1765
1949
  function additionalRateLimits(rl) {
@@ -1784,9 +1968,9 @@ function appendWindowMetrics(metrics, rl, headerPct) {
1784
1968
  }
1785
1969
  }
1786
1970
  function appendCredits2(metrics, source) {
1787
- const balance = numberValue3(source?.credits?.balance ?? source?.credit_balance);
1971
+ const balance = numberValue(source?.credits?.balance ?? source?.credit_balance);
1788
1972
  if (balance !== void 0 && balance >= 0) {
1789
- metrics.push({ label: "Credits", used: Math.floor(balance) * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
1973
+ metrics.push({ label: "Credits", used: balance * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
1790
1974
  }
1791
1975
  }
1792
1976
  async function fetchResetCredits(headers) {
@@ -1801,7 +1985,7 @@ async function fetchResetCredits(headers) {
1801
1985
  });
1802
1986
  if (!res.ok) return void 0;
1803
1987
  const data = await readJson(res);
1804
- return numberValue3(data?.available_count ?? data?.available ?? data?.remaining);
1988
+ return numberValue(data?.available_count ?? data?.available ?? data?.remaining);
1805
1989
  } catch {
1806
1990
  return void 0;
1807
1991
  }
@@ -1827,41 +2011,41 @@ async function liveBilling(auth) {
1827
2011
  const metrics = [];
1828
2012
  appendWindowMetrics(metrics, data.rate_limit ?? data, headerPct);
1829
2013
  appendCredits2(metrics, data);
1830
- const resetCredits = await fetchResetCredits(headers) ?? numberValue3(data?.rate_limit_reset_credits?.available_count ?? data?.rate_limit_reset_credits?.available);
2014
+ const resetCredits = numberValue(data?.rate_limit_reset_credits?.available_count ?? data?.rate_limit_reset_credits?.available) ?? await fetchResetCredits(headers);
1831
2015
  if (resetCredits !== void 0 && resetCredits >= 0) {
1832
- metrics.push({ label: "Rate Limit Resets", used: resetCredits, limit: null, format: { kind: "count", suffix: "available" } });
2016
+ metrics.push({ label: "Resets", used: resetCredits, limit: null, format: { kind: "count", suffix: "available" } });
1833
2017
  }
1834
2018
  if (metrics.length === 0) return null;
1835
- return { plan: auth.plan ?? planLabel2(data.plan_type), metrics, error: null, ...identityFields3(auth) };
2019
+ return { plan: auth.plan ?? planLabel2(data.plan_type), metrics, error: null, ...identityFields(auth) };
1836
2020
  } catch {
1837
2021
  return null;
1838
2022
  }
1839
2023
  }
1840
- async function newestRolloutFile(homeDir) {
1841
- let best = null;
2024
+ var SNAPSHOT_CANDIDATES = 8;
2025
+ var SNAPSHOT_STALE_MS = 24 * 36e5;
2026
+ async function newestRolloutFiles(homeDir) {
2027
+ const all = [];
1842
2028
  for (const home of codexHomes(homeDir)) {
1843
- const dir = join8(home, "sessions");
2029
+ const dir = join9(home, "sessions");
1844
2030
  let listing;
1845
2031
  try {
1846
- listing = await readdir3(dir, { recursive: true });
2032
+ listing = await readdir2(dir, { recursive: true });
1847
2033
  } catch {
1848
2034
  continue;
1849
2035
  }
1850
2036
  for (const f of listing) {
1851
2037
  if (!f.endsWith(".jsonl") || !f.includes("rollout-")) continue;
1852
- const path = join8(dir, f);
2038
+ const path = join9(dir, f);
1853
2039
  try {
1854
2040
  const s = await fsStat3(path);
1855
- if (!best || s.mtimeMs > best.mtime) best = { path, mtime: s.mtimeMs };
2041
+ all.push({ path, mtime: s.mtimeMs });
1856
2042
  } catch {
1857
2043
  }
1858
2044
  }
1859
2045
  }
1860
- return best?.path ?? null;
2046
+ return all.sort((a, b) => b.mtime - a.mtime).slice(0, SNAPSHOT_CANDIDATES);
1861
2047
  }
1862
- async function snapshotBilling(homeDir, auth = null) {
1863
- const path = await newestRolloutFile(homeDir);
1864
- if (!path) return null;
2048
+ async function lastRateLimits(path) {
1865
2049
  let last = null;
1866
2050
  try {
1867
2051
  const input = createReadStream3(path);
@@ -1879,16 +2063,23 @@ async function snapshotBilling(homeDir, auth = null) {
1879
2063
  } catch {
1880
2064
  return null;
1881
2065
  }
1882
- if (!last) return null;
1883
- const metrics = [];
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" } });
2066
+ return last;
2067
+ }
2068
+ async function snapshotBilling(homeDir, auth = null) {
2069
+ for (const file of await newestRolloutFiles(homeDir)) {
2070
+ const last = await lastRateLimits(file.path);
2071
+ if (!last) continue;
2072
+ const metrics = [];
2073
+ appendWindowMetrics(metrics, last.rate_limit ?? last);
2074
+ appendCredits2(metrics, last);
2075
+ const resetCredits = numberValue(last?.rate_limit_reset_credits?.available_count ?? last?.rate_limit_reset_credits?.available);
2076
+ if (resetCredits !== void 0 && resetCredits >= 0) {
2077
+ metrics.push({ label: "Resets", used: resetCredits, limit: null, format: { kind: "count", suffix: "available" } });
2078
+ }
2079
+ if (metrics.length === 0) continue;
2080
+ return { plan: auth?.plan ?? planLabel2(last.plan_type), metrics, error: null, asOfMs: file.mtime, ...identityFields(auth) };
1889
2081
  }
1890
- if (metrics.length === 0) return null;
1891
- return { plan: auth?.plan ?? planLabel2(last.plan_type), metrics, error: null, ...identityFields3(auth) };
2082
+ return null;
1892
2083
  }
1893
2084
  async function codexBilling(account) {
1894
2085
  const auth = await getAuth2(account.homeDir);
@@ -1897,12 +2088,15 @@ async function codexBilling(account) {
1897
2088
  if (live) return live;
1898
2089
  }
1899
2090
  const snap = await snapshotBilling(account.homeDir, auth);
1900
- if (snap) return snap;
2091
+ if (snap && Date.now() - snap.asOfMs < SNAPSHOT_STALE_MS) {
2092
+ const { asOfMs: _asOfMs, ...result } = snap;
2093
+ return result;
2094
+ }
1901
2095
  return {
1902
- plan: auth?.plan ?? null,
2096
+ plan: auth?.plan ?? snap?.plan ?? null,
1903
2097
  metrics: [],
1904
2098
  error: auth ? "Usage API failed \u2014 run codex to refresh" : "Not logged in \u2014 run codex",
1905
- ...identityFields3(auth)
2099
+ ...identityFields(auth)
1906
2100
  };
1907
2101
  }
1908
2102
 
@@ -2079,21 +2273,21 @@ var cursorProvider = {
2079
2273
  hasBilling: true,
2080
2274
  detect: (homeDir) => detectCursor(homeDir),
2081
2275
  fetchTable: (account, tz) => cursorTable(tz, account.homeDir),
2082
- fetchBilling: (account) => cursorBilling(account)
2276
+ fetchBilling: (account, tz) => cursorBilling(account, tz)
2083
2277
  };
2084
2278
 
2085
2279
  // src/providers/pi/usage.ts
2086
- import { readdir as readdir4, stat as fsStat4, access as access4 } from "fs/promises";
2280
+ import { stat as fsStat4, access as access5 } from "fs/promises";
2087
2281
  import { createReadStream as createReadStream4 } from "fs";
2088
2282
  import { createInterface as createInterface4 } from "readline";
2089
- import { join as join9 } from "path";
2090
- import { homedir as homedir6 } from "os";
2283
+ import { join as join10 } from "path";
2284
+ import { homedir as homedir7 } from "os";
2091
2285
  function piSessionsDir(homeDir) {
2092
- return join9(homeDir ?? homedir6(), ".pi", "agent", "sessions");
2286
+ return join10(homeDir ?? homedir7(), ".pi", "agent", "sessions");
2093
2287
  }
2094
2288
  async function detectPi(homeDir) {
2095
2289
  try {
2096
- await access4(piSessionsDir(homeDir));
2290
+ await access5(piSessionsDir(homeDir));
2097
2291
  return true;
2098
2292
  } catch {
2099
2293
  return false;
@@ -2146,15 +2340,10 @@ async function loadEntries3(since, homeDir) {
2146
2340
  const dir = piSessionsDir(homeDir);
2147
2341
  const files = [];
2148
2342
  const seenIno = /* @__PURE__ */ new Set();
2149
- let listing;
2150
- try {
2151
- listing = await readdir4(dir, { recursive: true });
2152
- } catch {
2153
- return [];
2154
- }
2343
+ const listing = await walkFiles(dir);
2155
2344
  for (const f of listing) {
2156
2345
  if (!f.endsWith(".jsonl")) continue;
2157
- const path = join9(dir, f);
2346
+ const path = join10(dir, f);
2158
2347
  try {
2159
2348
  const s = await fsStat4(path);
2160
2349
  if (s.mtimeMs < since) continue;
@@ -2189,25 +2378,25 @@ var piProvider = {
2189
2378
  };
2190
2379
 
2191
2380
  // src/providers/opencode/usage.ts
2192
- import { access as access5 } from "fs/promises";
2193
- import { join as join10 } from "path";
2194
- import { homedir as homedir7 } from "os";
2381
+ import { access as access6 } from "fs/promises";
2382
+ import { join as join11 } from "path";
2383
+ import { homedir as homedir8 } from "os";
2195
2384
  function opencodeDbPaths(homeDir) {
2196
- const base = homeDir ?? homedir7();
2385
+ const base = homeDir ?? homedir8();
2197
2386
  const paths = [];
2198
- if (!homeDir && process.env.XDG_DATA_HOME) paths.push(join10(process.env.XDG_DATA_HOME, "opencode", "opencode.db"));
2199
- paths.push(join10(base, ".local", "share", "opencode", "opencode.db"));
2200
- if (process.platform === "darwin") paths.push(join10(base, "Library", "Application Support", "opencode", "opencode.db"));
2387
+ if (!homeDir && process.env.XDG_DATA_HOME) paths.push(join11(process.env.XDG_DATA_HOME, "opencode", "opencode.db"));
2388
+ paths.push(join11(base, ".local", "share", "opencode", "opencode.db"));
2389
+ if (process.platform === "darwin") paths.push(join11(base, "Library", "Application Support", "opencode", "opencode.db"));
2201
2390
  if (process.platform === "win32") {
2202
- const lad = homeDir ? join10(homeDir, "AppData", "Local") : process.env.LOCALAPPDATA;
2203
- if (lad) paths.push(join10(lad, "opencode", "opencode.db"));
2391
+ const lad = homeDir ? join11(homeDir, "AppData", "Local") : process.env.LOCALAPPDATA;
2392
+ if (lad) paths.push(join11(lad, "opencode", "opencode.db"));
2204
2393
  }
2205
2394
  return [...new Set(paths)];
2206
2395
  }
2207
2396
  async function findDb(homeDir) {
2208
2397
  for (const p of opencodeDbPaths(homeDir)) {
2209
2398
  try {
2210
- await access5(p);
2399
+ await access6(p);
2211
2400
  return p;
2212
2401
  } catch {
2213
2402
  }
@@ -2265,134 +2454,45 @@ var opencodeProvider = {
2265
2454
  };
2266
2455
 
2267
2456
  // src/providers/copilot/billing.ts
2268
- 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";
2269
2458
  import { join as join12 } from "path";
2270
2459
  import { homedir as homedir9 } from "os";
2271
-
2272
- // src/providers/detect.ts
2273
- import { accessSync as accessSync2, constants as constants2, existsSync } from "fs";
2274
- import { join as join11, delimiter as delimiter2, isAbsolute as isAbsolute2 } from "path";
2275
- import { homedir as homedir8 } from "os";
2276
- function searchDirs() {
2277
- const home = homedir8();
2278
- const fromEnv = (process.env.PATH ?? "").split(delimiter2).filter(Boolean);
2279
- const extra = process.platform === "win32" ? [
2280
- process.env.APPDATA && join11(process.env.APPDATA, "npm"),
2281
- process.env.LOCALAPPDATA && join11(process.env.LOCALAPPDATA, "pnpm"),
2282
- join11(home, "scoop", "shims")
2283
- ] : [
2284
- "/opt/homebrew/bin",
2285
- "/usr/local/bin",
2286
- "/usr/bin",
2287
- "/bin",
2288
- "/opt/local/bin",
2289
- join11(home, ".local", "bin"),
2290
- join11(home, "bin"),
2291
- join11(home, ".npm-global", "bin"),
2292
- join11(home, ".bun", "bin"),
2293
- join11(home, ".local", "share", "pnpm")
2294
- ];
2295
- return [.../* @__PURE__ */ new Set([...fromEnv, ...extra.filter((d) => !!d)])];
2296
- }
2297
- function isExec2(p) {
2298
- try {
2299
- accessSync2(p, process.platform === "win32" ? constants2.F_OK : constants2.X_OK);
2300
- return true;
2301
- } catch {
2302
- return false;
2303
- }
2304
- }
2305
- function onPath(names) {
2306
- const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").map((e) => e.toLowerCase()).concat("") : [""];
2307
- for (const dir of searchDirs()) {
2308
- for (const n of names) {
2309
- for (const e of exts) {
2310
- if (isExec2(join11(dir, n + e))) return true;
2311
- }
2460
+ var USAGE_URL3 = "https://api.github.com/copilot_internal/user";
2461
+ var GH_KEYCHAIN_SERVICE = "gh:github.com";
2462
+ function ghConfigDir(homeDir) {
2463
+ if (!homeDir) {
2464
+ const explicit = process.env.GH_CONFIG_DIR;
2465
+ if (explicit && explicit.trim()) return explicit.trim();
2466
+ if (process.platform === "win32") {
2467
+ return join12(envDir("APPDATA") ?? join12(homedir9(), "AppData", "Roaming"), "GitHub CLI");
2312
2468
  }
2469
+ const xdg = envDir("XDG_CONFIG_HOME");
2470
+ return xdg ? join12(xdg, "gh") : join12(homedir9(), ".config", "gh");
2313
2471
  }
2314
- return false;
2315
- }
2316
- function anyExists(paths) {
2317
- return paths.some((p) => !!p && isExec2(p));
2318
- }
2319
- function installSignals(id) {
2320
- const home = homedir8();
2321
- const pf = process.env.ProgramFiles;
2322
- const pf86 = process.env["ProgramFiles(x86)"];
2323
- const lad = process.env.LOCALAPPDATA;
2324
- switch (id) {
2325
- case "claude":
2326
- return onPath(["claude"]) || anyExists([
2327
- "/Applications/Claude.app",
2328
- join11(home, "Applications", "Claude.app"),
2329
- lad && join11(lad, "Programs", "claude", "Claude.exe")
2330
- ]);
2331
- case "codex": {
2332
- const bin = process.env.CODEX_BIN;
2333
- if (bin && isAbsolute2(bin) && isExec2(bin)) return true;
2334
- return onPath(["codex"]) || anyExists([
2335
- lad && join11(lad, "Programs", "OpenAI", "Codex", "bin", "codex.exe"),
2336
- lad && join11(lad, "Programs", "OpenAI", "Codex", "codex.exe"),
2337
- lad && join11(lad, "Programs", "codex", "codex.exe"),
2338
- pf && join11(pf, "OpenAI", "Codex", "bin", "codex.exe")
2339
- ]) || existsSync(join11(home, ".codex", "sessions")) || existsSync(join11(home, ".codex", "auth.json"));
2340
- }
2341
- case "cursor":
2342
- return onPath(["cursor", "cursor-agent"]) || anyExists([
2343
- "/Applications/Cursor.app",
2344
- join11(home, "Applications", "Cursor.app"),
2345
- lad && join11(lad, "Programs", "cursor", "Cursor.exe"),
2346
- pf && join11(pf, "Cursor", "Cursor.exe"),
2347
- pf86 && join11(pf86, "Cursor", "Cursor.exe"),
2348
- "/opt/Cursor/cursor",
2349
- "/usr/share/cursor/cursor",
2350
- "/usr/bin/cursor"
2351
- ]);
2352
- case "pi":
2353
- return onPath(["pi"]);
2354
- case "opencode":
2355
- return onPath(["opencode"]);
2356
- case "copilot":
2357
- return onPath(["gh"]);
2358
- case "antigravity":
2359
- return onPath(["antigravity"]) || anyExists([
2360
- "/Applications/Antigravity.app",
2361
- join11(home, "Applications", "Antigravity.app"),
2362
- lad && join11(lad, "Programs", "Antigravity", "Antigravity.exe")
2363
- ]);
2364
- case "gemini":
2365
- return onPath(["gemini"]);
2366
- default:
2367
- return false;
2368
- }
2369
- }
2370
-
2371
- // src/providers/copilot/billing.ts
2372
- var USAGE_URL3 = "https://api.github.com/copilot_internal/user";
2373
- var GH_KEYCHAIN_SERVICE = "gh:github.com";
2374
- function ghConfigDir(homeDir) {
2375
- if (!homeDir) {
2376
- const explicit = process.env.GH_CONFIG_DIR;
2377
- if (explicit && explicit.trim()) return explicit.trim();
2378
- if (process.platform === "win32") {
2379
- return join12(envDir("APPDATA") ?? join12(homedir9(), "AppData", "Roaming"), "GitHub CLI");
2380
- }
2381
- const xdg = envDir("XDG_CONFIG_HOME");
2382
- return xdg ? join12(xdg, "gh") : join12(homedir9(), ".config", "gh");
2383
- }
2384
- return process.platform === "win32" ? join12(homeDir, "AppData", "Roaming", "GitHub CLI") : join12(homeDir, ".config", "gh");
2472
+ return process.platform === "win32" ? join12(homeDir, "AppData", "Roaming", "GitHub CLI") : join12(homeDir, ".config", "gh");
2385
2473
  }
2386
2474
  function ghHostsPath(homeDir) {
2387
2475
  return join12(ghConfigDir(homeDir), "hosts.yml");
2388
2476
  }
2389
2477
  async function detectCopilot(homeDir) {
2390
- try {
2391
- await access6(ghHostsPath(homeDir));
2392
- return true;
2393
- } catch {
2478
+ const home = homeDir ?? homedir9();
2479
+ const candidates = [
2480
+ join12(home, ".config", "github-copilot"),
2481
+ join12(home, ".copilot"),
2482
+ join12(vscodeUserDir(homeDir), "globalStorage", "github.copilot"),
2483
+ join12(vscodeUserDir(homeDir), "globalStorage", "github.copilot-chat")
2484
+ ];
2485
+ if (process.platform === "win32") {
2486
+ candidates.push(join12(envDir("LOCALAPPDATA") ?? join12(home, "AppData", "Local"), "github-copilot"));
2487
+ }
2488
+ for (const path of candidates) {
2489
+ try {
2490
+ await access7(path);
2491
+ return true;
2492
+ } catch {
2493
+ }
2394
2494
  }
2395
- return onPath(["gh"]);
2495
+ return false;
2396
2496
  }
2397
2497
  function unquoteYamlValue(value) {
2398
2498
  const trimmed = value.trim();
@@ -2464,7 +2564,7 @@ async function loadTokenFromVsCode(homeDir) {
2464
2564
  join12(userDir, "globalStorage", "state.vscdb")
2465
2565
  ];
2466
2566
  try {
2467
- for (const dirent of await readdir5(join12(userDir, "globalStorage"), { withFileTypes: true })) {
2567
+ for (const dirent of await readdir3(join12(userDir, "globalStorage"), { withFileTypes: true })) {
2468
2568
  if (dirent.isDirectory() && dirent.name.toLowerCase().includes("github")) {
2469
2569
  candidates.push(join12(userDir, "globalStorage", dirent.name, "auth.json"));
2470
2570
  }
@@ -2481,7 +2581,7 @@ async function loadTokenFromVsCode(homeDir) {
2481
2581
  return null;
2482
2582
  }
2483
2583
  async function loadToken(homeDir) {
2484
- return await loadTokenFromHosts(homeDir) || await loadTokenFromGhKeychain() || await loadTokenFromVsCode(homeDir);
2584
+ return await loadTokenFromHosts(homeDir) || (homeDir ? null : await loadTokenFromGhKeychain()) || await loadTokenFromVsCode(homeDir);
2485
2585
  }
2486
2586
  function redactToken(token) {
2487
2587
  return token.length <= 4 ? "****" : `****${token.slice(-4)}`;
@@ -2489,14 +2589,6 @@ function redactToken(token) {
2489
2589
  function resetDate(value) {
2490
2590
  return typeof value === "string" && value.trim() ? resetIn(value) : null;
2491
2591
  }
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
2592
  function boolValue2(value) {
2501
2593
  if (typeof value === "boolean") return value;
2502
2594
  if (typeof value === "number") return value !== 0;
@@ -2509,10 +2601,10 @@ function boolValue2(value) {
2509
2601
  }
2510
2602
  function percentMetric2(label, snapshot, reset, primary) {
2511
2603
  if (!snapshot) return null;
2512
- const entitlement = numberValue4(snapshot.entitlement);
2513
- const remaining = numberValue4(snapshot.remaining);
2604
+ const entitlement = numberValue(snapshot.entitlement);
2605
+ const remaining = numberValue(snapshot.remaining);
2514
2606
  if (boolValue2(snapshot.unlimited) === true || entitlement === -1 || remaining === -1 || entitlement === 0) return null;
2515
- const percentRemaining = numberValue4(snapshot.percent_remaining);
2607
+ const percentRemaining = numberValue(snapshot.percent_remaining);
2516
2608
  const used = percentRemaining !== void 0 ? 100 - percentRemaining : entitlement !== void 0 && entitlement > 0 && remaining !== void 0 ? 100 - remaining / entitlement * 100 : void 0;
2517
2609
  if (used === void 0) return null;
2518
2610
  return {
@@ -2525,8 +2617,8 @@ function percentMetric2(label, snapshot, reset, primary) {
2525
2617
  };
2526
2618
  }
2527
2619
  function countMetric(label, remaining, total, reset) {
2528
- const remainingCount = numberValue4(remaining);
2529
- const totalCount = numberValue4(total);
2620
+ const remainingCount = numberValue(remaining);
2621
+ const totalCount = numberValue(total);
2530
2622
  if (remainingCount === void 0 || totalCount === void 0 || totalCount <= 0) return null;
2531
2623
  return {
2532
2624
  label,
@@ -2540,7 +2632,7 @@ function overageMetric(snapshot) {
2540
2632
  if (!snapshot || boolValue2(snapshot.overage_permitted) !== true) return null;
2541
2633
  return {
2542
2634
  label: "Extra",
2543
- used: Math.max(0, numberValue4(snapshot.overage_count) ?? 0),
2635
+ used: Math.max(0, numberValue(snapshot.overage_count) ?? 0),
2544
2636
  limit: null,
2545
2637
  format: { kind: "count" }
2546
2638
  };
@@ -2612,39 +2704,21 @@ var copilotProvider = {
2612
2704
  };
2613
2705
 
2614
2706
  // src/providers/antigravity/billing.ts
2615
- import { access as access7, readdir as readdir7 } from "fs/promises";
2707
+ import { access as access8, readdir as readdir5 } from "fs/promises";
2616
2708
  import { join as join14 } from "path";
2617
2709
  import { homedir as homedir11 } from "os";
2618
2710
 
2619
- // src/providers/cloud-code.ts
2620
- import { readFile as readFile5, readdir as readdir6, realpath, stat } from "fs/promises";
2711
+ // src/providers/cloud-code/auth.ts
2712
+ import { readFile as readFile5, readdir as readdir4, realpath, stat } from "fs/promises";
2621
2713
  import { spawnSync } from "child_process";
2622
2714
  import { homedir as homedir10 } from "os";
2623
2715
  import { dirname, join as join13 } from "path";
2624
- var CLOUD_CODE_URLS = [
2625
- "https://daily-cloudcode-pa.googleapis.com",
2626
- "https://cloudcode-pa.googleapis.com"
2627
- ];
2628
- var LOAD_CODE_ASSIST_PATH = "/v1internal:loadCodeAssist";
2629
- var FETCH_MODELS_PATH = "/v1internal:fetchAvailableModels";
2630
- var RETRIEVE_QUOTA_PATH = "/v1internal:retrieveUserQuota";
2631
2716
  var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
2632
2717
  var GOOGLE_OAUTH_CLIENT_REGEX = /OAUTH_CLIENT_ID\s*=\s*["']([0-9]{6,}-[a-z0-9]+\.apps\.googleusercontent\.com)["']\s*;?\s*(?:var|const|let)?\s*OAUTH_CLIENT_SECRET\s*=\s*["'](GOCSPX-[A-Za-z0-9_-]+)["']/s;
2633
2718
  var MAX_BUNDLE_READ = 32 * 1024 * 1024;
2634
2719
  var cachedClient;
2635
2720
  var OAUTH_TOKEN_KEY = "antigravityUnifiedStateSync.oauthToken";
2636
2721
  var OAUTH_TOKEN_SENTINEL = "oauthTokenInfoSentinelKey";
2637
- var CC_MODEL_BLACKLIST = {
2638
- MODEL_CHAT_20706: true,
2639
- MODEL_CHAT_23310: true,
2640
- MODEL_GOOGLE_GEMINI_2_5_FLASH: true,
2641
- MODEL_GOOGLE_GEMINI_2_5_FLASH_THINKING: true,
2642
- MODEL_GOOGLE_GEMINI_2_5_FLASH_LITE: true,
2643
- MODEL_GOOGLE_GEMINI_2_5_PRO: true,
2644
- MODEL_PLACEHOLDER_M19: true,
2645
- MODEL_PLACEHOLDER_M9: true,
2646
- MODEL_PLACEHOLDER_M12: true
2647
- };
2648
2722
  function readVarint(bytes, start) {
2649
2723
  let value = 0;
2650
2724
  let shift = 0;
@@ -2738,10 +2812,6 @@ async function readAntigravityOAuthToken(db) {
2738
2812
  if (!accessToken && !refreshToken) return { token: null, status: "ok" };
2739
2813
  return { token: { accessToken, refreshToken, expirySeconds }, status: "ok" };
2740
2814
  }
2741
- function redact(token) {
2742
- if (!token) return "none";
2743
- return `...${token.slice(-4)}`;
2744
- }
2745
2815
  function geminiBundleCandidates() {
2746
2816
  const candidates = [];
2747
2817
  const addBundle = (nodeModulesRoot) => {
@@ -2791,7 +2861,7 @@ async function resolveBundleDir(candidate) {
2791
2861
  async function scanBundleDir(dir) {
2792
2862
  let entries;
2793
2863
  try {
2794
- entries = await readdir6(dir);
2864
+ entries = await readdir4(dir);
2795
2865
  } catch {
2796
2866
  return null;
2797
2867
  }
@@ -2853,6 +2923,30 @@ async function refreshAccessToken(refreshToken) {
2853
2923
  return null;
2854
2924
  }
2855
2925
  }
2926
+
2927
+ // src/providers/cloud-code/api.ts
2928
+ var CLOUD_CODE_URLS = [
2929
+ "https://daily-cloudcode-pa.googleapis.com",
2930
+ "https://cloudcode-pa.googleapis.com"
2931
+ ];
2932
+ var LOAD_CODE_ASSIST_PATH = "/v1internal:loadCodeAssist";
2933
+ var FETCH_MODELS_PATH = "/v1internal:fetchAvailableModels";
2934
+ var RETRIEVE_QUOTA_PATH = "/v1internal:retrieveUserQuota";
2935
+ var CC_MODEL_BLACKLIST = {
2936
+ MODEL_CHAT_20706: true,
2937
+ MODEL_CHAT_23310: true,
2938
+ MODEL_GOOGLE_GEMINI_2_5_FLASH: true,
2939
+ MODEL_GOOGLE_GEMINI_2_5_FLASH_THINKING: true,
2940
+ MODEL_GOOGLE_GEMINI_2_5_FLASH_LITE: true,
2941
+ MODEL_GOOGLE_GEMINI_2_5_PRO: true,
2942
+ MODEL_PLACEHOLDER_M19: true,
2943
+ MODEL_PLACEHOLDER_M9: true,
2944
+ MODEL_PLACEHOLDER_M12: true
2945
+ };
2946
+ function redact(token) {
2947
+ if (!token) return "none";
2948
+ return `...${token.slice(-4)}`;
2949
+ }
2856
2950
  async function requestCloudCodeJson(path, token, body) {
2857
2951
  for (const base of CLOUD_CODE_URLS) {
2858
2952
  try {
@@ -2958,6 +3052,8 @@ async function fetchCloudCodeQuota(token, expiredMessage = "Token expired") {
2958
3052
  if (!refreshed) return { ok: false, plan: result.plan, error: expiredMessage };
2959
3053
  return fetchWithAccessToken(refreshed);
2960
3054
  }
3055
+
3056
+ // src/providers/cloud-code/index.ts
2961
3057
  function normalizeLabel(label) {
2962
3058
  return label.replace(/\s*\([^)]*\)\s*$/, "").trim();
2963
3059
  }
@@ -3010,7 +3106,7 @@ function cloudCodeSqliteError(status) {
3010
3106
  // src/providers/antigravity/billing.ts
3011
3107
  async function exists(path) {
3012
3108
  try {
3013
- await access7(path);
3109
+ await access8(path);
3014
3110
  return true;
3015
3111
  } catch {
3016
3112
  return false;
@@ -3032,7 +3128,7 @@ async function antigravityStateDb(homeDir) {
3032
3128
  join14(support, "Antigravity", ...tail)
3033
3129
  ];
3034
3130
  try {
3035
- const entries = await readdir7(support, { withFileTypes: true });
3131
+ const entries = await readdir5(support, { withFileTypes: true });
3036
3132
  const matches = entries.filter((e) => e.isDirectory() && e.name.includes("Antigravity")).map((e) => join14(support, e.name, ...tail));
3037
3133
  return firstExisting([...exact, ...matches]);
3038
3134
  } catch {
@@ -3080,12 +3176,12 @@ var antigravityProvider = {
3080
3176
  };
3081
3177
 
3082
3178
  // src/providers/gemini/billing.ts
3083
- 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";
3084
3180
  import { join as join16 } from "path";
3085
3181
  import { homedir as homedir13 } from "os";
3086
3182
 
3087
3183
  // src/providers/gemini/usage.ts
3088
- import { readdir as readdir8, stat as fsStat5 } from "fs/promises";
3184
+ import { readFile as readFile6, stat as fsStat5 } from "fs/promises";
3089
3185
  import { createReadStream as createReadStream5 } from "fs";
3090
3186
  import { createInterface as createInterface5 } from "readline";
3091
3187
  import { join as join15 } from "path";
@@ -3096,8 +3192,8 @@ var PRICING3 = {
3096
3192
  "gemini-3-pro-preview": { in: 2e-6, out: 12e-6, cr: 2e-7 },
3097
3193
  "gemini-3-pro": { in: 2e-6, out: 12e-6, cr: 2e-7 },
3098
3194
  "gemini-3.5-flash": { in: 15e-7, out: 9e-6, cr: 15e-8 },
3099
- "gemini-3-flash-preview": { in: 15e-7, out: 9e-6, cr: 15e-8 },
3100
- "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 },
3101
3197
  "gemini-2.5-flash-lite": { in: 1e-7, out: 4e-7, cr: 1e-8 },
3102
3198
  "gemini-3.1-flash-lite": { in: 1e-7, out: 4e-7, cr: 1e-8 },
3103
3199
  "gemini-2.5-flash": { in: 3e-7, out: 25e-7, cr: 3e-8 },
@@ -3105,7 +3201,7 @@ var PRICING3 = {
3105
3201
  "gemini-2.0-flash": { in: 1e-7, out: 4e-7, cr: 25e-9 }
3106
3202
  };
3107
3203
  var PRICE_KEYS3 = Object.keys(PRICING3).sort((a, b) => b.length - a.length);
3108
- var ZERO_PRICE3 = { in: 0, out: 0, cr: 0 };
3204
+ var ZERO_PRICE2 = { in: 0, out: 0, cr: 0 };
3109
3205
  function geminiTmpDir(homeDir) {
3110
3206
  return join15(homeDir ?? homedir12(), ".gemini", "tmp");
3111
3207
  }
@@ -3116,7 +3212,7 @@ function priceFor3(model) {
3116
3212
  const rest = m.slice(key.length);
3117
3213
  if (rest === "" || rest[0] === "-") return PRICING3[key];
3118
3214
  }
3119
- return ZERO_PRICE3;
3215
+ return ZERO_PRICE2;
3120
3216
  }
3121
3217
  function shortModel2(model) {
3122
3218
  return model.replace(/(-preview|-customtools)+$/, "");
@@ -3124,52 +3220,90 @@ function shortModel2(model) {
3124
3220
  function isGeminiSessionFile(path) {
3125
3221
  return /(^|[\\/])chats[\\/]session-.*\.jsonl$/.test(path) || /(^|[\\/])chats[\\/]session-.*\.json$/.test(path);
3126
3222
  }
3127
- 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) {
3128
3270
  const entries = [];
3129
- const rl = createInterface5({ input: createReadStream5(path), crlfDelay: Infinity });
3130
- 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")) {
3131
3290
  try {
3132
- const obj = JSON.parse(line.charCodeAt(0) === 65279 ? line.slice(1) : line);
3133
- if (obj.sessionId && obj.kind || obj.$set || obj.$rewindTo) continue;
3134
- if (obj.type !== "gemini" || !obj.tokens) continue;
3135
- const ts = Date.parse(obj.timestamp ?? "");
3136
- if (!Number.isFinite(ts)) continue;
3137
- const t = obj.tokens;
3138
- const input = Math.max(0, safeNum(t.input) + safeNum(t.tool) - safeNum(t.cached));
3139
- const output = safeNum(t.output) + safeNum(t.thoughts);
3140
- const cacheRead = safeNum(t.cached);
3141
- if (input + output + cacheRead === 0) continue;
3142
- const model = typeof obj.model === "string" && obj.model ? obj.model : "unknown";
3143
- const p = priceFor3(model);
3144
- entries.push({
3145
- id: typeof obj.id === "string" ? obj.id : void 0,
3146
- ts,
3147
- model: shortModel2(model),
3148
- cost: input * p.in + cacheRead * p.cr + output * p.out,
3149
- input,
3150
- output,
3151
- cacheCreate: 0,
3152
- cacheRead,
3153
- cacheSavings: cacheRead * (p.in - p.cr)
3154
- });
3291
+ const raw = await readFile6(path, "utf-8");
3292
+ return entriesFromJson(JSON.parse(raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw));
3155
3293
  } catch {
3156
3294
  }
3157
3295
  }
3158
- return entries;
3296
+ return parseLineFile(path);
3159
3297
  }
3160
3298
  async function loadEntries5(since, homeDir) {
3161
3299
  const files = [];
3162
3300
  const seen = /* @__PURE__ */ new Set();
3163
3301
  const seenIno = /* @__PURE__ */ new Set();
3164
- let listing;
3165
- try {
3166
- listing = await readdir8(geminiTmpDir(homeDir), { recursive: true });
3167
- } catch {
3168
- return [];
3169
- }
3302
+ const root = geminiTmpDir(homeDir);
3303
+ const listing = await walkFiles(root);
3170
3304
  for (const f of listing) {
3171
3305
  if (!isGeminiSessionFile(f)) continue;
3172
- const path = join15(geminiTmpDir(homeDir), f);
3306
+ const path = join15(root, f);
3173
3307
  if (seen.has(path)) continue;
3174
3308
  seen.add(path);
3175
3309
  try {
@@ -3213,7 +3347,7 @@ function authTypeFromSettings(settings) {
3213
3347
  }
3214
3348
  async function authMethodFromSettings(homeDir) {
3215
3349
  try {
3216
- const raw = await readFile6(join16(geminiDir(homeDir), "settings.json"), "utf8");
3350
+ const raw = await readFile7(join16(geminiDir(homeDir), "settings.json"), "utf8");
3217
3351
  return authTypeFromSettings(JSON.parse(raw));
3218
3352
  } catch {
3219
3353
  return "none";
@@ -3221,12 +3355,12 @@ async function authMethodFromSettings(homeDir) {
3221
3355
  }
3222
3356
  async function hasGeminiApiKeyFile(homeDir) {
3223
3357
  try {
3224
- await access8(join16(geminiDir(homeDir), "api_key"));
3358
+ await access9(join16(geminiDir(homeDir), "api_key"));
3225
3359
  return true;
3226
3360
  } catch {
3227
3361
  }
3228
3362
  try {
3229
- const env = await readFile6(join16(geminiDir(homeDir), ".env"), "utf8");
3363
+ const env = await readFile7(join16(geminiDir(homeDir), ".env"), "utf8");
3230
3364
  return /^\s*GEMINI_API_KEY\s*=/m.test(env);
3231
3365
  } catch {
3232
3366
  return false;
@@ -3247,7 +3381,7 @@ async function noOAuthAuthMessage(homeDir) {
3247
3381
  async function detectGemini(homeDir) {
3248
3382
  let oauthOk = false;
3249
3383
  try {
3250
- await access8(geminiCredsPath(homeDir));
3384
+ await access9(geminiCredsPath(homeDir));
3251
3385
  oauthOk = true;
3252
3386
  } catch {
3253
3387
  }
@@ -3256,30 +3390,21 @@ async function detectGemini(homeDir) {
3256
3390
  async function hasGeminiChatSessions(homeDir) {
3257
3391
  let listing;
3258
3392
  try {
3259
- listing = await readdir9(geminiTmpDir(homeDir), { recursive: true });
3393
+ listing = await readdir6(geminiTmpDir(homeDir), { recursive: true });
3260
3394
  } catch {
3261
3395
  return false;
3262
3396
  }
3263
3397
  return listing.some((path) => /(^|[\\/])chats[\\/]session-.*\.jsonl$/.test(path));
3264
3398
  }
3265
- function decodeBase64UrlJson3(segment) {
3266
- try {
3267
- const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
3268
- const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
3269
- return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
3270
- } catch {
3271
- return null;
3272
- }
3273
- }
3274
3399
  function geminiIdentity(creds) {
3275
- const tokenPayload = typeof creds?.id_token === "string" && creds.id_token.includes(".") ? decodeBase64UrlJson3(creds.id_token.split(".")[1]) : null;
3400
+ const tokenPayload = typeof creds?.id_token === "string" && creds.id_token.includes(".") ? decodeBase64UrlJson(creds.id_token.split(".")[1]) : null;
3276
3401
  const email = typeof creds?.email === "string" && creds.email.trim() || typeof tokenPayload?.email === "string" && tokenPayload.email.trim() || null;
3277
3402
  const displayName = typeof creds?.displayName === "string" && creds.displayName.trim() || typeof creds?.name === "string" && creds.name.trim() || typeof tokenPayload?.name === "string" && tokenPayload.name.trim() || null;
3278
3403
  return { email, displayName };
3279
3404
  }
3280
3405
  async function readGeminiCreds(path) {
3281
3406
  try {
3282
- return JSON.parse(await readFile6(path, "utf8"));
3407
+ return JSON.parse(await readFile7(path, "utf8"));
3283
3408
  } catch {
3284
3409
  return null;
3285
3410
  }
@@ -3323,6 +3448,113 @@ var geminiProvider = {
3323
3448
  fetchBilling: (account) => geminiBilling(account)
3324
3449
  };
3325
3450
 
3451
+ // src/providers/detect.ts
3452
+ import { accessSync as accessSync2, constants as constants2, existsSync } from "fs";
3453
+ import { join as join17, delimiter as delimiter2, isAbsolute as isAbsolute2 } from "path";
3454
+ import { homedir as homedir14 } from "os";
3455
+ function searchDirs() {
3456
+ const home = homedir14();
3457
+ const fromEnv = (process.env.PATH ?? "").split(delimiter2).filter(Boolean);
3458
+ const extra = process.platform === "win32" ? [
3459
+ process.env.APPDATA && join17(process.env.APPDATA, "npm"),
3460
+ process.env.LOCALAPPDATA && join17(process.env.LOCALAPPDATA, "pnpm"),
3461
+ join17(home, "scoop", "shims")
3462
+ ] : [
3463
+ "/opt/homebrew/bin",
3464
+ "/usr/local/bin",
3465
+ "/usr/bin",
3466
+ "/bin",
3467
+ "/opt/local/bin",
3468
+ join17(home, ".local", "bin"),
3469
+ join17(home, "bin"),
3470
+ join17(home, ".npm-global", "bin"),
3471
+ join17(home, ".bun", "bin"),
3472
+ join17(home, ".local", "share", "pnpm")
3473
+ ];
3474
+ return [.../* @__PURE__ */ new Set([...fromEnv, ...extra.filter((d) => !!d)])];
3475
+ }
3476
+ function isExec2(p) {
3477
+ try {
3478
+ accessSync2(p, process.platform === "win32" ? constants2.F_OK : constants2.X_OK);
3479
+ return true;
3480
+ } catch {
3481
+ return false;
3482
+ }
3483
+ }
3484
+ function onPath(names) {
3485
+ const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").map((e) => e.toLowerCase()).concat("") : [""];
3486
+ for (const dir of searchDirs()) {
3487
+ for (const n of names) {
3488
+ for (const e of exts) {
3489
+ if (isExec2(join17(dir, n + e))) return true;
3490
+ }
3491
+ }
3492
+ }
3493
+ return false;
3494
+ }
3495
+ function anyExists(paths) {
3496
+ return paths.some((p) => !!p && isExec2(p));
3497
+ }
3498
+ function installSignals(id) {
3499
+ const home = homedir14();
3500
+ const pf = process.env.ProgramFiles;
3501
+ const pf86 = process.env["ProgramFiles(x86)"];
3502
+ const lad = process.env.LOCALAPPDATA;
3503
+ switch (id) {
3504
+ case "claude":
3505
+ return onPath(["claude"]) || anyExists([
3506
+ "/Applications/Claude.app",
3507
+ join17(home, "Applications", "Claude.app"),
3508
+ lad && join17(lad, "Programs", "claude", "Claude.exe")
3509
+ ]);
3510
+ case "codex": {
3511
+ const bin = process.env.CODEX_BIN;
3512
+ if (bin && isAbsolute2(bin) && isExec2(bin)) return true;
3513
+ return onPath(["codex"]) || anyExists([
3514
+ lad && join17(lad, "Programs", "OpenAI", "Codex", "bin", "codex.exe"),
3515
+ lad && join17(lad, "Programs", "OpenAI", "Codex", "codex.exe"),
3516
+ lad && join17(lad, "Programs", "codex", "codex.exe"),
3517
+ pf && join17(pf, "OpenAI", "Codex", "bin", "codex.exe")
3518
+ ]) || existsSync(join17(home, ".codex", "sessions")) || existsSync(join17(home, ".codex", "auth.json"));
3519
+ }
3520
+ case "cursor":
3521
+ return onPath(["cursor", "cursor-agent"]) || anyExists([
3522
+ "/Applications/Cursor.app",
3523
+ join17(home, "Applications", "Cursor.app"),
3524
+ lad && join17(lad, "Programs", "cursor", "Cursor.exe"),
3525
+ pf && join17(pf, "Cursor", "Cursor.exe"),
3526
+ pf86 && join17(pf86, "Cursor", "Cursor.exe"),
3527
+ "/opt/Cursor/cursor",
3528
+ "/usr/share/cursor/cursor",
3529
+ "/usr/bin/cursor"
3530
+ ]);
3531
+ case "pi":
3532
+ return onPath(["pi"]);
3533
+ case "opencode":
3534
+ return onPath(["opencode"]);
3535
+ case "copilot": {
3536
+ const appData = process.env.APPDATA;
3537
+ return [
3538
+ join17(home, ".config", "github-copilot"),
3539
+ join17(home, ".copilot"),
3540
+ lad && join17(lad, "github-copilot"),
3541
+ appData && join17(appData, "GitHub Copilot"),
3542
+ join17(home, ".local", "share", "gh", "extensions", "gh-copilot")
3543
+ ].some((p) => !!p && existsSync(p));
3544
+ }
3545
+ case "antigravity":
3546
+ return onPath(["antigravity"]) || anyExists([
3547
+ "/Applications/Antigravity.app",
3548
+ join17(home, "Applications", "Antigravity.app"),
3549
+ lad && join17(lad, "Programs", "Antigravity", "Antigravity.exe")
3550
+ ]);
3551
+ case "gemini":
3552
+ return onPath(["gemini"]);
3553
+ default:
3554
+ return false;
3555
+ }
3556
+ }
3557
+
3326
3558
  // src/providers/index.ts
3327
3559
  var PROVIDER_ORDER = [...PROVIDER_IDS];
3328
3560
  var PROVIDERS = {
@@ -3350,11 +3582,32 @@ async function detectProviders() {
3350
3582
  }
3351
3583
 
3352
3584
  // src/accounts.ts
3353
- import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
3354
- import { homedir as homedir14 } from "os";
3355
- import { basename, join as join17, resolve } from "path";
3585
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync } from "fs";
3586
+ import { homedir as homedir15 } from "os";
3587
+ import { basename, join as join19, resolve } from "path";
3588
+
3589
+ // src/providers/codex/identity.ts
3590
+ import { readFileSync as readFileSync2 } from "fs";
3591
+ import { join as join18 } from "path";
3592
+ function codexAuthPaths(homeDir) {
3593
+ return [join18(homeDir, ".codex", "auth.json"), join18(homeDir, "auth.json")];
3594
+ }
3595
+ function readCodexIdentity(homeDir) {
3596
+ for (const path of codexAuthPaths(homeDir)) {
3597
+ try {
3598
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
3599
+ const { email, displayName, payload } = identityFromIdToken(parsed?.tokens?.id_token);
3600
+ if (!payload) continue;
3601
+ return { email, displayName };
3602
+ } catch {
3603
+ }
3604
+ }
3605
+ return {};
3606
+ }
3607
+
3608
+ // src/accounts.ts
3356
3609
  function accountKey(providerId, homeDir) {
3357
- return `${providerId}:${homeDir ? resolve(expandHome(homeDir)) : homedir14()}`;
3610
+ return `${providerId}:${homeDir ? resolve(expandHome(homeDir)) : homedir15()}`;
3358
3611
  }
3359
3612
  function uniqueId(base, used) {
3360
3613
  let id = slugify(base) || "account";
@@ -3373,23 +3626,11 @@ function uniqueId(base, used) {
3373
3626
  used.add(id);
3374
3627
  return id;
3375
3628
  }
3376
- function readClaudeIdentity2(homeDir) {
3377
- try {
3378
- const parsed = JSON.parse(readFileSync(join17(homeDir, ".claude.json"), "utf-8"));
3379
- const oauth = parsed?.oauthAccount;
3380
- return {
3381
- email: typeof oauth?.emailAddress === "string" && oauth.emailAddress.trim() ? oauth.emailAddress.trim() : void 0,
3382
- displayName: typeof oauth?.displayName === "string" && oauth.displayName.trim() ? oauth.displayName.trim() : void 0
3383
- };
3384
- } catch {
3385
- return {};
3386
- }
3387
- }
3388
3629
  function hasClaudeState(homeDir) {
3389
- return existsSync2(join17(homeDir, ".claude.json")) || existsSync2(join17(homeDir, ".claude", ".credentials.json")) || existsSync2(join17(homeDir, ".claude", "projects")) || existsSync2(join17(homeDir, ".config", "claude", ".credentials.json")) || existsSync2(join17(homeDir, ".config", "claude", "projects"));
3630
+ return existsSync2(join19(homeDir, ".claude.json")) || existsSync2(join19(homeDir, ".claude", ".credentials.json")) || existsSync2(join19(homeDir, ".claude", "projects")) || existsSync2(join19(homeDir, ".config", "claude", ".credentials.json")) || existsSync2(join19(homeDir, ".config", "claude", "projects"));
3390
3631
  }
3391
3632
  function candidateAlternateHomes(prefix) {
3392
- const home = homedir14();
3633
+ const home = homedir15();
3393
3634
  let entries;
3394
3635
  try {
3395
3636
  entries = readdirSync(home);
@@ -3400,7 +3641,7 @@ function candidateAlternateHomes(prefix) {
3400
3641
  const pattern = new RegExp(`^\\.${prefix}[_-]`);
3401
3642
  for (const name of entries) {
3402
3643
  if (!pattern.test(name)) continue;
3403
- const path = join17(home, name);
3644
+ const path = join19(home, name);
3404
3645
  try {
3405
3646
  if (!statSync(path).isDirectory()) continue;
3406
3647
  out.push(path);
@@ -3410,45 +3651,16 @@ function candidateAlternateHomes(prefix) {
3410
3651
  return out.sort();
3411
3652
  }
3412
3653
  function labelForClaudeHome(homeDir) {
3413
- const identity = readClaudeIdentity2(homeDir);
3654
+ const identity = readClaudeIdentity(homeDir);
3414
3655
  if (identity.email) return `Claude ${identity.email}`;
3415
3656
  if (identity.displayName) return `Claude ${identity.displayName}`;
3416
3657
  const raw = basename(homeDir).replace(/^\.claude[_-]?/, "").replace(/[_-]+/g, " ").trim();
3417
3658
  return raw ? `Claude ${raw}` : "Claude";
3418
3659
  }
3419
- function decodeBase64UrlJson4(segment) {
3420
- try {
3421
- const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
3422
- const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
3423
- return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
3424
- } catch {
3425
- return null;
3426
- }
3427
- }
3428
- function codexAuthPaths(homeDir) {
3429
- return [join17(homeDir, ".codex", "auth.json"), join17(homeDir, "auth.json")];
3430
- }
3431
- function readCodexIdentity(homeDir) {
3432
- for (const path of codexAuthPaths(homeDir)) {
3433
- try {
3434
- const parsed = JSON.parse(readFileSync(path, "utf-8"));
3435
- const idToken = parsed?.tokens?.id_token;
3436
- if (typeof idToken !== "string" || !idToken.includes(".")) continue;
3437
- const payload = decodeBase64UrlJson4(idToken.split(".")[1]);
3438
- if (!payload || typeof payload !== "object") continue;
3439
- return {
3440
- email: typeof payload?.email === "string" && payload.email.trim() ? payload.email.trim() : void 0,
3441
- 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
3442
- };
3443
- } catch {
3444
- }
3445
- }
3446
- return {};
3447
- }
3448
3660
  function hasCodexAuth(homeDir) {
3449
3661
  for (const path of codexAuthPaths(homeDir)) {
3450
3662
  try {
3451
- const parsed = JSON.parse(readFileSync(path, "utf-8"));
3663
+ const parsed = JSON.parse(readFileSync3(path, "utf-8"));
3452
3664
  const accessToken = parsed?.tokens?.access_token;
3453
3665
  if (typeof accessToken === "string" && accessToken.trim()) return true;
3454
3666
  } catch {
@@ -3542,6 +3754,83 @@ function accountsByProvider(accounts) {
3542
3754
  return groups;
3543
3755
  }
3544
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
+
3545
3834
  // src/peak.ts
3546
3835
  async function fetchPeak() {
3547
3836
  try {
@@ -3675,6 +3964,9 @@ export {
3675
3964
  detectProviders,
3676
3965
  buildAccounts,
3677
3966
  accountsByProvider,
3967
+ namedHex,
3968
+ colorHex,
3969
+ modelColor,
3678
3970
  fetchPeak,
3679
3971
  TOKMON_WS_PATH,
3680
3972
  TOKMON_WS_METHODS,