tokmon 0.20.6 → 0.21.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,7 +6,7 @@ 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
12
  import { readFile, writeFile, rename, mkdir } from "fs/promises";
@@ -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,7 +149,7 @@ 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;
152
+ var CACHE_VERSION = 7;
145
153
  var STABLE_AGE_MS = 5 * 6e4;
146
154
  var PRUNE_AGE_MS = 200 * DAY_MS;
147
155
  var memCache = /* @__PURE__ */ new Map();
@@ -252,6 +260,15 @@ async function loadCachedEntries(files, parse, since) {
252
260
  if (dirty) scheduleFlush();
253
261
  return dedupe(chunks.flat().filter((e) => e.ts >= since));
254
262
  }
263
+ function lastDayKeys(now, tz, n) {
264
+ const keys = [];
265
+ let cursor = now;
266
+ for (let i = 0; i < n; i++) {
267
+ keys.unshift(dayKey(cursor, tz));
268
+ cursor = startOfDay(cursor, tz) - DAY_MS / 2;
269
+ }
270
+ return keys;
271
+ }
255
272
  function dashboardSince(tz) {
256
273
  const now = Date.now();
257
274
  return Math.min(startOfMonth(now, tz), startOfWeek(now, tz), now - SPARK_DAYS * DAY_MS);
@@ -277,7 +294,7 @@ function dedupe(entries) {
277
294
  for (const raw of entries) {
278
295
  const e = cleanEntry(raw);
279
296
  if (e.ts <= 0) continue;
280
- const k = e.id ?? `${e.ts} ${e.model} ${e.input} ${e.output} ${e.cacheCreate} ${e.cacheRead}`;
297
+ const k = e.id ?? `${e.ts} ${e.model} ${e.input} ${e.output} ${e.cacheCreate} ${e.cacheRead} ${e.cost}`;
281
298
  if (seen.has(k)) continue;
282
299
  seen.add(k);
283
300
  out.push(e);
@@ -316,8 +333,7 @@ function summarize(entries, tz) {
316
333
  const hrs = Math.max((now - oldestToday) / 36e5, 1 / 60);
317
334
  const rawBurnRate = hadToday ? today.cost / hrs : 0;
318
335
  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);
336
+ const series = lastDayKeys(now, tz, SPARK_DAYS).map((k) => byDay.get(k) ?? 0);
321
337
  return { today, week, month, burnRate, series };
322
338
  }
323
339
  function groupBy(entries, keyFn) {
@@ -515,6 +531,33 @@ async function readJson(res) {
515
531
  }
516
532
  }
517
533
 
534
+ // src/providers/_shared/identity.ts
535
+ function identityFields(identity) {
536
+ return {
537
+ email: identity?.email ?? null,
538
+ displayName: identity?.displayName ?? null
539
+ };
540
+ }
541
+
542
+ // src/providers/_shared/jwt.ts
543
+ function decodeBase64UrlJson(segment) {
544
+ try {
545
+ const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
546
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
547
+ return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
548
+ } catch {
549
+ return null;
550
+ }
551
+ }
552
+ function identityFromIdToken(idToken) {
553
+ if (typeof idToken !== "string" || !idToken.includes(".")) return {};
554
+ const payload = decodeBase64UrlJson(idToken.split(".")[1]);
555
+ if (!payload || typeof payload !== "object") return {};
556
+ const email = typeof payload.email === "string" && payload.email.trim() ? payload.email.trim() : void 0;
557
+ 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;
558
+ return { email, displayName, payload };
559
+ }
560
+
518
561
  // src/providers/_shared/time.ts
519
562
  function msToIso(ms) {
520
563
  return Number.isFinite(ms) && Math.abs(ms) <= 864e13 ? new Date(ms).toISOString() : null;
@@ -636,20 +679,17 @@ function sqliteStatusMessage(status) {
636
679
 
637
680
  // src/providers/cursor/activity.ts
638
681
  var DAY_MS2 = 864e5;
682
+ var HOUR_MS = 36e5;
639
683
  function trackingDb(homeDir) {
640
684
  return join3(homeDir ?? homedir(), ".cursor", "ai-tracking", "ai-code-tracking.db");
641
685
  }
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) {
686
+ async function cursorActivity(tz, homeDir) {
647
687
  const db = trackingDb(homeDir);
648
688
  try {
649
689
  const now = Date.now();
650
690
  const res = await runSqlite(
651
691
  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;`
692
+ `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
693
  );
654
694
  if (res.status !== "ok") return null;
655
695
  const byDay = /* @__PURE__ */ new Map();
@@ -657,11 +697,13 @@ async function cursorActivity(homeDir) {
657
697
  for (const row of res.rows) {
658
698
  const raw = Number(row.c);
659
699
  const n = Number.isFinite(raw) && raw > 0 ? raw : 0;
660
- byDay.set(String(row.d), n);
700
+ const hour = Number(row.h);
701
+ if (!Number.isFinite(hour)) continue;
702
+ const key = dayKey(hour * HOUR_MS + HOUR_MS / 2, tz);
703
+ byDay.set(key, (byDay.get(key) ?? 0) + n);
661
704
  month += n;
662
705
  }
663
- const series = [];
664
- for (let i = SPARK_DAYS - 1; i >= 0; i--) series.push(byDay.get(localDayKey(now - i * DAY_MS2)) ?? 0);
706
+ const series = lastDayKeys(now, tz, SPARK_DAYS).map((k) => byDay.get(k) ?? 0);
665
707
  if (month === 0 && series.every((v) => v === 0)) return null;
666
708
  return { series, summary: `${tokens(month)} lines` };
667
709
  } catch {
@@ -711,23 +753,6 @@ function cleanStoredString(value) {
711
753
  const trimmed = value.trim().replace(/^"|"$/g, "");
712
754
  return trimmed || void 0;
713
755
  }
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
756
  function cursorSessionToken(accessToken) {
732
757
  const payload = accessToken.includes(".") ? decodeBase64UrlJson(accessToken.split(".")[1]) : null;
733
758
  const subject = typeof payload?.sub === "string" ? payload.sub : null;
@@ -736,12 +761,6 @@ function cursorSessionToken(accessToken) {
736
761
  const userId = (parts.length > 1 ? parts[1] : parts[0]).trim();
737
762
  return userId ? `${userId}%3A%3A${accessToken}` : null;
738
763
  }
739
- function identityFields(email, displayName) {
740
- return {
741
- email: email ?? null,
742
- displayName: displayName ?? null
743
- };
744
- }
745
764
  async function connectPost(url, token) {
746
765
  try {
747
766
  const res = await fetch(url, {
@@ -798,10 +817,10 @@ function appendCredits(metrics, creditGrants, stripe) {
798
817
  if (total <= 0) return;
799
818
  metrics.push({ label: "Credits", used: dollars(Math.max(0, total - (hasValidGrants ? grantUsed : 0))), limit: null, format: { kind: "dollars" } });
800
819
  }
801
- async function cursorBilling(account) {
820
+ async function cursorBilling(account, tz) {
802
821
  const [core, activity, spend] = await Promise.all([
803
822
  cursorBillingCore(account),
804
- cursorActivity(account.homeDir),
823
+ cursorActivity(tz, account.homeDir),
805
824
  cursorModelSpend(account.homeDir)
806
825
  ]);
807
826
  let merged = activity;
@@ -831,7 +850,7 @@ async function cursorBillingCore(account) {
831
850
  const planFallback = membership ? membership.charAt(0).toUpperCase() + membership.slice(1) : null;
832
851
  if (!token) {
833
852
  const error = tokenRes.status === "ok" ? "Not signed in \u2014 open Cursor" : sqliteStatusMessage(tokenRes.status);
834
- return { plan: planFallback, metrics: [], error, ...identityFields(email, displayName) };
853
+ return { plan: planFallback, metrics: [], error, ...identityFields({ email, displayName }) };
835
854
  }
836
855
  const [usage, planInfo, creditGrants, stripe] = await Promise.all([
837
856
  connectPost(USAGE_URL, token),
@@ -841,7 +860,7 @@ async function cursorBillingCore(account) {
841
860
  ]);
842
861
  if (!usage || usage.__status) {
843
862
  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) };
863
+ return { plan: planFallback, metrics: [], error: expired ? "Token expired \u2014 re-open Cursor" : "Cursor API error", ...identityFields({ email, displayName }) };
845
864
  }
846
865
  const planName = planInfo?.planInfo?.planName ?? planFallback;
847
866
  const price = planInfo?.planInfo?.price;
@@ -852,7 +871,6 @@ async function cursorBillingCore(account) {
852
871
  const endMs = numberValue(rawEnd) ?? NaN;
853
872
  const iso = msToIso(endMs);
854
873
  const resets = iso && endMs > 0 ? resetIn(iso) : null;
855
- appendCredits(metrics, creditGrants, stripe);
856
874
  const limit = numberValue(pu.limit);
857
875
  const planUsedCents = numberValue(pu.totalSpend) ?? (limit !== void 0 && numberValue(pu.remaining) !== void 0 ? limit - numberValue(pu.remaining) : void 0);
858
876
  const computedPercent = limit !== void 0 && limit > 0 && planUsedCents !== void 0 ? planUsedCents / limit * 100 : void 0;
@@ -908,10 +926,11 @@ async function cursorBillingCore(account) {
908
926
  });
909
927
  }
910
928
  }
929
+ appendCredits(metrics, creditGrants, stripe);
911
930
  if (metrics.length === 0) {
912
- return { plan, metrics: [], error: usage.enabled === false ? "No active subscription" : "No usage data", ...identityFields(email, displayName) };
931
+ return { plan, metrics: [], error: usage.enabled === false ? "No active subscription" : "No usage data", ...identityFields({ email, displayName }) };
913
932
  }
914
- return { plan, metrics, error: null, ...identityFields(email, displayName) };
933
+ return { plan, metrics, error: null, ...identityFields({ email, displayName }) };
915
934
  }
916
935
 
917
936
  // src/providers/cursor/composer.ts
@@ -985,6 +1004,7 @@ var PRICING = {
985
1004
  "claude-opus-4": { i: 5e-6, o: 25e-6, cc: 625e-8, cr: 5e-7 },
986
1005
  "claude-3-opus": { i: 15e-6, o: 75e-6, cc: 1875e-8, cr: 15e-7 },
987
1006
  "claude-sonnet-4": { i: 3e-6, o: 15e-6, cc: 375e-8, cr: 3e-7 },
1007
+ "claude-sonnet-5": { i: 3e-6, o: 15e-6, cc: 375e-8, cr: 3e-7 },
988
1008
  "claude-haiku-4": { i: 1e-6, o: 5e-6, cc: 125e-8, cr: 1e-7 },
989
1009
  "claude-fable-5": { i: 1e-5, o: 5e-5, cc: 125e-7, cr: 1e-6 }
990
1010
  };
@@ -1129,8 +1149,8 @@ async function claudeTable(tz, homeDir) {
1129
1149
 
1130
1150
  // src/providers/claude/billing.ts
1131
1151
  import { readFile as readFile2 } from "fs/promises";
1132
- import { join as join6 } from "path";
1133
- import { homedir as homedir4 } from "os";
1152
+ import { join as join7 } from "path";
1153
+ import { homedir as homedir5 } from "os";
1134
1154
 
1135
1155
  // src/providers/_shared/keychain.ts
1136
1156
  import { execFile as execFileCb2 } from "child_process";
@@ -1157,7 +1177,10 @@ function unwrapGoKeyringBase64(raw) {
1157
1177
  return Buffer.from(raw.slice(GO_KEYRING_PREFIX.length), "base64").toString("utf-8");
1158
1178
  }
1159
1179
 
1160
- // src/providers/claude/billing.ts
1180
+ // src/providers/claude/identity.ts
1181
+ import { readFileSync } from "fs";
1182
+ import { join as join6 } from "path";
1183
+ import { homedir as homedir4 } from "os";
1161
1184
  function titleWords(value) {
1162
1185
  return value.split(/[_\s-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join(" ");
1163
1186
  }
@@ -1168,25 +1191,22 @@ function claudeOrgPlanLabel(orgType) {
1168
1191
  const label = titleWords(stripped);
1169
1192
  return label ? `Claude ${label}` : null;
1170
1193
  }
1171
- async function readClaudeIdentity(homeDir) {
1194
+ function readClaudeIdentity(homeDir) {
1172
1195
  const base = homeDir ? expandHome(homeDir) : homedir4();
1173
1196
  try {
1174
- const parsed = JSON.parse(await readFile2(join6(base, ".claude.json"), "utf-8"));
1197
+ const parsed = JSON.parse(readFileSync(join6(base, ".claude.json"), "utf-8"));
1175
1198
  const oauth = parsed?.oauthAccount;
1176
1199
  const email = typeof oauth?.emailAddress === "string" && oauth.emailAddress.trim() ? oauth.emailAddress.trim() : void 0;
1177
1200
  const displayName = typeof oauth?.displayName === "string" && oauth.displayName.trim() ? oauth.displayName.trim() : void 0;
1178
1201
  const plan = claudeOrgPlanLabel(parsed?.organizationType);
1179
- return { email, displayName, plan: plan ?? void 0 };
1202
+ const accountUuid = typeof oauth?.accountUuid === "string" && oauth.accountUuid.trim() ? oauth.accountUuid.trim() : void 0;
1203
+ return { email, displayName, plan: plan ?? void 0, accountUuid };
1180
1204
  } catch {
1181
1205
  return {};
1182
1206
  }
1183
1207
  }
1184
- function identityFields2(identity) {
1185
- return {
1186
- email: identity.email ?? null,
1187
- displayName: identity.displayName ?? null
1188
- };
1189
- }
1208
+
1209
+ // src/providers/claude/billing.ts
1190
1210
  function parseAuth(raw) {
1191
1211
  try {
1192
1212
  const creds = JSON.parse(raw);
@@ -1196,7 +1216,8 @@ function parseAuth(raw) {
1196
1216
  return {
1197
1217
  token,
1198
1218
  subscriptionType: typeof o.subscriptionType === "string" ? o.subscriptionType : void 0,
1199
- rateLimitTier: typeof o.rateLimitTier === "string" ? o.rateLimitTier : void 0
1219
+ rateLimitTier: typeof o.rateLimitTier === "string" ? o.rateLimitTier : void 0,
1220
+ expiresAt: typeof o.expiresAt === "number" && Number.isFinite(o.expiresAt) ? o.expiresAt : void 0
1200
1221
  };
1201
1222
  } catch {
1202
1223
  return null;
@@ -1205,7 +1226,7 @@ function parseAuth(raw) {
1205
1226
  async function readCredentialsFile(homeDir) {
1206
1227
  for (const dir of claudeConfigDirs(homeDir)) {
1207
1228
  try {
1208
- const auth = parseAuth(await readFile2(join6(dir, ".credentials.json"), "utf-8"));
1229
+ const auth = parseAuth(await readFile2(join7(dir, ".credentials.json"), "utf-8"));
1209
1230
  if (auth) return auth;
1210
1231
  } catch {
1211
1232
  }
@@ -1216,20 +1237,65 @@ async function readMacKeychain() {
1216
1237
  const raw = await readMacKeychainRaw("Claude Code-credentials");
1217
1238
  return raw ? parseAuth(raw) : null;
1218
1239
  }
1219
- async function getAuth(homeDir) {
1240
+ async function authCandidates(homeDir) {
1220
1241
  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;
1242
+ const isDefault = !expandedHomeDir || expandedHomeDir === homedir5();
1243
+ const out = [];
1244
+ const file = await readCredentialsFile(isDefault ? void 0 : expandedHomeDir);
1245
+ const keychain = process.platform === "darwin" ? await readMacKeychain() : null;
1246
+ const ordered = isDefault ? [keychain && { auth: keychain, shared: true }, file && { auth: file, shared: false }] : [file && { auth: file, shared: false }, keychain && { auth: keychain, shared: true }];
1247
+ for (const c of ordered) if (c) out.push(c);
1248
+ return out;
1249
+ }
1250
+ var tokenIdentityCache = /* @__PURE__ */ new Map();
1251
+ async function tokenIdentity(token) {
1252
+ if (tokenIdentityCache.has(token)) return tokenIdentityCache.get(token);
1253
+ try {
1254
+ const res = await fetch("https://api.anthropic.com/api/oauth/profile", {
1255
+ headers: {
1256
+ "Authorization": `Bearer ${token}`,
1257
+ "anthropic-beta": "oauth-2025-04-20",
1258
+ "User-Agent": "tokmon"
1259
+ },
1260
+ signal: AbortSignal.timeout(1e4)
1261
+ });
1262
+ if (res.status === 401 || res.status === 403) {
1263
+ tokenIdentityCache.set(token, null);
1264
+ return null;
1226
1265
  }
1227
- return readCredentialsFile(void 0);
1266
+ if (!res.ok) return void 0;
1267
+ const data = await readJson(res);
1268
+ const uuid = data?.account?.uuid;
1269
+ if (typeof uuid !== "string" || !uuid) return void 0;
1270
+ const identity = {
1271
+ accountUuid: uuid,
1272
+ email: typeof data?.account?.email === "string" ? data.account.email : null
1273
+ };
1274
+ if (tokenIdentityCache.size > 64) tokenIdentityCache.clear();
1275
+ tokenIdentityCache.set(token, identity);
1276
+ return identity;
1277
+ } catch {
1278
+ return void 0;
1228
1279
  }
1229
- const fileAuth = await readCredentialsFile(expandedHomeDir);
1230
- if (fileAuth) return fileAuth;
1231
- if (process.platform === "darwin") return readMacKeychain();
1232
- return null;
1280
+ }
1281
+ async function getAuth(homeDir, expectedUuid) {
1282
+ const candidates = await authCandidates(homeDir);
1283
+ let wrongAccountEmail;
1284
+ let sawExpired = false;
1285
+ for (const { auth, shared } of candidates) {
1286
+ if (auth.expiresAt !== void 0 && auth.expiresAt < Date.now() - 6e4) {
1287
+ sawExpired = true;
1288
+ continue;
1289
+ }
1290
+ if (!shared || !expectedUuid) return { auth };
1291
+ const identity = await tokenIdentity(auth.token);
1292
+ if (identity === void 0) return { auth };
1293
+ if (identity === null) continue;
1294
+ if (identity.accountUuid === expectedUuid) return { auth };
1295
+ wrongAccountEmail = identity.email;
1296
+ }
1297
+ if (wrongAccountEmail !== void 0) return { auth: null, wrongAccountEmail };
1298
+ return { auth: null, expired: sawExpired };
1233
1299
  }
1234
1300
  function planLabel(auth) {
1235
1301
  const sub = auth.subscriptionType;
@@ -1239,14 +1305,6 @@ function planLabel(auth) {
1239
1305
  return tier ? `${base} ${tier[1]}x` : base;
1240
1306
  }
1241
1307
  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
1308
  function boolValue(value) {
1251
1309
  if (typeof value === "boolean") return value;
1252
1310
  if (typeof value === "number") return value !== 0;
@@ -1255,22 +1313,23 @@ function boolValue(value) {
1255
1313
  }
1256
1314
  function resetFrom(value) {
1257
1315
  if (typeof value === "string" && value.trim()) return resetIn(value);
1258
- const n = numberValue2(value);
1316
+ const n = numberValue(value);
1259
1317
  if (n === void 0) return null;
1260
1318
  const ms = Math.abs(n) < 1e10 ? n * 1e3 : n;
1261
1319
  return resetIn(new Date(ms).toISOString());
1262
1320
  }
1263
1321
  function usageMetric(label, window, primary) {
1264
- const used = numberValue2(window?.utilization);
1322
+ const used = numberValue(window?.utilization);
1265
1323
  if (used === void 0) return null;
1266
1324
  return { ...pct(used, resetFrom(window?.resets_at), primary), label };
1267
1325
  }
1268
1326
  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) };
1327
+ const identity = readClaudeIdentity(account.homeDir);
1328
+ const { auth, wrongAccountEmail, expired } = await getAuth(account.homeDir, identity.accountUuid);
1329
+ if (!auth) {
1330
+ 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";
1331
+ return { plan: identity.plan ?? null, metrics: [], error, ...identityFields(identity) };
1332
+ }
1274
1333
  const plan = identity.plan ?? planLabel(auth);
1275
1334
  try {
1276
1335
  const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
@@ -1282,14 +1341,14 @@ async function claudeBilling(account) {
1282
1341
  signal: AbortSignal.timeout(1e4)
1283
1342
  });
1284
1343
  if (res.status === 429) {
1285
- const retryAfter = numberValue2(res.headers.get("retry-after"));
1344
+ const retryAfter = numberValue(res.headers.get("retry-after"));
1286
1345
  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) };
1346
+ return { plan, metrics: [], error: `Rate limited${retryText}`, ...identityFields(identity) };
1288
1347
  }
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) };
1348
+ if (res.status === 401) return { plan, metrics: [], error: "Token expired \u2014 run claude to refresh", ...identityFields(identity) };
1349
+ if (!res.ok) return { plan, metrics: [], error: `API ${res.status}`, ...identityFields(identity) };
1291
1350
  const data = await readJson(res);
1292
- if (!data) return { plan, metrics: [], error: "Unexpected API response", ...identityFields2(identity) };
1351
+ if (!data) return { plan, metrics: [], error: "Unexpected API response", ...identityFields(identity) };
1293
1352
  const metrics = [];
1294
1353
  const fiveHour = usageMetric("Session", data.five_hour, true);
1295
1354
  if (fiveHour) metrics.push(fiveHour);
@@ -1298,8 +1357,8 @@ async function claudeBilling(account) {
1298
1357
  const sevenDaySonnet = usageMetric("Sonnet", data.seven_day_sonnet);
1299
1358
  if (sevenDaySonnet) metrics.push(sevenDaySonnet);
1300
1359
  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);
1360
+ const usedCredits = numberValue(data.extra_usage?.used_credits);
1361
+ const monthlyLimit = numberValue(data.extra_usage?.monthly_limit);
1303
1362
  if (usedCredits !== void 0 && (usedCredits > 0 || monthlyLimit !== void 0 && monthlyLimit > 0)) {
1304
1363
  metrics.push({
1305
1364
  label: "Extra",
@@ -1309,9 +1368,9 @@ async function claudeBilling(account) {
1309
1368
  });
1310
1369
  }
1311
1370
  }
1312
- return { plan, metrics, error: null, ...identityFields2(identity) };
1371
+ return { plan, metrics, error: null, ...identityFields(identity) };
1313
1372
  } catch {
1314
- return { plan, metrics: [], error: "Network error", ...identityFields2(identity) };
1373
+ return { plan, metrics: [], error: "Network error", ...identityFields(identity) };
1315
1374
  }
1316
1375
  }
1317
1376
 
@@ -1332,8 +1391,8 @@ var claudeProvider = {
1332
1391
  import { readdir as readdir2, stat as fsStat2, access as access3, open as openFile } from "fs/promises";
1333
1392
  import { createReadStream as createReadStream2 } from "fs";
1334
1393
  import { createInterface as createInterface2 } from "readline";
1335
- import { join as join7 } from "path";
1336
- import { homedir as homedir5 } from "os";
1394
+ import { join as join8 } from "path";
1395
+ import { homedir as homedir6 } from "os";
1337
1396
  var PRICING2 = {
1338
1397
  "gpt-5.5-codex": { in: 5e-6, cr: 5e-7, out: 3e-5 },
1339
1398
  "gpt-5.5": { in: 5e-6, cr: 5e-7, out: 3e-5 },
@@ -1345,26 +1404,26 @@ var PRICING2 = {
1345
1404
  "gpt-5": { in: 125e-8, cr: 125e-9, out: 1e-5 },
1346
1405
  "o4-mini": { in: 11e-7, cr: 275e-9, out: 44e-7 }
1347
1406
  };
1348
- var ZERO_PRICE2 = { in: 0, cr: 0, out: 0 };
1407
+ var FALLBACK_PRICE = PRICING2["gpt-5.5"];
1349
1408
  var PRICE_KEYS2 = Object.keys(PRICING2).sort((a, b) => b.length - a.length);
1350
1409
  function codexHomes(homeDir) {
1351
- if (homeDir) return [.../* @__PURE__ */ new Set([join7(homeDir, ".codex"), homeDir])];
1410
+ if (homeDir) return [.../* @__PURE__ */ new Set([join8(homeDir, ".codex"), homeDir])];
1352
1411
  const homes = [];
1353
1412
  const codexHome = envDir("CODEX_HOME");
1354
1413
  if (codexHome) homes.push(codexHome);
1355
- homes.push(join7(homedir5(), ".codex"));
1356
- homes.push(join7(homedir5(), ".config", "codex"));
1414
+ homes.push(join8(homedir6(), ".codex"));
1415
+ homes.push(join8(homedir6(), ".config", "codex"));
1357
1416
  return [...new Set(homes)];
1358
1417
  }
1359
1418
  async function detectCodex(homeDir) {
1360
1419
  for (const home of codexHomes(homeDir)) {
1361
1420
  try {
1362
- await access3(join7(home, "sessions"));
1421
+ await access3(join8(home, "sessions"));
1363
1422
  return true;
1364
1423
  } catch {
1365
1424
  }
1366
1425
  try {
1367
- await access3(join7(home, "archived_sessions"));
1426
+ await access3(join8(home, "archived_sessions"));
1368
1427
  return true;
1369
1428
  } catch {
1370
1429
  }
@@ -1376,7 +1435,8 @@ function modelKeyMatches(model, key) {
1376
1435
  while (idx >= 0) {
1377
1436
  const before = idx === 0 ? "" : model[idx - 1];
1378
1437
  const rest = model.slice(idx + key.length);
1379
- if ((!before || !/[a-z0-9-]/.test(before)) && (rest === "" || rest[0] === "-" || !/[a-z0-9]/.test(rest[0]))) {
1438
+ const versionContinues = rest[0] === "." && /\d/.test(rest[1] ?? "");
1439
+ if ((!before || !/[a-z0-9-]/.test(before)) && !versionContinues && (rest === "" || rest[0] === "-" || !/[a-z0-9]/.test(rest[0]))) {
1380
1440
  return true;
1381
1441
  }
1382
1442
  idx = model.indexOf(key, idx + key.length);
@@ -1388,7 +1448,7 @@ function priceFor2(model) {
1388
1448
  for (const key of PRICE_KEYS2) {
1389
1449
  if (modelKeyMatches(m, key)) return PRICING2[key];
1390
1450
  }
1391
- return ZERO_PRICE2;
1451
+ return FALLBACK_PRICE;
1392
1452
  }
1393
1453
  function extractModel(obj) {
1394
1454
  const p = obj?.payload ?? obj;
@@ -1597,7 +1657,7 @@ async function loadEntries2(since, homeDir) {
1597
1657
  const seen = /* @__PURE__ */ new Set();
1598
1658
  const seenIno = /* @__PURE__ */ new Set();
1599
1659
  for (const home of codexHomes(homeDir)) {
1600
- for (const dir of [join7(home, "sessions"), join7(home, "archived_sessions")]) {
1660
+ for (const dir of [join8(home, "sessions"), join8(home, "archived_sessions")]) {
1601
1661
  let listing;
1602
1662
  try {
1603
1663
  listing = await readdir2(dir, { recursive: true });
@@ -1606,7 +1666,7 @@ async function loadEntries2(since, homeDir) {
1606
1666
  }
1607
1667
  for (const f of listing) {
1608
1668
  if (!f.endsWith(".jsonl")) continue;
1609
- const path = join7(dir, f);
1669
+ const path = join8(dir, f);
1610
1670
  if (seen.has(path)) continue;
1611
1671
  seen.add(path);
1612
1672
  try {
@@ -1637,19 +1697,10 @@ async function codexTable(tz, homeDir) {
1637
1697
  import { readFile as readFile3, readdir as readdir3, stat as fsStat3 } from "fs/promises";
1638
1698
  import { createReadStream as createReadStream3 } from "fs";
1639
1699
  import { createInterface as createInterface3 } from "readline";
1640
- import { join as join8 } from "path";
1700
+ import { join as join9 } from "path";
1641
1701
  var USAGE_URL2 = "https://chatgpt.com/backend-api/wham/usage";
1642
1702
  var RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
1643
1703
  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
1704
  function chatGptPlanLabel(planType) {
1654
1705
  if (typeof planType !== "string" || !planType.trim()) return null;
1655
1706
  const p = planType.trim().toLowerCase();
@@ -1664,28 +1715,19 @@ function chatGptPlanLabel(planType) {
1664
1715
  if (labels[p]) return labels[p];
1665
1716
  return `ChatGPT ${p.charAt(0).toUpperCase()}${p.slice(1)}`;
1666
1717
  }
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;
1718
+ function codexIdentity(idToken) {
1719
+ const { email, displayName, payload } = identityFromIdToken(idToken);
1720
+ if (!payload) return {};
1673
1721
  const plan = chatGptPlanLabel(payload["https://api.openai.com/auth"]?.chatgpt_plan_type);
1674
1722
  return { email, displayName, plan: plan ?? void 0 };
1675
1723
  }
1676
- function identityFields3(auth) {
1677
- return {
1678
- email: auth?.email ?? null,
1679
- displayName: auth?.displayName ?? null
1680
- };
1681
- }
1682
1724
  async function readAuthFile(home) {
1683
1725
  try {
1684
- const raw = await readFile3(join8(home, "auth.json"), "utf-8");
1726
+ const raw = await readFile3(join9(home, "auth.json"), "utf-8");
1685
1727
  const auth = JSON.parse(raw);
1686
1728
  const accessToken = auth?.tokens?.access_token;
1687
1729
  if (!accessToken) return null;
1688
- return { accessToken, accountId: auth?.tokens?.account_id, ...identityFromIdToken(auth?.tokens?.id_token) };
1730
+ return { accessToken, accountId: auth?.tokens?.account_id, ...codexIdentity(auth?.tokens?.id_token) };
1689
1731
  } catch {
1690
1732
  return null;
1691
1733
  }
@@ -1697,7 +1739,7 @@ async function readKeychainAuth() {
1697
1739
  const auth = JSON.parse(raw);
1698
1740
  const accessToken = auth?.tokens?.access_token;
1699
1741
  if (!accessToken) return null;
1700
- return { accessToken, accountId: auth?.tokens?.account_id, ...identityFromIdToken(auth?.tokens?.id_token) };
1742
+ return { accessToken, accountId: auth?.tokens?.account_id, ...codexIdentity(auth?.tokens?.id_token) };
1701
1743
  } catch {
1702
1744
  return null;
1703
1745
  }
@@ -1707,7 +1749,7 @@ async function getAuth2(homeDir) {
1707
1749
  const auth = await readAuthFile(home);
1708
1750
  if (auth) return auth;
1709
1751
  }
1710
- if (process.platform === "darwin") return readKeychainAuth();
1752
+ if (!homeDir && process.platform === "darwin") return readKeychainAuth();
1711
1753
  return null;
1712
1754
  }
1713
1755
  function planLabel2(planType) {
@@ -1717,19 +1759,11 @@ function planLabel2(planType) {
1717
1759
  if (p === "pro") return "Pro 20x";
1718
1760
  return planType.charAt(0).toUpperCase() + planType.slice(1);
1719
1761
  }
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
1762
  function resetDateMs(window) {
1729
1763
  if (!window) return null;
1730
- const absolute = numberValue3(window.reset_at ?? window.resets_at);
1764
+ const absolute = numberValue(window.reset_at ?? window.resets_at);
1731
1765
  if (absolute !== void 0) return absolute > 1e10 ? absolute : absolute * 1e3;
1732
- const after = numberValue3(window.reset_after_seconds);
1766
+ const after = numberValue(window.reset_after_seconds);
1733
1767
  if (after !== void 0) return Date.now() + after * 1e3;
1734
1768
  for (const key of ["reset_at", "resets_at"]) {
1735
1769
  const raw = window[key];
@@ -1745,10 +1779,16 @@ function resetFrom2(window) {
1745
1779
  const iso = ms === null ? null : msToIso(ms);
1746
1780
  return iso ? resetIn(iso) : null;
1747
1781
  }
1782
+ function windowSeconds(window) {
1783
+ const seconds = numberValue(window?.limit_window_seconds ?? window?.period_seconds ?? window?.window_seconds);
1784
+ if (seconds !== void 0) return seconds;
1785
+ const minutes = numberValue(window?.limit_window_minutes ?? window?.window_minutes ?? window?.period_minutes);
1786
+ return minutes === void 0 ? void 0 : minutes * 60;
1787
+ }
1748
1788
  function normalizedUsedPercent(window, percent) {
1749
- const used = numberValue3(percent);
1789
+ const used = numberValue(percent);
1750
1790
  if (used === void 0) return void 0;
1751
- const periodSeconds = numberValue3(window?.limit_window_seconds ?? window?.period_seconds ?? window?.window_seconds);
1791
+ const periodSeconds = windowSeconds(window);
1752
1792
  const resetMs = resetDateMs(window);
1753
1793
  if (periodSeconds && resetMs) {
1754
1794
  const elapsedMs = periodSeconds * 1e3 - Math.max(0, resetMs - Date.now());
@@ -1759,7 +1799,7 @@ function normalizedUsedPercent(window, percent) {
1759
1799
  function metricLabelForAdditional(window) {
1760
1800
  const name = String(window?.limit_name ?? window?.name ?? window?.metered_feature ?? window?.feature ?? "").toLowerCase();
1761
1801
  if (!name.includes("spark")) return null;
1762
- const seconds = numberValue3(window?.limit_window_seconds ?? window?.period_seconds ?? window?.window_seconds);
1802
+ const seconds = windowSeconds(window);
1763
1803
  return name.includes("week") || seconds !== void 0 && seconds >= 6 * 24 * 60 * 60 ? "Spark Weekly" : "Spark";
1764
1804
  }
1765
1805
  function additionalRateLimits(rl) {
@@ -1784,9 +1824,9 @@ function appendWindowMetrics(metrics, rl, headerPct) {
1784
1824
  }
1785
1825
  }
1786
1826
  function appendCredits2(metrics, source) {
1787
- const balance = numberValue3(source?.credits?.balance ?? source?.credit_balance);
1827
+ const balance = numberValue(source?.credits?.balance ?? source?.credit_balance);
1788
1828
  if (balance !== void 0 && balance >= 0) {
1789
- metrics.push({ label: "Credits", used: Math.floor(balance) * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
1829
+ metrics.push({ label: "Credits", used: balance * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
1790
1830
  }
1791
1831
  }
1792
1832
  async function fetchResetCredits(headers) {
@@ -1801,7 +1841,7 @@ async function fetchResetCredits(headers) {
1801
1841
  });
1802
1842
  if (!res.ok) return void 0;
1803
1843
  const data = await readJson(res);
1804
- return numberValue3(data?.available_count ?? data?.available ?? data?.remaining);
1844
+ return numberValue(data?.available_count ?? data?.available ?? data?.remaining);
1805
1845
  } catch {
1806
1846
  return void 0;
1807
1847
  }
@@ -1827,20 +1867,22 @@ async function liveBilling(auth) {
1827
1867
  const metrics = [];
1828
1868
  appendWindowMetrics(metrics, data.rate_limit ?? data, headerPct);
1829
1869
  appendCredits2(metrics, data);
1830
- const resetCredits = await fetchResetCredits(headers) ?? numberValue3(data?.rate_limit_reset_credits?.available_count ?? data?.rate_limit_reset_credits?.available);
1870
+ const resetCredits = numberValue(data?.rate_limit_reset_credits?.available_count ?? data?.rate_limit_reset_credits?.available) ?? await fetchResetCredits(headers);
1831
1871
  if (resetCredits !== void 0 && resetCredits >= 0) {
1832
- metrics.push({ label: "Rate Limit Resets", used: resetCredits, limit: null, format: { kind: "count", suffix: "available" } });
1872
+ metrics.push({ label: "Resets", used: resetCredits, limit: null, format: { kind: "count", suffix: "available" } });
1833
1873
  }
1834
1874
  if (metrics.length === 0) return null;
1835
- return { plan: auth.plan ?? planLabel2(data.plan_type), metrics, error: null, ...identityFields3(auth) };
1875
+ return { plan: auth.plan ?? planLabel2(data.plan_type), metrics, error: null, ...identityFields(auth) };
1836
1876
  } catch {
1837
1877
  return null;
1838
1878
  }
1839
1879
  }
1840
- async function newestRolloutFile(homeDir) {
1841
- let best = null;
1880
+ var SNAPSHOT_CANDIDATES = 8;
1881
+ var SNAPSHOT_STALE_MS = 24 * 36e5;
1882
+ async function newestRolloutFiles(homeDir) {
1883
+ const all = [];
1842
1884
  for (const home of codexHomes(homeDir)) {
1843
- const dir = join8(home, "sessions");
1885
+ const dir = join9(home, "sessions");
1844
1886
  let listing;
1845
1887
  try {
1846
1888
  listing = await readdir3(dir, { recursive: true });
@@ -1849,19 +1891,17 @@ async function newestRolloutFile(homeDir) {
1849
1891
  }
1850
1892
  for (const f of listing) {
1851
1893
  if (!f.endsWith(".jsonl") || !f.includes("rollout-")) continue;
1852
- const path = join8(dir, f);
1894
+ const path = join9(dir, f);
1853
1895
  try {
1854
1896
  const s = await fsStat3(path);
1855
- if (!best || s.mtimeMs > best.mtime) best = { path, mtime: s.mtimeMs };
1897
+ all.push({ path, mtime: s.mtimeMs });
1856
1898
  } catch {
1857
1899
  }
1858
1900
  }
1859
1901
  }
1860
- return best?.path ?? null;
1902
+ return all.sort((a, b) => b.mtime - a.mtime).slice(0, SNAPSHOT_CANDIDATES);
1861
1903
  }
1862
- async function snapshotBilling(homeDir, auth = null) {
1863
- const path = await newestRolloutFile(homeDir);
1864
- if (!path) return null;
1904
+ async function lastRateLimits(path) {
1865
1905
  let last = null;
1866
1906
  try {
1867
1907
  const input = createReadStream3(path);
@@ -1879,16 +1919,23 @@ async function snapshotBilling(homeDir, auth = null) {
1879
1919
  } catch {
1880
1920
  return null;
1881
1921
  }
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" } });
1922
+ return last;
1923
+ }
1924
+ async function snapshotBilling(homeDir, auth = null) {
1925
+ for (const file of await newestRolloutFiles(homeDir)) {
1926
+ const last = await lastRateLimits(file.path);
1927
+ if (!last) continue;
1928
+ const metrics = [];
1929
+ appendWindowMetrics(metrics, last.rate_limit ?? last);
1930
+ appendCredits2(metrics, last);
1931
+ const resetCredits = numberValue(last?.rate_limit_reset_credits?.available_count ?? last?.rate_limit_reset_credits?.available);
1932
+ if (resetCredits !== void 0 && resetCredits >= 0) {
1933
+ metrics.push({ label: "Resets", used: resetCredits, limit: null, format: { kind: "count", suffix: "available" } });
1934
+ }
1935
+ if (metrics.length === 0) continue;
1936
+ return { plan: auth?.plan ?? planLabel2(last.plan_type), metrics, error: null, asOfMs: file.mtime, ...identityFields(auth) };
1889
1937
  }
1890
- if (metrics.length === 0) return null;
1891
- return { plan: auth?.plan ?? planLabel2(last.plan_type), metrics, error: null, ...identityFields3(auth) };
1938
+ return null;
1892
1939
  }
1893
1940
  async function codexBilling(account) {
1894
1941
  const auth = await getAuth2(account.homeDir);
@@ -1897,12 +1944,15 @@ async function codexBilling(account) {
1897
1944
  if (live) return live;
1898
1945
  }
1899
1946
  const snap = await snapshotBilling(account.homeDir, auth);
1900
- if (snap) return snap;
1947
+ if (snap && Date.now() - snap.asOfMs < SNAPSHOT_STALE_MS) {
1948
+ const { asOfMs: _asOfMs, ...result } = snap;
1949
+ return result;
1950
+ }
1901
1951
  return {
1902
- plan: auth?.plan ?? null,
1952
+ plan: auth?.plan ?? snap?.plan ?? null,
1903
1953
  metrics: [],
1904
1954
  error: auth ? "Usage API failed \u2014 run codex to refresh" : "Not logged in \u2014 run codex",
1905
- ...identityFields3(auth)
1955
+ ...identityFields(auth)
1906
1956
  };
1907
1957
  }
1908
1958
 
@@ -2079,17 +2129,17 @@ var cursorProvider = {
2079
2129
  hasBilling: true,
2080
2130
  detect: (homeDir) => detectCursor(homeDir),
2081
2131
  fetchTable: (account, tz) => cursorTable(tz, account.homeDir),
2082
- fetchBilling: (account) => cursorBilling(account)
2132
+ fetchBilling: (account, tz) => cursorBilling(account, tz)
2083
2133
  };
2084
2134
 
2085
2135
  // src/providers/pi/usage.ts
2086
2136
  import { readdir as readdir4, stat as fsStat4, access as access4 } from "fs/promises";
2087
2137
  import { createReadStream as createReadStream4 } from "fs";
2088
2138
  import { createInterface as createInterface4 } from "readline";
2089
- import { join as join9 } from "path";
2090
- import { homedir as homedir6 } from "os";
2139
+ import { join as join10 } from "path";
2140
+ import { homedir as homedir7 } from "os";
2091
2141
  function piSessionsDir(homeDir) {
2092
- return join9(homeDir ?? homedir6(), ".pi", "agent", "sessions");
2142
+ return join10(homeDir ?? homedir7(), ".pi", "agent", "sessions");
2093
2143
  }
2094
2144
  async function detectPi(homeDir) {
2095
2145
  try {
@@ -2154,7 +2204,7 @@ async function loadEntries3(since, homeDir) {
2154
2204
  }
2155
2205
  for (const f of listing) {
2156
2206
  if (!f.endsWith(".jsonl")) continue;
2157
- const path = join9(dir, f);
2207
+ const path = join10(dir, f);
2158
2208
  try {
2159
2209
  const s = await fsStat4(path);
2160
2210
  if (s.mtimeMs < since) continue;
@@ -2190,17 +2240,17 @@ var piProvider = {
2190
2240
 
2191
2241
  // src/providers/opencode/usage.ts
2192
2242
  import { access as access5 } from "fs/promises";
2193
- import { join as join10 } from "path";
2194
- import { homedir as homedir7 } from "os";
2243
+ import { join as join11 } from "path";
2244
+ import { homedir as homedir8 } from "os";
2195
2245
  function opencodeDbPaths(homeDir) {
2196
- const base = homeDir ?? homedir7();
2246
+ const base = homeDir ?? homedir8();
2197
2247
  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"));
2248
+ if (!homeDir && process.env.XDG_DATA_HOME) paths.push(join11(process.env.XDG_DATA_HOME, "opencode", "opencode.db"));
2249
+ paths.push(join11(base, ".local", "share", "opencode", "opencode.db"));
2250
+ if (process.platform === "darwin") paths.push(join11(base, "Library", "Application Support", "opencode", "opencode.db"));
2201
2251
  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"));
2252
+ const lad = homeDir ? join11(homeDir, "AppData", "Local") : process.env.LOCALAPPDATA;
2253
+ if (lad) paths.push(join11(lad, "opencode", "opencode.db"));
2204
2254
  }
2205
2255
  return [...new Set(paths)];
2206
2256
  }
@@ -2268,107 +2318,6 @@ var opencodeProvider = {
2268
2318
  import { access as access6, readFile as readFile4, readdir as readdir5 } from "fs/promises";
2269
2319
  import { join as join12 } from "path";
2270
2320
  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
- }
2312
- }
2313
- }
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
2321
  var USAGE_URL3 = "https://api.github.com/copilot_internal/user";
2373
2322
  var GH_KEYCHAIN_SERVICE = "gh:github.com";
2374
2323
  function ghConfigDir(homeDir) {
@@ -2387,12 +2336,24 @@ function ghHostsPath(homeDir) {
2387
2336
  return join12(ghConfigDir(homeDir), "hosts.yml");
2388
2337
  }
2389
2338
  async function detectCopilot(homeDir) {
2390
- try {
2391
- await access6(ghHostsPath(homeDir));
2392
- return true;
2393
- } catch {
2339
+ const home = homeDir ?? homedir9();
2340
+ const candidates = [
2341
+ join12(home, ".config", "github-copilot"),
2342
+ join12(home, ".copilot"),
2343
+ join12(vscodeUserDir(homeDir), "globalStorage", "github.copilot"),
2344
+ join12(vscodeUserDir(homeDir), "globalStorage", "github.copilot-chat")
2345
+ ];
2346
+ if (process.platform === "win32") {
2347
+ candidates.push(join12(envDir("LOCALAPPDATA") ?? join12(home, "AppData", "Local"), "github-copilot"));
2394
2348
  }
2395
- return onPath(["gh"]);
2349
+ for (const path of candidates) {
2350
+ try {
2351
+ await access6(path);
2352
+ return true;
2353
+ } catch {
2354
+ }
2355
+ }
2356
+ return false;
2396
2357
  }
2397
2358
  function unquoteYamlValue(value) {
2398
2359
  const trimmed = value.trim();
@@ -2481,7 +2442,7 @@ async function loadTokenFromVsCode(homeDir) {
2481
2442
  return null;
2482
2443
  }
2483
2444
  async function loadToken(homeDir) {
2484
- return await loadTokenFromHosts(homeDir) || await loadTokenFromGhKeychain() || await loadTokenFromVsCode(homeDir);
2445
+ return await loadTokenFromHosts(homeDir) || (homeDir ? null : await loadTokenFromGhKeychain()) || await loadTokenFromVsCode(homeDir);
2485
2446
  }
2486
2447
  function redactToken(token) {
2487
2448
  return token.length <= 4 ? "****" : `****${token.slice(-4)}`;
@@ -2489,14 +2450,6 @@ function redactToken(token) {
2489
2450
  function resetDate(value) {
2490
2451
  return typeof value === "string" && value.trim() ? resetIn(value) : null;
2491
2452
  }
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
2453
  function boolValue2(value) {
2501
2454
  if (typeof value === "boolean") return value;
2502
2455
  if (typeof value === "number") return value !== 0;
@@ -2509,10 +2462,10 @@ function boolValue2(value) {
2509
2462
  }
2510
2463
  function percentMetric2(label, snapshot, reset, primary) {
2511
2464
  if (!snapshot) return null;
2512
- const entitlement = numberValue4(snapshot.entitlement);
2513
- const remaining = numberValue4(snapshot.remaining);
2465
+ const entitlement = numberValue(snapshot.entitlement);
2466
+ const remaining = numberValue(snapshot.remaining);
2514
2467
  if (boolValue2(snapshot.unlimited) === true || entitlement === -1 || remaining === -1 || entitlement === 0) return null;
2515
- const percentRemaining = numberValue4(snapshot.percent_remaining);
2468
+ const percentRemaining = numberValue(snapshot.percent_remaining);
2516
2469
  const used = percentRemaining !== void 0 ? 100 - percentRemaining : entitlement !== void 0 && entitlement > 0 && remaining !== void 0 ? 100 - remaining / entitlement * 100 : void 0;
2517
2470
  if (used === void 0) return null;
2518
2471
  return {
@@ -2525,8 +2478,8 @@ function percentMetric2(label, snapshot, reset, primary) {
2525
2478
  };
2526
2479
  }
2527
2480
  function countMetric(label, remaining, total, reset) {
2528
- const remainingCount = numberValue4(remaining);
2529
- const totalCount = numberValue4(total);
2481
+ const remainingCount = numberValue(remaining);
2482
+ const totalCount = numberValue(total);
2530
2483
  if (remainingCount === void 0 || totalCount === void 0 || totalCount <= 0) return null;
2531
2484
  return {
2532
2485
  label,
@@ -2540,7 +2493,7 @@ function overageMetric(snapshot) {
2540
2493
  if (!snapshot || boolValue2(snapshot.overage_permitted) !== true) return null;
2541
2494
  return {
2542
2495
  label: "Extra",
2543
- used: Math.max(0, numberValue4(snapshot.overage_count) ?? 0),
2496
+ used: Math.max(0, numberValue(snapshot.overage_count) ?? 0),
2544
2497
  limit: null,
2545
2498
  format: { kind: "count" }
2546
2499
  };
@@ -2616,35 +2569,17 @@ import { access as access7, readdir as readdir7 } from "fs/promises";
2616
2569
  import { join as join14 } from "path";
2617
2570
  import { homedir as homedir11 } from "os";
2618
2571
 
2619
- // src/providers/cloud-code.ts
2572
+ // src/providers/cloud-code/auth.ts
2620
2573
  import { readFile as readFile5, readdir as readdir6, realpath, stat } from "fs/promises";
2621
2574
  import { spawnSync } from "child_process";
2622
2575
  import { homedir as homedir10 } from "os";
2623
2576
  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
2577
  var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
2632
2578
  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
2579
  var MAX_BUNDLE_READ = 32 * 1024 * 1024;
2634
2580
  var cachedClient;
2635
2581
  var OAUTH_TOKEN_KEY = "antigravityUnifiedStateSync.oauthToken";
2636
2582
  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
2583
  function readVarint(bytes, start) {
2649
2584
  let value = 0;
2650
2585
  let shift = 0;
@@ -2738,10 +2673,6 @@ async function readAntigravityOAuthToken(db) {
2738
2673
  if (!accessToken && !refreshToken) return { token: null, status: "ok" };
2739
2674
  return { token: { accessToken, refreshToken, expirySeconds }, status: "ok" };
2740
2675
  }
2741
- function redact(token) {
2742
- if (!token) return "none";
2743
- return `...${token.slice(-4)}`;
2744
- }
2745
2676
  function geminiBundleCandidates() {
2746
2677
  const candidates = [];
2747
2678
  const addBundle = (nodeModulesRoot) => {
@@ -2853,6 +2784,30 @@ async function refreshAccessToken(refreshToken) {
2853
2784
  return null;
2854
2785
  }
2855
2786
  }
2787
+
2788
+ // src/providers/cloud-code/api.ts
2789
+ var CLOUD_CODE_URLS = [
2790
+ "https://daily-cloudcode-pa.googleapis.com",
2791
+ "https://cloudcode-pa.googleapis.com"
2792
+ ];
2793
+ var LOAD_CODE_ASSIST_PATH = "/v1internal:loadCodeAssist";
2794
+ var FETCH_MODELS_PATH = "/v1internal:fetchAvailableModels";
2795
+ var RETRIEVE_QUOTA_PATH = "/v1internal:retrieveUserQuota";
2796
+ var CC_MODEL_BLACKLIST = {
2797
+ MODEL_CHAT_20706: true,
2798
+ MODEL_CHAT_23310: true,
2799
+ MODEL_GOOGLE_GEMINI_2_5_FLASH: true,
2800
+ MODEL_GOOGLE_GEMINI_2_5_FLASH_THINKING: true,
2801
+ MODEL_GOOGLE_GEMINI_2_5_FLASH_LITE: true,
2802
+ MODEL_GOOGLE_GEMINI_2_5_PRO: true,
2803
+ MODEL_PLACEHOLDER_M19: true,
2804
+ MODEL_PLACEHOLDER_M9: true,
2805
+ MODEL_PLACEHOLDER_M12: true
2806
+ };
2807
+ function redact(token) {
2808
+ if (!token) return "none";
2809
+ return `...${token.slice(-4)}`;
2810
+ }
2856
2811
  async function requestCloudCodeJson(path, token, body) {
2857
2812
  for (const base of CLOUD_CODE_URLS) {
2858
2813
  try {
@@ -2958,6 +2913,8 @@ async function fetchCloudCodeQuota(token, expiredMessage = "Token expired") {
2958
2913
  if (!refreshed) return { ok: false, plan: result.plan, error: expiredMessage };
2959
2914
  return fetchWithAccessToken(refreshed);
2960
2915
  }
2916
+
2917
+ // src/providers/cloud-code/index.ts
2961
2918
  function normalizeLabel(label) {
2962
2919
  return label.replace(/\s*\([^)]*\)\s*$/, "").trim();
2963
2920
  }
@@ -3105,7 +3062,7 @@ var PRICING3 = {
3105
3062
  "gemini-2.0-flash": { in: 1e-7, out: 4e-7, cr: 25e-9 }
3106
3063
  };
3107
3064
  var PRICE_KEYS3 = Object.keys(PRICING3).sort((a, b) => b.length - a.length);
3108
- var ZERO_PRICE3 = { in: 0, out: 0, cr: 0 };
3065
+ var ZERO_PRICE2 = { in: 0, out: 0, cr: 0 };
3109
3066
  function geminiTmpDir(homeDir) {
3110
3067
  return join15(homeDir ?? homedir12(), ".gemini", "tmp");
3111
3068
  }
@@ -3116,7 +3073,7 @@ function priceFor3(model) {
3116
3073
  const rest = m.slice(key.length);
3117
3074
  if (rest === "" || rest[0] === "-") return PRICING3[key];
3118
3075
  }
3119
- return ZERO_PRICE3;
3076
+ return ZERO_PRICE2;
3120
3077
  }
3121
3078
  function shortModel2(model) {
3122
3079
  return model.replace(/(-preview|-customtools)+$/, "");
@@ -3262,17 +3219,8 @@ async function hasGeminiChatSessions(homeDir) {
3262
3219
  }
3263
3220
  return listing.some((path) => /(^|[\\/])chats[\\/]session-.*\.jsonl$/.test(path));
3264
3221
  }
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
3222
  function geminiIdentity(creds) {
3275
- const tokenPayload = typeof creds?.id_token === "string" && creds.id_token.includes(".") ? decodeBase64UrlJson3(creds.id_token.split(".")[1]) : null;
3223
+ const tokenPayload = typeof creds?.id_token === "string" && creds.id_token.includes(".") ? decodeBase64UrlJson(creds.id_token.split(".")[1]) : null;
3276
3224
  const email = typeof creds?.email === "string" && creds.email.trim() || typeof tokenPayload?.email === "string" && tokenPayload.email.trim() || null;
3277
3225
  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
3226
  return { email, displayName };
@@ -3323,6 +3271,113 @@ var geminiProvider = {
3323
3271
  fetchBilling: (account) => geminiBilling(account)
3324
3272
  };
3325
3273
 
3274
+ // src/providers/detect.ts
3275
+ import { accessSync as accessSync2, constants as constants2, existsSync } from "fs";
3276
+ import { join as join17, delimiter as delimiter2, isAbsolute as isAbsolute2 } from "path";
3277
+ import { homedir as homedir14 } from "os";
3278
+ function searchDirs() {
3279
+ const home = homedir14();
3280
+ const fromEnv = (process.env.PATH ?? "").split(delimiter2).filter(Boolean);
3281
+ const extra = process.platform === "win32" ? [
3282
+ process.env.APPDATA && join17(process.env.APPDATA, "npm"),
3283
+ process.env.LOCALAPPDATA && join17(process.env.LOCALAPPDATA, "pnpm"),
3284
+ join17(home, "scoop", "shims")
3285
+ ] : [
3286
+ "/opt/homebrew/bin",
3287
+ "/usr/local/bin",
3288
+ "/usr/bin",
3289
+ "/bin",
3290
+ "/opt/local/bin",
3291
+ join17(home, ".local", "bin"),
3292
+ join17(home, "bin"),
3293
+ join17(home, ".npm-global", "bin"),
3294
+ join17(home, ".bun", "bin"),
3295
+ join17(home, ".local", "share", "pnpm")
3296
+ ];
3297
+ return [.../* @__PURE__ */ new Set([...fromEnv, ...extra.filter((d) => !!d)])];
3298
+ }
3299
+ function isExec2(p) {
3300
+ try {
3301
+ accessSync2(p, process.platform === "win32" ? constants2.F_OK : constants2.X_OK);
3302
+ return true;
3303
+ } catch {
3304
+ return false;
3305
+ }
3306
+ }
3307
+ function onPath(names) {
3308
+ const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").map((e) => e.toLowerCase()).concat("") : [""];
3309
+ for (const dir of searchDirs()) {
3310
+ for (const n of names) {
3311
+ for (const e of exts) {
3312
+ if (isExec2(join17(dir, n + e))) return true;
3313
+ }
3314
+ }
3315
+ }
3316
+ return false;
3317
+ }
3318
+ function anyExists(paths) {
3319
+ return paths.some((p) => !!p && isExec2(p));
3320
+ }
3321
+ function installSignals(id) {
3322
+ const home = homedir14();
3323
+ const pf = process.env.ProgramFiles;
3324
+ const pf86 = process.env["ProgramFiles(x86)"];
3325
+ const lad = process.env.LOCALAPPDATA;
3326
+ switch (id) {
3327
+ case "claude":
3328
+ return onPath(["claude"]) || anyExists([
3329
+ "/Applications/Claude.app",
3330
+ join17(home, "Applications", "Claude.app"),
3331
+ lad && join17(lad, "Programs", "claude", "Claude.exe")
3332
+ ]);
3333
+ case "codex": {
3334
+ const bin = process.env.CODEX_BIN;
3335
+ if (bin && isAbsolute2(bin) && isExec2(bin)) return true;
3336
+ return onPath(["codex"]) || anyExists([
3337
+ lad && join17(lad, "Programs", "OpenAI", "Codex", "bin", "codex.exe"),
3338
+ lad && join17(lad, "Programs", "OpenAI", "Codex", "codex.exe"),
3339
+ lad && join17(lad, "Programs", "codex", "codex.exe"),
3340
+ pf && join17(pf, "OpenAI", "Codex", "bin", "codex.exe")
3341
+ ]) || existsSync(join17(home, ".codex", "sessions")) || existsSync(join17(home, ".codex", "auth.json"));
3342
+ }
3343
+ case "cursor":
3344
+ return onPath(["cursor", "cursor-agent"]) || anyExists([
3345
+ "/Applications/Cursor.app",
3346
+ join17(home, "Applications", "Cursor.app"),
3347
+ lad && join17(lad, "Programs", "cursor", "Cursor.exe"),
3348
+ pf && join17(pf, "Cursor", "Cursor.exe"),
3349
+ pf86 && join17(pf86, "Cursor", "Cursor.exe"),
3350
+ "/opt/Cursor/cursor",
3351
+ "/usr/share/cursor/cursor",
3352
+ "/usr/bin/cursor"
3353
+ ]);
3354
+ case "pi":
3355
+ return onPath(["pi"]);
3356
+ case "opencode":
3357
+ return onPath(["opencode"]);
3358
+ case "copilot": {
3359
+ const appData = process.env.APPDATA;
3360
+ return [
3361
+ join17(home, ".config", "github-copilot"),
3362
+ join17(home, ".copilot"),
3363
+ lad && join17(lad, "github-copilot"),
3364
+ appData && join17(appData, "GitHub Copilot"),
3365
+ join17(home, ".local", "share", "gh", "extensions", "gh-copilot")
3366
+ ].some((p) => !!p && existsSync(p));
3367
+ }
3368
+ case "antigravity":
3369
+ return onPath(["antigravity"]) || anyExists([
3370
+ "/Applications/Antigravity.app",
3371
+ join17(home, "Applications", "Antigravity.app"),
3372
+ lad && join17(lad, "Programs", "Antigravity", "Antigravity.exe")
3373
+ ]);
3374
+ case "gemini":
3375
+ return onPath(["gemini"]);
3376
+ default:
3377
+ return false;
3378
+ }
3379
+ }
3380
+
3326
3381
  // src/providers/index.ts
3327
3382
  var PROVIDER_ORDER = [...PROVIDER_IDS];
3328
3383
  var PROVIDERS = {
@@ -3350,11 +3405,32 @@ async function detectProviders() {
3350
3405
  }
3351
3406
 
3352
3407
  // 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";
3408
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync } from "fs";
3409
+ import { homedir as homedir15 } from "os";
3410
+ import { basename, join as join19, resolve } from "path";
3411
+
3412
+ // src/providers/codex/identity.ts
3413
+ import { readFileSync as readFileSync2 } from "fs";
3414
+ import { join as join18 } from "path";
3415
+ function codexAuthPaths(homeDir) {
3416
+ return [join18(homeDir, ".codex", "auth.json"), join18(homeDir, "auth.json")];
3417
+ }
3418
+ function readCodexIdentity(homeDir) {
3419
+ for (const path of codexAuthPaths(homeDir)) {
3420
+ try {
3421
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
3422
+ const { email, displayName, payload } = identityFromIdToken(parsed?.tokens?.id_token);
3423
+ if (!payload) continue;
3424
+ return { email, displayName };
3425
+ } catch {
3426
+ }
3427
+ }
3428
+ return {};
3429
+ }
3430
+
3431
+ // src/accounts.ts
3356
3432
  function accountKey(providerId, homeDir) {
3357
- return `${providerId}:${homeDir ? resolve(expandHome(homeDir)) : homedir14()}`;
3433
+ return `${providerId}:${homeDir ? resolve(expandHome(homeDir)) : homedir15()}`;
3358
3434
  }
3359
3435
  function uniqueId(base, used) {
3360
3436
  let id = slugify(base) || "account";
@@ -3373,23 +3449,11 @@ function uniqueId(base, used) {
3373
3449
  used.add(id);
3374
3450
  return id;
3375
3451
  }
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
3452
  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"));
3453
+ 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
3454
  }
3391
3455
  function candidateAlternateHomes(prefix) {
3392
- const home = homedir14();
3456
+ const home = homedir15();
3393
3457
  let entries;
3394
3458
  try {
3395
3459
  entries = readdirSync(home);
@@ -3400,7 +3464,7 @@ function candidateAlternateHomes(prefix) {
3400
3464
  const pattern = new RegExp(`^\\.${prefix}[_-]`);
3401
3465
  for (const name of entries) {
3402
3466
  if (!pattern.test(name)) continue;
3403
- const path = join17(home, name);
3467
+ const path = join19(home, name);
3404
3468
  try {
3405
3469
  if (!statSync(path).isDirectory()) continue;
3406
3470
  out.push(path);
@@ -3410,45 +3474,16 @@ function candidateAlternateHomes(prefix) {
3410
3474
  return out.sort();
3411
3475
  }
3412
3476
  function labelForClaudeHome(homeDir) {
3413
- const identity = readClaudeIdentity2(homeDir);
3477
+ const identity = readClaudeIdentity(homeDir);
3414
3478
  if (identity.email) return `Claude ${identity.email}`;
3415
3479
  if (identity.displayName) return `Claude ${identity.displayName}`;
3416
3480
  const raw = basename(homeDir).replace(/^\.claude[_-]?/, "").replace(/[_-]+/g, " ").trim();
3417
3481
  return raw ? `Claude ${raw}` : "Claude";
3418
3482
  }
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
3483
  function hasCodexAuth(homeDir) {
3449
3484
  for (const path of codexAuthPaths(homeDir)) {
3450
3485
  try {
3451
- const parsed = JSON.parse(readFileSync(path, "utf-8"));
3486
+ const parsed = JSON.parse(readFileSync3(path, "utf-8"));
3452
3487
  const accessToken = parsed?.tokens?.access_token;
3453
3488
  if (typeof accessToken === "string" && accessToken.trim()) return true;
3454
3489
  } catch {