tokmon 0.20.5 → 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.
- package/dist/{bootstrap-ink-MVH5QEVR.js → bootstrap-ink-QHJOOWIW.js} +552 -439
- package/dist/{chunk-XQEJ4WQ5.js → chunk-7MFZMI5C.js} +2 -2
- package/dist/{chunk-YYATNY5E.js → chunk-CPN67OK6.js} +70 -111
- package/dist/{chunk-EHIQHGJL.js → chunk-YCIUMAKK.js} +832 -425
- package/dist/cli.js +4 -4
- package/dist/{config-C6Z65JUP.js → config-UPNBGIJO.js} +1 -1
- package/dist/{daemon-HEBPH6PG.js → daemon-LZVP4SWF.js} +3 -3
- package/dist/server-F7CUK4ZM.js +9 -0
- package/dist/web/assets/{breakdown-DFnPYZtA.js → breakdown-ClwCJce8.js} +1 -1
- package/dist/web/assets/{chart-C8J22kR3.js → chart-Dcuh_e3J.js} +1 -1
- package/dist/web/assets/index-nAr5lqix.js +105 -0
- package/dist/web/assets/{timeline-BjoaOdbh.js → timeline-gDyrCQ0O.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/server-RM4ZXN6C.js +0 -9
- package/dist/web/assets/index-B9eW55YB.js +0 -105
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
expandHome,
|
|
7
7
|
isValidTimezone,
|
|
8
8
|
slugify
|
|
9
|
-
} from "./chunk-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
@@ -673,6 +715,8 @@ async function cursorActivity(homeDir) {
|
|
|
673
715
|
var BASE = "https://api2.cursor.sh/aiserver.v1.DashboardService";
|
|
674
716
|
var USAGE_URL = `${BASE}/GetCurrentPeriodUsage`;
|
|
675
717
|
var PLAN_URL = `${BASE}/GetPlanInfo`;
|
|
718
|
+
var CREDITS_URL = `${BASE}/GetCreditGrantsBalance`;
|
|
719
|
+
var STRIPE_URL = "https://cursor.com/api/auth/stripe";
|
|
676
720
|
function cursorStateDb(homeDir) {
|
|
677
721
|
const base = homeDir ?? homedir2();
|
|
678
722
|
const tail = ["Cursor", "User", "globalStorage", "state.vscdb"];
|
|
@@ -709,11 +753,13 @@ function cleanStoredString(value) {
|
|
|
709
753
|
const trimmed = value.trim().replace(/^"|"$/g, "");
|
|
710
754
|
return trimmed || void 0;
|
|
711
755
|
}
|
|
712
|
-
function
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
756
|
+
function cursorSessionToken(accessToken) {
|
|
757
|
+
const payload = accessToken.includes(".") ? decodeBase64UrlJson(accessToken.split(".")[1]) : null;
|
|
758
|
+
const subject = typeof payload?.sub === "string" ? payload.sub : null;
|
|
759
|
+
if (!subject) return null;
|
|
760
|
+
const parts = subject.split("|");
|
|
761
|
+
const userId = (parts.length > 1 ? parts[1] : parts[0]).trim();
|
|
762
|
+
return userId ? `${userId}%3A%3A${accessToken}` : null;
|
|
717
763
|
}
|
|
718
764
|
async function connectPost(url, token) {
|
|
719
765
|
try {
|
|
@@ -734,10 +780,47 @@ async function connectPost(url, token) {
|
|
|
734
780
|
return null;
|
|
735
781
|
}
|
|
736
782
|
}
|
|
737
|
-
async function
|
|
783
|
+
async function connectGetWithSession(url, token) {
|
|
784
|
+
const session = cursorSessionToken(token);
|
|
785
|
+
if (!session) return null;
|
|
786
|
+
try {
|
|
787
|
+
const res = await fetch(url, {
|
|
788
|
+
headers: {
|
|
789
|
+
"Cookie": `WorkosCursorSessionToken=${session}`,
|
|
790
|
+
"User-Agent": "tokmon"
|
|
791
|
+
},
|
|
792
|
+
signal: AbortSignal.timeout(1e4)
|
|
793
|
+
});
|
|
794
|
+
if (!res.ok) return { __status: res.status };
|
|
795
|
+
return await readJson(res);
|
|
796
|
+
} catch {
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function onDemandSpendCents(su, limit, remaining) {
|
|
801
|
+
for (const raw of [su.individualUsed, su.pooledUsed, su.totalSpend]) {
|
|
802
|
+
const n = numberValue(raw);
|
|
803
|
+
if (n !== void 0 && n > 0) return n;
|
|
804
|
+
}
|
|
805
|
+
const inferred = Math.max(0, limit - remaining);
|
|
806
|
+
if (inferred > 0) return inferred;
|
|
807
|
+
return numberValue(su.individualUsed) ?? numberValue(su.pooledUsed) ?? numberValue(su.totalSpend) ?? 0;
|
|
808
|
+
}
|
|
809
|
+
function appendCredits(metrics, creditGrants, stripe) {
|
|
810
|
+
const stripeBalance = numberValue(stripe?.customerBalance);
|
|
811
|
+
const stripeBalanceCents = stripeBalance !== void 0 && stripeBalance < 0 ? Math.abs(stripeBalance) : 0;
|
|
812
|
+
const hasGrants = creditGrants?.hasCreditGrants === true;
|
|
813
|
+
const grantTotal = hasGrants ? numberValue(creditGrants?.totalCents) ?? 0 : 0;
|
|
814
|
+
const grantUsed = hasGrants ? numberValue(creditGrants?.usedCents) ?? 0 : 0;
|
|
815
|
+
const hasValidGrants = hasGrants && grantTotal > 0;
|
|
816
|
+
const total = (hasValidGrants ? grantTotal : 0) + stripeBalanceCents;
|
|
817
|
+
if (total <= 0) return;
|
|
818
|
+
metrics.push({ label: "Credits", used: dollars(Math.max(0, total - (hasValidGrants ? grantUsed : 0))), limit: null, format: { kind: "dollars" } });
|
|
819
|
+
}
|
|
820
|
+
async function cursorBilling(account, tz) {
|
|
738
821
|
const [core, activity, spend] = await Promise.all([
|
|
739
822
|
cursorBillingCore(account),
|
|
740
|
-
cursorActivity(account.homeDir),
|
|
823
|
+
cursorActivity(tz, account.homeDir),
|
|
741
824
|
cursorModelSpend(account.homeDir)
|
|
742
825
|
]);
|
|
743
826
|
let merged = activity;
|
|
@@ -760,22 +843,24 @@ async function cursorBillingCore(account) {
|
|
|
760
843
|
readState(db, "cursorAuth/cachedEmail"),
|
|
761
844
|
readState(db, "cursorAuth/cachedName")
|
|
762
845
|
]);
|
|
763
|
-
const token = tokenRes.value;
|
|
846
|
+
const token = cleanStoredString(tokenRes.value);
|
|
764
847
|
const membership = membershipRes.value;
|
|
765
848
|
const email = cleanStoredString(emailRes.value);
|
|
766
849
|
const displayName = cleanStoredString(nameRes.value);
|
|
767
850
|
const planFallback = membership ? membership.charAt(0).toUpperCase() + membership.slice(1) : null;
|
|
768
851
|
if (!token) {
|
|
769
852
|
const error = tokenRes.status === "ok" ? "Not signed in \u2014 open Cursor" : sqliteStatusMessage(tokenRes.status);
|
|
770
|
-
return { plan: planFallback, metrics: [], error, ...identityFields(email, displayName) };
|
|
853
|
+
return { plan: planFallback, metrics: [], error, ...identityFields({ email, displayName }) };
|
|
771
854
|
}
|
|
772
|
-
const [usage, planInfo] = await Promise.all([
|
|
855
|
+
const [usage, planInfo, creditGrants, stripe] = await Promise.all([
|
|
773
856
|
connectPost(USAGE_URL, token),
|
|
774
|
-
connectPost(PLAN_URL, token)
|
|
857
|
+
connectPost(PLAN_URL, token),
|
|
858
|
+
connectPost(CREDITS_URL, token),
|
|
859
|
+
connectGetWithSession(STRIPE_URL, token)
|
|
775
860
|
]);
|
|
776
861
|
if (!usage || usage.__status) {
|
|
777
862
|
const expired = usage?.__status === 401 || usage?.__status === 403;
|
|
778
|
-
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 }) };
|
|
779
864
|
}
|
|
780
865
|
const planName = planInfo?.planInfo?.planName ?? planFallback;
|
|
781
866
|
const price = planInfo?.planInfo?.price;
|
|
@@ -783,43 +868,69 @@ async function cursorBillingCore(account) {
|
|
|
783
868
|
const pu = usage.planUsage ?? {};
|
|
784
869
|
const metrics = [];
|
|
785
870
|
const rawEnd = usage.billingCycleEnd;
|
|
786
|
-
const endMs =
|
|
871
|
+
const endMs = numberValue(rawEnd) ?? NaN;
|
|
787
872
|
const iso = msToIso(endMs);
|
|
788
873
|
const resets = iso && endMs > 0 ? resetIn(iso) : null;
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
874
|
+
const limit = numberValue(pu.limit);
|
|
875
|
+
const planUsedCents = numberValue(pu.totalSpend) ?? (limit !== void 0 && numberValue(pu.remaining) !== void 0 ? limit - numberValue(pu.remaining) : void 0);
|
|
876
|
+
const computedPercent = limit !== void 0 && limit > 0 && planUsedCents !== void 0 ? planUsedCents / limit * 100 : void 0;
|
|
877
|
+
const totalPercentUsed = numberValue(pu.totalPercentUsed) ?? computedPercent;
|
|
878
|
+
const su = usage.spendLimitUsage;
|
|
879
|
+
const planLower = typeof planName === "string" ? planName.trim().toLowerCase() : "";
|
|
880
|
+
const pooledLimit = numberValue(su?.pooledLimit) ?? 0;
|
|
881
|
+
const isTeamAccount = planLower === "team" || String(su?.limitType ?? "").toLowerCase() === "team" || pooledLimit > 0;
|
|
882
|
+
if (isTeamAccount && limit !== void 0 && planUsedCents !== void 0) {
|
|
883
|
+
metrics.push({
|
|
884
|
+
label: "Usage",
|
|
885
|
+
used: dollars(Math.max(0, planUsedCents)),
|
|
886
|
+
limit: dollars(limit),
|
|
887
|
+
format: { kind: "dollars" },
|
|
888
|
+
resetsAt: resets,
|
|
889
|
+
primary: true
|
|
890
|
+
});
|
|
891
|
+
} else if (totalPercentUsed !== void 0) {
|
|
892
|
+
metrics.push(percentMetric("Usage", totalPercentUsed, resets, true));
|
|
893
|
+
if (limit !== void 0 && planUsedCents !== void 0) {
|
|
793
894
|
metrics.push({
|
|
794
895
|
label: "Spend",
|
|
795
|
-
used: dollars(
|
|
796
|
-
limit: dollars(
|
|
896
|
+
used: dollars(Math.max(0, planUsedCents)),
|
|
897
|
+
limit: dollars(limit),
|
|
797
898
|
format: { kind: "dollars" }
|
|
798
899
|
});
|
|
799
900
|
}
|
|
800
901
|
}
|
|
801
|
-
|
|
802
|
-
|
|
902
|
+
const autoPercentUsed = numberValue(pu.autoPercentUsed);
|
|
903
|
+
if (autoPercentUsed !== void 0) {
|
|
904
|
+
metrics.push({ label: "Auto", used: autoPercentUsed, limit: 100, format: { kind: "percent" } });
|
|
803
905
|
}
|
|
804
|
-
|
|
805
|
-
|
|
906
|
+
const apiPercentUsed = numberValue(pu.apiPercentUsed);
|
|
907
|
+
if (apiPercentUsed !== void 0) {
|
|
908
|
+
metrics.push({ label: "API", used: apiPercentUsed, limit: 100, format: { kind: "percent" } });
|
|
806
909
|
}
|
|
807
|
-
const su = usage.spendLimitUsage;
|
|
808
910
|
if (su) {
|
|
809
|
-
const pair =
|
|
911
|
+
const pair = numberValue(su.individualLimit) !== void 0 && numberValue(su.individualRemaining) !== void 0 ? { limit: numberValue(su.individualLimit), remaining: numberValue(su.individualRemaining) } : numberValue(su.pooledLimit) !== void 0 && numberValue(su.pooledRemaining) !== void 0 ? { limit: numberValue(su.pooledLimit), remaining: numberValue(su.pooledRemaining) } : null;
|
|
912
|
+
const spent = onDemandSpendCents(su, pair?.limit ?? 0, pair?.remaining ?? 0);
|
|
810
913
|
if (pair && pair.limit > 0) {
|
|
811
914
|
metrics.push({
|
|
812
915
|
label: "On-demand",
|
|
813
|
-
used: dollars(
|
|
916
|
+
used: dollars(spent),
|
|
814
917
|
limit: dollars(pair.limit),
|
|
815
918
|
format: { kind: "dollars" }
|
|
816
919
|
});
|
|
920
|
+
} else if (spent > 0) {
|
|
921
|
+
metrics.push({
|
|
922
|
+
label: "On-demand",
|
|
923
|
+
used: dollars(spent),
|
|
924
|
+
limit: null,
|
|
925
|
+
format: { kind: "dollars" }
|
|
926
|
+
});
|
|
817
927
|
}
|
|
818
928
|
}
|
|
929
|
+
appendCredits(metrics, creditGrants, stripe);
|
|
819
930
|
if (metrics.length === 0) {
|
|
820
|
-
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 }) };
|
|
821
932
|
}
|
|
822
|
-
return { plan, metrics, error: null, ...identityFields(email, displayName) };
|
|
933
|
+
return { plan, metrics, error: null, ...identityFields({ email, displayName }) };
|
|
823
934
|
}
|
|
824
935
|
|
|
825
936
|
// src/providers/cursor/composer.ts
|
|
@@ -893,6 +1004,7 @@ var PRICING = {
|
|
|
893
1004
|
"claude-opus-4": { i: 5e-6, o: 25e-6, cc: 625e-8, cr: 5e-7 },
|
|
894
1005
|
"claude-3-opus": { i: 15e-6, o: 75e-6, cc: 1875e-8, cr: 15e-7 },
|
|
895
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 },
|
|
896
1008
|
"claude-haiku-4": { i: 1e-6, o: 5e-6, cc: 125e-8, cr: 1e-7 },
|
|
897
1009
|
"claude-fable-5": { i: 1e-5, o: 5e-5, cc: 125e-7, cr: 1e-6 }
|
|
898
1010
|
};
|
|
@@ -949,6 +1061,11 @@ function costOf(model, u) {
|
|
|
949
1061
|
function shortModel(model) {
|
|
950
1062
|
return model.replace("claude-", "").replace(/-\d{8}$/, "");
|
|
951
1063
|
}
|
|
1064
|
+
function timestampMs(value) {
|
|
1065
|
+
if (typeof value === "number" && Number.isFinite(value)) return value > 1e10 ? value : value * 1e3;
|
|
1066
|
+
if (typeof value === "string" && value.trim()) return new Date(value.trim()).getTime();
|
|
1067
|
+
return NaN;
|
|
1068
|
+
}
|
|
952
1069
|
async function parseFile(path) {
|
|
953
1070
|
const entries = [];
|
|
954
1071
|
const input = createReadStream(path);
|
|
@@ -961,7 +1078,7 @@ async function parseFile(path) {
|
|
|
961
1078
|
try {
|
|
962
1079
|
const obj = JSON.parse(line.charCodeAt(0) === 65279 ? line.slice(1) : line);
|
|
963
1080
|
if (obj.type !== "assistant" || !obj.message?.usage) continue;
|
|
964
|
-
const ts =
|
|
1081
|
+
const ts = timestampMs(obj.timestamp);
|
|
965
1082
|
if (!Number.isFinite(ts)) continue;
|
|
966
1083
|
const u = obj.message.usage;
|
|
967
1084
|
const model = typeof obj.message.model === "string" && obj.message.model ? obj.message.model : "unknown";
|
|
@@ -1032,8 +1149,8 @@ async function claudeTable(tz, homeDir) {
|
|
|
1032
1149
|
|
|
1033
1150
|
// src/providers/claude/billing.ts
|
|
1034
1151
|
import { readFile as readFile2 } from "fs/promises";
|
|
1035
|
-
import { join as
|
|
1036
|
-
import { homedir as
|
|
1152
|
+
import { join as join7 } from "path";
|
|
1153
|
+
import { homedir as homedir5 } from "os";
|
|
1037
1154
|
|
|
1038
1155
|
// src/providers/_shared/keychain.ts
|
|
1039
1156
|
import { execFile as execFileCb2 } from "child_process";
|
|
@@ -1060,7 +1177,10 @@ function unwrapGoKeyringBase64(raw) {
|
|
|
1060
1177
|
return Buffer.from(raw.slice(GO_KEYRING_PREFIX.length), "base64").toString("utf-8");
|
|
1061
1178
|
}
|
|
1062
1179
|
|
|
1063
|
-
// src/providers/claude/
|
|
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";
|
|
1064
1184
|
function titleWords(value) {
|
|
1065
1185
|
return value.split(/[_\s-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join(" ");
|
|
1066
1186
|
}
|
|
@@ -1071,25 +1191,22 @@ function claudeOrgPlanLabel(orgType) {
|
|
|
1071
1191
|
const label = titleWords(stripped);
|
|
1072
1192
|
return label ? `Claude ${label}` : null;
|
|
1073
1193
|
}
|
|
1074
|
-
|
|
1194
|
+
function readClaudeIdentity(homeDir) {
|
|
1075
1195
|
const base = homeDir ? expandHome(homeDir) : homedir4();
|
|
1076
1196
|
try {
|
|
1077
|
-
const parsed = JSON.parse(
|
|
1197
|
+
const parsed = JSON.parse(readFileSync(join6(base, ".claude.json"), "utf-8"));
|
|
1078
1198
|
const oauth = parsed?.oauthAccount;
|
|
1079
1199
|
const email = typeof oauth?.emailAddress === "string" && oauth.emailAddress.trim() ? oauth.emailAddress.trim() : void 0;
|
|
1080
1200
|
const displayName = typeof oauth?.displayName === "string" && oauth.displayName.trim() ? oauth.displayName.trim() : void 0;
|
|
1081
1201
|
const plan = claudeOrgPlanLabel(parsed?.organizationType);
|
|
1082
|
-
|
|
1202
|
+
const accountUuid = typeof oauth?.accountUuid === "string" && oauth.accountUuid.trim() ? oauth.accountUuid.trim() : void 0;
|
|
1203
|
+
return { email, displayName, plan: plan ?? void 0, accountUuid };
|
|
1083
1204
|
} catch {
|
|
1084
1205
|
return {};
|
|
1085
1206
|
}
|
|
1086
1207
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
email: identity.email ?? null,
|
|
1090
|
-
displayName: identity.displayName ?? null
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1208
|
+
|
|
1209
|
+
// src/providers/claude/billing.ts
|
|
1093
1210
|
function parseAuth(raw) {
|
|
1094
1211
|
try {
|
|
1095
1212
|
const creds = JSON.parse(raw);
|
|
@@ -1099,7 +1216,8 @@ function parseAuth(raw) {
|
|
|
1099
1216
|
return {
|
|
1100
1217
|
token,
|
|
1101
1218
|
subscriptionType: typeof o.subscriptionType === "string" ? o.subscriptionType : void 0,
|
|
1102
|
-
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
|
|
1103
1221
|
};
|
|
1104
1222
|
} catch {
|
|
1105
1223
|
return null;
|
|
@@ -1108,7 +1226,7 @@ function parseAuth(raw) {
|
|
|
1108
1226
|
async function readCredentialsFile(homeDir) {
|
|
1109
1227
|
for (const dir of claudeConfigDirs(homeDir)) {
|
|
1110
1228
|
try {
|
|
1111
|
-
const auth = parseAuth(await readFile2(
|
|
1229
|
+
const auth = parseAuth(await readFile2(join7(dir, ".credentials.json"), "utf-8"));
|
|
1112
1230
|
if (auth) return auth;
|
|
1113
1231
|
} catch {
|
|
1114
1232
|
}
|
|
@@ -1119,20 +1237,65 @@ async function readMacKeychain() {
|
|
|
1119
1237
|
const raw = await readMacKeychainRaw("Claude Code-credentials");
|
|
1120
1238
|
return raw ? parseAuth(raw) : null;
|
|
1121
1239
|
}
|
|
1122
|
-
async function
|
|
1240
|
+
async function authCandidates(homeDir) {
|
|
1123
1241
|
const expandedHomeDir = homeDir ? expandHome(homeDir) : void 0;
|
|
1124
|
-
const isDefault = !expandedHomeDir || expandedHomeDir ===
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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;
|
|
1129
1265
|
}
|
|
1130
|
-
return
|
|
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;
|
|
1131
1279
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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 };
|
|
1136
1299
|
}
|
|
1137
1300
|
function planLabel(auth) {
|
|
1138
1301
|
const sub = auth.subscriptionType;
|
|
@@ -1142,17 +1305,31 @@ function planLabel(auth) {
|
|
|
1142
1305
|
return tier ? `${base} ${tier[1]}x` : base;
|
|
1143
1306
|
}
|
|
1144
1307
|
var pct = (used, resets, primary) => percentMetric("", used, resets ?? null, primary);
|
|
1308
|
+
function boolValue(value) {
|
|
1309
|
+
if (typeof value === "boolean") return value;
|
|
1310
|
+
if (typeof value === "number") return value !== 0;
|
|
1311
|
+
if (typeof value === "string") return value.trim().toLowerCase() === "true" || value.trim() === "1";
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
function resetFrom(value) {
|
|
1315
|
+
if (typeof value === "string" && value.trim()) return resetIn(value);
|
|
1316
|
+
const n = numberValue(value);
|
|
1317
|
+
if (n === void 0) return null;
|
|
1318
|
+
const ms = Math.abs(n) < 1e10 ? n * 1e3 : n;
|
|
1319
|
+
return resetIn(new Date(ms).toISOString());
|
|
1320
|
+
}
|
|
1145
1321
|
function usageMetric(label, window, primary) {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
return { ...pct(
|
|
1322
|
+
const used = numberValue(window?.utilization);
|
|
1323
|
+
if (used === void 0) return null;
|
|
1324
|
+
return { ...pct(used, resetFrom(window?.resets_at), primary), label };
|
|
1149
1325
|
}
|
|
1150
1326
|
async function claudeBilling(account) {
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
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
|
+
}
|
|
1156
1333
|
const plan = identity.plan ?? planLabel(auth);
|
|
1157
1334
|
try {
|
|
1158
1335
|
const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
@@ -1163,30 +1340,37 @@ async function claudeBilling(account) {
|
|
|
1163
1340
|
},
|
|
1164
1341
|
signal: AbortSignal.timeout(1e4)
|
|
1165
1342
|
});
|
|
1166
|
-
if (res.status === 429)
|
|
1167
|
-
|
|
1168
|
-
|
|
1343
|
+
if (res.status === 429) {
|
|
1344
|
+
const retryAfter = numberValue(res.headers.get("retry-after"));
|
|
1345
|
+
const retryText = retryAfter !== void 0 ? ` \u2014 retry in ~${Math.ceil(retryAfter / 60)}m` : " \u2014 retrying next poll";
|
|
1346
|
+
return { plan, metrics: [], error: `Rate limited${retryText}`, ...identityFields(identity) };
|
|
1347
|
+
}
|
|
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) };
|
|
1169
1350
|
const data = await readJson(res);
|
|
1170
|
-
if (!data) return { plan, metrics: [], error: "Unexpected API response", ...
|
|
1351
|
+
if (!data) return { plan, metrics: [], error: "Unexpected API response", ...identityFields(identity) };
|
|
1171
1352
|
const metrics = [];
|
|
1172
|
-
const fiveHour = usageMetric("
|
|
1353
|
+
const fiveHour = usageMetric("Session", data.five_hour, true);
|
|
1173
1354
|
if (fiveHour) metrics.push(fiveHour);
|
|
1174
|
-
const sevenDay = usageMetric("
|
|
1355
|
+
const sevenDay = usageMetric("Weekly", data.seven_day);
|
|
1175
1356
|
if (sevenDay) metrics.push(sevenDay);
|
|
1176
1357
|
const sevenDaySonnet = usageMetric("Sonnet", data.seven_day_sonnet);
|
|
1177
1358
|
if (sevenDaySonnet) metrics.push(sevenDaySonnet);
|
|
1178
|
-
if (data.extra_usage?.is_enabled) {
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1359
|
+
if (boolValue(data.extra_usage?.is_enabled)) {
|
|
1360
|
+
const usedCredits = numberValue(data.extra_usage?.used_credits);
|
|
1361
|
+
const monthlyLimit = numberValue(data.extra_usage?.monthly_limit);
|
|
1362
|
+
if (usedCredits !== void 0 && (usedCredits > 0 || monthlyLimit !== void 0 && monthlyLimit > 0)) {
|
|
1363
|
+
metrics.push({
|
|
1364
|
+
label: "Extra",
|
|
1365
|
+
used: finite(usedCredits) / 100,
|
|
1366
|
+
limit: monthlyLimit !== void 0 && monthlyLimit > 0 ? monthlyLimit / 100 : null,
|
|
1367
|
+
format: { kind: "dollars", currency: data.extra_usage?.currency ?? "USD" }
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1186
1370
|
}
|
|
1187
|
-
return { plan, metrics, error: null, ...
|
|
1371
|
+
return { plan, metrics, error: null, ...identityFields(identity) };
|
|
1188
1372
|
} catch {
|
|
1189
|
-
return { plan, metrics: [], error: "Network error", ...
|
|
1373
|
+
return { plan, metrics: [], error: "Network error", ...identityFields(identity) };
|
|
1190
1374
|
}
|
|
1191
1375
|
}
|
|
1192
1376
|
|
|
@@ -1204,11 +1388,11 @@ var claudeProvider = {
|
|
|
1204
1388
|
};
|
|
1205
1389
|
|
|
1206
1390
|
// src/providers/codex/usage.ts
|
|
1207
|
-
import { readdir as readdir2, stat as fsStat2, access as access3 } from "fs/promises";
|
|
1391
|
+
import { readdir as readdir2, stat as fsStat2, access as access3, open as openFile } from "fs/promises";
|
|
1208
1392
|
import { createReadStream as createReadStream2 } from "fs";
|
|
1209
1393
|
import { createInterface as createInterface2 } from "readline";
|
|
1210
|
-
import { join as
|
|
1211
|
-
import { homedir as
|
|
1394
|
+
import { join as join8 } from "path";
|
|
1395
|
+
import { homedir as homedir6 } from "os";
|
|
1212
1396
|
var PRICING2 = {
|
|
1213
1397
|
"gpt-5.5-codex": { in: 5e-6, cr: 5e-7, out: 3e-5 },
|
|
1214
1398
|
"gpt-5.5": { in: 5e-6, cr: 5e-7, out: 3e-5 },
|
|
@@ -1220,21 +1404,26 @@ var PRICING2 = {
|
|
|
1220
1404
|
"gpt-5": { in: 125e-8, cr: 125e-9, out: 1e-5 },
|
|
1221
1405
|
"o4-mini": { in: 11e-7, cr: 275e-9, out: 44e-7 }
|
|
1222
1406
|
};
|
|
1223
|
-
var
|
|
1407
|
+
var FALLBACK_PRICE = PRICING2["gpt-5.5"];
|
|
1224
1408
|
var PRICE_KEYS2 = Object.keys(PRICING2).sort((a, b) => b.length - a.length);
|
|
1225
1409
|
function codexHomes(homeDir) {
|
|
1226
|
-
if (homeDir) return [.../* @__PURE__ */ new Set([
|
|
1410
|
+
if (homeDir) return [.../* @__PURE__ */ new Set([join8(homeDir, ".codex"), homeDir])];
|
|
1227
1411
|
const homes = [];
|
|
1228
1412
|
const codexHome = envDir("CODEX_HOME");
|
|
1229
1413
|
if (codexHome) homes.push(codexHome);
|
|
1230
|
-
homes.push(
|
|
1231
|
-
homes.push(
|
|
1414
|
+
homes.push(join8(homedir6(), ".codex"));
|
|
1415
|
+
homes.push(join8(homedir6(), ".config", "codex"));
|
|
1232
1416
|
return [...new Set(homes)];
|
|
1233
1417
|
}
|
|
1234
1418
|
async function detectCodex(homeDir) {
|
|
1235
1419
|
for (const home of codexHomes(homeDir)) {
|
|
1236
1420
|
try {
|
|
1237
|
-
await access3(
|
|
1421
|
+
await access3(join8(home, "sessions"));
|
|
1422
|
+
return true;
|
|
1423
|
+
} catch {
|
|
1424
|
+
}
|
|
1425
|
+
try {
|
|
1426
|
+
await access3(join8(home, "archived_sessions"));
|
|
1238
1427
|
return true;
|
|
1239
1428
|
} catch {
|
|
1240
1429
|
}
|
|
@@ -1246,7 +1435,8 @@ function modelKeyMatches(model, key) {
|
|
|
1246
1435
|
while (idx >= 0) {
|
|
1247
1436
|
const before = idx === 0 ? "" : model[idx - 1];
|
|
1248
1437
|
const rest = model.slice(idx + key.length);
|
|
1249
|
-
|
|
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]))) {
|
|
1250
1440
|
return true;
|
|
1251
1441
|
}
|
|
1252
1442
|
idx = model.indexOf(key, idx + key.length);
|
|
@@ -1258,11 +1448,11 @@ function priceFor2(model) {
|
|
|
1258
1448
|
for (const key of PRICE_KEYS2) {
|
|
1259
1449
|
if (modelKeyMatches(m, key)) return PRICING2[key];
|
|
1260
1450
|
}
|
|
1261
|
-
return
|
|
1451
|
+
return FALLBACK_PRICE;
|
|
1262
1452
|
}
|
|
1263
1453
|
function extractModel(obj) {
|
|
1264
1454
|
const p = obj?.payload ?? obj;
|
|
1265
|
-
return p?.model || p?.collaboration_mode?.settings?.model || p?.model_slug || p?.config?.model || p?.info?.model || null;
|
|
1455
|
+
return p?.model || p?.model_name || p?.collaboration_mode?.settings?.model || p?.model_slug || p?.config?.model || p?.info?.model || p?.info?.model_name || p?.info?.model_slug || p?.metadata?.model || p?.info?.metadata?.model || null;
|
|
1266
1456
|
}
|
|
1267
1457
|
function subtractClamped(cur, prev) {
|
|
1268
1458
|
const sub = (a, b) => Math.max(0, (a ?? 0) - (b ?? 0));
|
|
@@ -1270,38 +1460,160 @@ function subtractClamped(cur, prev) {
|
|
|
1270
1460
|
input_tokens: sub(cur.input_tokens, prev?.input_tokens),
|
|
1271
1461
|
cached_input_tokens: sub(cur.cached_input_tokens, prev?.cached_input_tokens),
|
|
1272
1462
|
output_tokens: sub(cur.output_tokens, prev?.output_tokens),
|
|
1273
|
-
reasoning_output_tokens: sub(cur.reasoning_output_tokens, prev?.reasoning_output_tokens)
|
|
1463
|
+
reasoning_output_tokens: sub(cur.reasoning_output_tokens, prev?.reasoning_output_tokens),
|
|
1464
|
+
total_tokens: sub(cur.total_tokens, prev?.total_tokens)
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
function tokenNumber(obj, keys) {
|
|
1468
|
+
for (const key of keys) {
|
|
1469
|
+
const raw = obj?.[key];
|
|
1470
|
+
const n = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() ? Number(raw) : NaN;
|
|
1471
|
+
if (Number.isFinite(n) && n >= 0) return Math.floor(n);
|
|
1472
|
+
}
|
|
1473
|
+
return void 0;
|
|
1474
|
+
}
|
|
1475
|
+
function normalizeUsage(obj) {
|
|
1476
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
1477
|
+
const input = tokenNumber(obj, ["input_tokens", "prompt_tokens", "input"]);
|
|
1478
|
+
const cached = tokenNumber(obj, ["cached_input_tokens", "cache_read_input_tokens", "cached_tokens"]);
|
|
1479
|
+
const output = tokenNumber(obj, ["output_tokens", "completion_tokens", "output"]);
|
|
1480
|
+
const reasoning = tokenNumber(obj, ["reasoning_output_tokens", "reasoning_tokens"]);
|
|
1481
|
+
let total = tokenNumber(obj, ["total_tokens"]);
|
|
1482
|
+
const hasUsage = [input, cached, output, reasoning, total].some((v) => v !== void 0);
|
|
1483
|
+
if (!hasUsage) return void 0;
|
|
1484
|
+
if (total === void 0 || total === 0 && (input ?? 0) + (output ?? 0) + (reasoning ?? 0) > 0) {
|
|
1485
|
+
total = (input ?? 0) + (output ?? 0) + (reasoning ?? 0);
|
|
1486
|
+
}
|
|
1487
|
+
return {
|
|
1488
|
+
input_tokens: input ?? 0,
|
|
1489
|
+
cached_input_tokens: cached ?? 0,
|
|
1490
|
+
output_tokens: output ?? 0,
|
|
1491
|
+
reasoning_output_tokens: reasoning ?? 0,
|
|
1492
|
+
total_tokens: total
|
|
1274
1493
|
};
|
|
1275
1494
|
}
|
|
1276
1495
|
function eventSig(last, total) {
|
|
1277
|
-
const f = (x) => x ? `${x.input_tokens ?? 0},${x.cached_input_tokens ?? 0},${x.output_tokens ?? 0},${x.reasoning_output_tokens ?? 0}` : "-";
|
|
1496
|
+
const f = (x) => x ? `${x.input_tokens ?? 0},${x.cached_input_tokens ?? 0},${x.output_tokens ?? 0},${x.reasoning_output_tokens ?? 0},${x.total_tokens ?? 0}` : "-";
|
|
1278
1497
|
return `${f(last)}|${f(total)}`;
|
|
1279
1498
|
}
|
|
1499
|
+
function timestampMs2(value) {
|
|
1500
|
+
if (typeof value === "number" && Number.isFinite(value)) return value > 1e10 ? value : value * 1e3;
|
|
1501
|
+
if (typeof value === "string" && value.trim()) return new Date(value.trim()).getTime();
|
|
1502
|
+
return NaN;
|
|
1503
|
+
}
|
|
1504
|
+
function timestampSecond(value) {
|
|
1505
|
+
if (typeof value === "string" && value.trim().length >= 19) return value.trim().slice(0, 19);
|
|
1506
|
+
const ts = timestampMs2(value);
|
|
1507
|
+
return Number.isFinite(ts) ? new Date(ts).toISOString().slice(0, 19) : null;
|
|
1508
|
+
}
|
|
1509
|
+
async function hasThreadSpawn(path) {
|
|
1510
|
+
let handle = null;
|
|
1511
|
+
try {
|
|
1512
|
+
handle = await openFile(path, "r");
|
|
1513
|
+
const buffer = Buffer.alloc(16 * 1024);
|
|
1514
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
1515
|
+
return buffer.subarray(0, bytesRead).includes("thread_spawn");
|
|
1516
|
+
} catch {
|
|
1517
|
+
return false;
|
|
1518
|
+
} finally {
|
|
1519
|
+
await handle?.close().catch(() => {
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
async function detectReplaySecond(path) {
|
|
1524
|
+
if (!await hasThreadSpawn(path)) return null;
|
|
1525
|
+
let first = null;
|
|
1526
|
+
const input = createReadStream2(path);
|
|
1527
|
+
input.on("error", () => {
|
|
1528
|
+
});
|
|
1529
|
+
const rl = createInterface2({ input, crlfDelay: Infinity });
|
|
1530
|
+
try {
|
|
1531
|
+
for await (const rawLine of rl) {
|
|
1532
|
+
if (!rawLine.includes("token_count")) continue;
|
|
1533
|
+
try {
|
|
1534
|
+
const line = rawLine.charCodeAt(0) === 65279 ? rawLine.slice(1) : rawLine;
|
|
1535
|
+
const obj = JSON.parse(line);
|
|
1536
|
+
if ((obj?.payload?.type ?? obj?.type) !== "token_count") continue;
|
|
1537
|
+
const info = obj?.payload?.info;
|
|
1538
|
+
if (!normalizeUsage(info?.last_token_usage) && !normalizeUsage(info?.total_token_usage)) continue;
|
|
1539
|
+
const second = timestampSecond(obj.timestamp ?? obj?.payload?.timestamp);
|
|
1540
|
+
if (!second) continue;
|
|
1541
|
+
if (!first) first = second;
|
|
1542
|
+
else return first === second ? second : null;
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
} catch {
|
|
1547
|
+
}
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
function findUsage(obj) {
|
|
1551
|
+
return normalizeUsage(obj?.usage) ?? normalizeUsage(obj?.payload?.usage) ?? normalizeUsage(obj?.payload?.info?.usage) ?? normalizeUsage(obj?.result?.usage) ?? normalizeUsage(obj?.response?.usage) ?? normalizeUsage(obj?.token_usage) ?? normalizeUsage(obj);
|
|
1552
|
+
}
|
|
1553
|
+
function findTimestamp(obj) {
|
|
1554
|
+
return timestampMs2(obj?.timestamp ?? obj?.payload?.timestamp ?? obj?.created_at ?? obj?.createdAt ?? obj?.time);
|
|
1555
|
+
}
|
|
1280
1556
|
async function parseFile2(path) {
|
|
1281
1557
|
const entries = [];
|
|
1282
|
-
let model = "
|
|
1558
|
+
let model = "gpt-5";
|
|
1283
1559
|
let prevTotal = null;
|
|
1284
1560
|
let prevSig = null;
|
|
1561
|
+
const replaySecond = await detectReplaySecond(path);
|
|
1562
|
+
let skipReplay = replaySecond !== null;
|
|
1285
1563
|
const input = createReadStream2(path);
|
|
1286
1564
|
input.on("error", () => {
|
|
1287
1565
|
});
|
|
1288
1566
|
const rl = createInterface2({ input, crlfDelay: Infinity });
|
|
1289
1567
|
try {
|
|
1290
1568
|
for await (const rawLine of rl) {
|
|
1291
|
-
if (!rawLine.includes("token_count") && !rawLine.includes("turn_context")) continue;
|
|
1569
|
+
if (!rawLine.includes("token_count") && !rawLine.includes("turn_context") && !rawLine.includes('"usage"') && !rawLine.includes("input_tokens") && !rawLine.includes("prompt_tokens")) continue;
|
|
1292
1570
|
try {
|
|
1293
1571
|
const line = rawLine.charCodeAt(0) === 65279 ? rawLine.slice(1) : rawLine;
|
|
1294
1572
|
const obj = JSON.parse(line);
|
|
1295
1573
|
const payloadType = obj?.payload?.type ?? obj?.type;
|
|
1296
1574
|
if (payloadType === "turn_context") {
|
|
1297
|
-
const
|
|
1298
|
-
if (typeof
|
|
1575
|
+
const m2 = extractModel(obj);
|
|
1576
|
+
if (typeof m2 === "string" && m2.trim()) model = m2;
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
if (payloadType !== "token_count") {
|
|
1580
|
+
const usage = findUsage(obj);
|
|
1581
|
+
if (!usage) continue;
|
|
1582
|
+
const m2 = extractModel(obj);
|
|
1583
|
+
if (typeof m2 === "string" && m2.trim()) model = m2;
|
|
1584
|
+
const ts2 = findTimestamp(obj);
|
|
1585
|
+
if (!Number.isFinite(ts2)) continue;
|
|
1586
|
+
const inputTotal2 = safeNum(usage.input_tokens);
|
|
1587
|
+
const cached2 = Math.min(safeNum(usage.cached_input_tokens), inputTotal2);
|
|
1588
|
+
const inputTokens2 = inputTotal2 - cached2;
|
|
1589
|
+
const output2 = safeNum(usage.output_tokens);
|
|
1590
|
+
if (inputTokens2 + output2 + cached2 === 0) continue;
|
|
1591
|
+
const p2 = priceFor2(model);
|
|
1592
|
+
entries.push({
|
|
1593
|
+
id: `${ts2}|${model}|${inputTotal2}|${cached2}|${output2}|${safeNum(usage.reasoning_output_tokens)}|${safeNum(usage.total_tokens)}`,
|
|
1594
|
+
ts: ts2,
|
|
1595
|
+
model,
|
|
1596
|
+
cost: inputTokens2 * p2.in + cached2 * p2.cr + output2 * p2.out,
|
|
1597
|
+
input: inputTokens2,
|
|
1598
|
+
output: output2,
|
|
1599
|
+
cacheCreate: 0,
|
|
1600
|
+
cacheRead: cached2,
|
|
1601
|
+
cacheSavings: cached2 * (p2.in - p2.cr)
|
|
1602
|
+
});
|
|
1299
1603
|
continue;
|
|
1300
1604
|
}
|
|
1301
|
-
if (payloadType !== "token_count") continue;
|
|
1302
1605
|
const info = obj?.payload?.info;
|
|
1303
|
-
const total = info?.total_token_usage;
|
|
1304
|
-
const last = info?.last_token_usage;
|
|
1606
|
+
const total = normalizeUsage(info?.total_token_usage);
|
|
1607
|
+
const last = normalizeUsage(info?.last_token_usage);
|
|
1608
|
+
const tsValue = obj.timestamp ?? obj?.payload?.timestamp;
|
|
1609
|
+
if (skipReplay && replaySecond) {
|
|
1610
|
+
const second = timestampSecond(tsValue);
|
|
1611
|
+
if (second === replaySecond) {
|
|
1612
|
+
if (total) prevTotal = total;
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
if (second) skipReplay = false;
|
|
1616
|
+
}
|
|
1305
1617
|
const sig = eventSig(last, total);
|
|
1306
1618
|
if (sig === prevSig) continue;
|
|
1307
1619
|
prevSig = sig;
|
|
@@ -1312,8 +1624,10 @@ async function parseFile2(path) {
|
|
|
1312
1624
|
}
|
|
1313
1625
|
if (total) prevTotal = total;
|
|
1314
1626
|
if (!d) continue;
|
|
1315
|
-
const ts =
|
|
1627
|
+
const ts = timestampMs2(tsValue);
|
|
1316
1628
|
if (!Number.isFinite(ts)) continue;
|
|
1629
|
+
const m = extractModel(obj);
|
|
1630
|
+
if (typeof m === "string" && m.trim()) model = m;
|
|
1317
1631
|
const inputTotal = safeNum(d.input_tokens);
|
|
1318
1632
|
const cached = Math.min(safeNum(d.cached_input_tokens), inputTotal);
|
|
1319
1633
|
const inputTokens = inputTotal - cached;
|
|
@@ -1321,6 +1635,7 @@ async function parseFile2(path) {
|
|
|
1321
1635
|
if (inputTokens + output + cached === 0) continue;
|
|
1322
1636
|
const p = priceFor2(model);
|
|
1323
1637
|
entries.push({
|
|
1638
|
+
id: `${ts}|${model}|${inputTotal}|${cached}|${output}|${safeNum(d.reasoning_output_tokens)}|${safeNum(d.total_tokens)}`,
|
|
1324
1639
|
ts,
|
|
1325
1640
|
model,
|
|
1326
1641
|
cost: inputTokens * p.in + cached * p.cr + output * p.out,
|
|
@@ -1342,28 +1657,28 @@ async function loadEntries2(since, homeDir) {
|
|
|
1342
1657
|
const seen = /* @__PURE__ */ new Set();
|
|
1343
1658
|
const seenIno = /* @__PURE__ */ new Set();
|
|
1344
1659
|
for (const home of codexHomes(homeDir)) {
|
|
1345
|
-
const dir
|
|
1346
|
-
|
|
1347
|
-
try {
|
|
1348
|
-
listing = await readdir2(dir, { recursive: true });
|
|
1349
|
-
} catch {
|
|
1350
|
-
continue;
|
|
1351
|
-
}
|
|
1352
|
-
for (const f of listing) {
|
|
1353
|
-
if (!f.endsWith(".jsonl") || !f.includes("rollout-")) continue;
|
|
1354
|
-
const path = join7(dir, f);
|
|
1355
|
-
if (seen.has(path)) continue;
|
|
1356
|
-
seen.add(path);
|
|
1660
|
+
for (const dir of [join8(home, "sessions"), join8(home, "archived_sessions")]) {
|
|
1661
|
+
let listing;
|
|
1357
1662
|
try {
|
|
1358
|
-
|
|
1359
|
-
if (s.mtimeMs < since) continue;
|
|
1360
|
-
if (s.ino && process.platform !== "win32") {
|
|
1361
|
-
const idn = `${s.dev}:${s.ino}`;
|
|
1362
|
-
if (seenIno.has(idn)) continue;
|
|
1363
|
-
seenIno.add(idn);
|
|
1364
|
-
}
|
|
1365
|
-
files.push({ path, mtimeMs: s.mtimeMs, size: s.size });
|
|
1663
|
+
listing = await readdir2(dir, { recursive: true });
|
|
1366
1664
|
} catch {
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
for (const f of listing) {
|
|
1668
|
+
if (!f.endsWith(".jsonl")) continue;
|
|
1669
|
+
const path = join8(dir, f);
|
|
1670
|
+
if (seen.has(path)) continue;
|
|
1671
|
+
seen.add(path);
|
|
1672
|
+
try {
|
|
1673
|
+
const s = await fsStat2(path);
|
|
1674
|
+
if (s.ino && process.platform !== "win32") {
|
|
1675
|
+
const idn = `${s.dev}:${s.ino}`;
|
|
1676
|
+
if (seenIno.has(idn)) continue;
|
|
1677
|
+
seenIno.add(idn);
|
|
1678
|
+
}
|
|
1679
|
+
files.push({ path, mtimeMs: s.mtimeMs, size: s.size });
|
|
1680
|
+
} catch {
|
|
1681
|
+
}
|
|
1367
1682
|
}
|
|
1368
1683
|
}
|
|
1369
1684
|
}
|
|
@@ -1382,18 +1697,10 @@ async function codexTable(tz, homeDir) {
|
|
|
1382
1697
|
import { readFile as readFile3, readdir as readdir3, stat as fsStat3 } from "fs/promises";
|
|
1383
1698
|
import { createReadStream as createReadStream3 } from "fs";
|
|
1384
1699
|
import { createInterface as createInterface3 } from "readline";
|
|
1385
|
-
import { join as
|
|
1700
|
+
import { join as join9 } from "path";
|
|
1386
1701
|
var USAGE_URL2 = "https://chatgpt.com/backend-api/wham/usage";
|
|
1702
|
+
var RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
1387
1703
|
var CREDIT_USD_RATE = 0.04;
|
|
1388
|
-
function decodeBase64UrlJson(segment) {
|
|
1389
|
-
try {
|
|
1390
|
-
const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
1391
|
-
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
|
|
1392
|
-
return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
1393
|
-
} catch {
|
|
1394
|
-
return null;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
1704
|
function chatGptPlanLabel(planType) {
|
|
1398
1705
|
if (typeof planType !== "string" || !planType.trim()) return null;
|
|
1399
1706
|
const p = planType.trim().toLowerCase();
|
|
@@ -1408,28 +1715,19 @@ function chatGptPlanLabel(planType) {
|
|
|
1408
1715
|
if (labels[p]) return labels[p];
|
|
1409
1716
|
return `ChatGPT ${p.charAt(0).toUpperCase()}${p.slice(1)}`;
|
|
1410
1717
|
}
|
|
1411
|
-
function
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
if (!payload || typeof payload !== "object") return {};
|
|
1415
|
-
const email = typeof payload.email === "string" && payload.email.trim() ? payload.email.trim() : void 0;
|
|
1416
|
-
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 {};
|
|
1417
1721
|
const plan = chatGptPlanLabel(payload["https://api.openai.com/auth"]?.chatgpt_plan_type);
|
|
1418
1722
|
return { email, displayName, plan: plan ?? void 0 };
|
|
1419
1723
|
}
|
|
1420
|
-
function identityFields3(auth) {
|
|
1421
|
-
return {
|
|
1422
|
-
email: auth?.email ?? null,
|
|
1423
|
-
displayName: auth?.displayName ?? null
|
|
1424
|
-
};
|
|
1425
|
-
}
|
|
1426
1724
|
async function readAuthFile(home) {
|
|
1427
1725
|
try {
|
|
1428
|
-
const raw = await readFile3(
|
|
1726
|
+
const raw = await readFile3(join9(home, "auth.json"), "utf-8");
|
|
1429
1727
|
const auth = JSON.parse(raw);
|
|
1430
1728
|
const accessToken = auth?.tokens?.access_token;
|
|
1431
1729
|
if (!accessToken) return null;
|
|
1432
|
-
return { accessToken, accountId: auth?.tokens?.account_id, ...
|
|
1730
|
+
return { accessToken, accountId: auth?.tokens?.account_id, ...codexIdentity(auth?.tokens?.id_token) };
|
|
1433
1731
|
} catch {
|
|
1434
1732
|
return null;
|
|
1435
1733
|
}
|
|
@@ -1441,7 +1739,7 @@ async function readKeychainAuth() {
|
|
|
1441
1739
|
const auth = JSON.parse(raw);
|
|
1442
1740
|
const accessToken = auth?.tokens?.access_token;
|
|
1443
1741
|
if (!accessToken) return null;
|
|
1444
|
-
return { accessToken, accountId: auth?.tokens?.account_id, ...
|
|
1742
|
+
return { accessToken, accountId: auth?.tokens?.account_id, ...codexIdentity(auth?.tokens?.id_token) };
|
|
1445
1743
|
} catch {
|
|
1446
1744
|
return null;
|
|
1447
1745
|
}
|
|
@@ -1451,7 +1749,7 @@ async function getAuth2(homeDir) {
|
|
|
1451
1749
|
const auth = await readAuthFile(home);
|
|
1452
1750
|
if (auth) return auth;
|
|
1453
1751
|
}
|
|
1454
|
-
if (process.platform === "darwin") return readKeychainAuth();
|
|
1752
|
+
if (!homeDir && process.platform === "darwin") return readKeychainAuth();
|
|
1455
1753
|
return null;
|
|
1456
1754
|
}
|
|
1457
1755
|
function planLabel2(planType) {
|
|
@@ -1461,14 +1759,93 @@ function planLabel2(planType) {
|
|
|
1461
1759
|
if (p === "pro") return "Pro 20x";
|
|
1462
1760
|
return planType.charAt(0).toUpperCase() + planType.slice(1);
|
|
1463
1761
|
}
|
|
1464
|
-
function
|
|
1762
|
+
function resetDateMs(window) {
|
|
1465
1763
|
if (!window) return null;
|
|
1466
|
-
|
|
1467
|
-
if (
|
|
1468
|
-
|
|
1469
|
-
|
|
1764
|
+
const absolute = numberValue(window.reset_at ?? window.resets_at);
|
|
1765
|
+
if (absolute !== void 0) return absolute > 1e10 ? absolute : absolute * 1e3;
|
|
1766
|
+
const after = numberValue(window.reset_after_seconds);
|
|
1767
|
+
if (after !== void 0) return Date.now() + after * 1e3;
|
|
1768
|
+
for (const key of ["reset_at", "resets_at"]) {
|
|
1769
|
+
const raw = window[key];
|
|
1770
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
1771
|
+
const parsed = new Date(raw).getTime();
|
|
1772
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
function resetFrom2(window) {
|
|
1778
|
+
const ms = resetDateMs(window);
|
|
1779
|
+
const iso = ms === null ? null : msToIso(ms);
|
|
1470
1780
|
return iso ? resetIn(iso) : null;
|
|
1471
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
|
+
}
|
|
1788
|
+
function normalizedUsedPercent(window, percent) {
|
|
1789
|
+
const used = numberValue(percent);
|
|
1790
|
+
if (used === void 0) return void 0;
|
|
1791
|
+
const periodSeconds = windowSeconds(window);
|
|
1792
|
+
const resetMs = resetDateMs(window);
|
|
1793
|
+
if (periodSeconds && resetMs) {
|
|
1794
|
+
const elapsedMs = periodSeconds * 1e3 - Math.max(0, resetMs - Date.now());
|
|
1795
|
+
if (elapsedMs <= 6e4 && used <= 1) return 0;
|
|
1796
|
+
}
|
|
1797
|
+
return Math.max(0, used);
|
|
1798
|
+
}
|
|
1799
|
+
function metricLabelForAdditional(window) {
|
|
1800
|
+
const name = String(window?.limit_name ?? window?.name ?? window?.metered_feature ?? window?.feature ?? "").toLowerCase();
|
|
1801
|
+
if (!name.includes("spark")) return null;
|
|
1802
|
+
const seconds = windowSeconds(window);
|
|
1803
|
+
return name.includes("week") || seconds !== void 0 && seconds >= 6 * 24 * 60 * 60 ? "Spark Weekly" : "Spark";
|
|
1804
|
+
}
|
|
1805
|
+
function additionalRateLimits(rl) {
|
|
1806
|
+
const raw = rl?.additional_rate_limits ?? rl?.additionalRateLimits ?? rl?.additional;
|
|
1807
|
+
if (Array.isArray(raw)) return raw;
|
|
1808
|
+
if (raw && typeof raw === "object") return Object.values(raw);
|
|
1809
|
+
return [];
|
|
1810
|
+
}
|
|
1811
|
+
function appendWindowMetrics(metrics, rl, headerPct) {
|
|
1812
|
+
const primary = rl?.primary_window ?? rl?.primary ?? null;
|
|
1813
|
+
const secondary = rl?.secondary_window ?? rl?.secondary ?? null;
|
|
1814
|
+
const primaryPct = normalizedUsedPercent(primary, primary?.used_percent ?? primary?.percent_used ?? headerPct?.("x-codex-primary-used-percent"));
|
|
1815
|
+
const secondaryPct = normalizedUsedPercent(secondary, secondary?.used_percent ?? secondary?.percent_used ?? headerPct?.("x-codex-secondary-used-percent"));
|
|
1816
|
+
if (primaryPct !== void 0) metrics.push(percentMetric("Session", primaryPct, resetFrom2(primary), true));
|
|
1817
|
+
if (secondaryPct !== void 0) metrics.push(percentMetric("Weekly", secondaryPct, resetFrom2(secondary)));
|
|
1818
|
+
for (const item of additionalRateLimits(rl)) {
|
|
1819
|
+
const label = metricLabelForAdditional(item);
|
|
1820
|
+
if (!label) continue;
|
|
1821
|
+
const used = normalizedUsedPercent(item, item?.used_percent ?? item?.percent_used);
|
|
1822
|
+
if (used === void 0) continue;
|
|
1823
|
+
metrics.push(percentMetric(label, used, resetFrom2(item)));
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
function appendCredits2(metrics, source) {
|
|
1827
|
+
const balance = numberValue(source?.credits?.balance ?? source?.credit_balance);
|
|
1828
|
+
if (balance !== void 0 && balance >= 0) {
|
|
1829
|
+
metrics.push({ label: "Credits", used: balance * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
async function fetchResetCredits(headers) {
|
|
1833
|
+
try {
|
|
1834
|
+
const res = await fetch(RESET_CREDITS_URL, {
|
|
1835
|
+
headers: {
|
|
1836
|
+
...headers,
|
|
1837
|
+
"OpenAI-Beta": "codex-1",
|
|
1838
|
+
"originator": "Codex Desktop"
|
|
1839
|
+
},
|
|
1840
|
+
signal: AbortSignal.timeout(1e4)
|
|
1841
|
+
});
|
|
1842
|
+
if (!res.ok) return void 0;
|
|
1843
|
+
const data = await readJson(res);
|
|
1844
|
+
return numberValue(data?.available_count ?? data?.available ?? data?.remaining);
|
|
1845
|
+
} catch {
|
|
1846
|
+
return void 0;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1472
1849
|
async function liveBilling(auth) {
|
|
1473
1850
|
try {
|
|
1474
1851
|
const headers = {
|
|
@@ -1481,34 +1858,31 @@ async function liveBilling(auth) {
|
|
|
1481
1858
|
if (!res.ok) return null;
|
|
1482
1859
|
const data = await readJson(res);
|
|
1483
1860
|
if (!data) return null;
|
|
1484
|
-
const metrics = [];
|
|
1485
|
-
const rl = data.rate_limit ?? null;
|
|
1486
|
-
const primary = rl?.primary_window ?? null;
|
|
1487
|
-
const secondary = rl?.secondary_window ?? null;
|
|
1488
1861
|
const headerPct = (name) => {
|
|
1489
1862
|
const h = res.headers.get(name);
|
|
1490
1863
|
if (h === null || h.trim() === "") return void 0;
|
|
1491
1864
|
const n = Number(h);
|
|
1492
1865
|
return Number.isFinite(n) ? n : void 0;
|
|
1493
1866
|
};
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
metrics.push({ label: "Credits", used: balance * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
|
|
1867
|
+
const metrics = [];
|
|
1868
|
+
appendWindowMetrics(metrics, data.rate_limit ?? data, headerPct);
|
|
1869
|
+
appendCredits2(metrics, data);
|
|
1870
|
+
const resetCredits = numberValue(data?.rate_limit_reset_credits?.available_count ?? data?.rate_limit_reset_credits?.available) ?? await fetchResetCredits(headers);
|
|
1871
|
+
if (resetCredits !== void 0 && resetCredits >= 0) {
|
|
1872
|
+
metrics.push({ label: "Resets", used: resetCredits, limit: null, format: { kind: "count", suffix: "available" } });
|
|
1501
1873
|
}
|
|
1502
1874
|
if (metrics.length === 0) return null;
|
|
1503
|
-
return { plan: auth.plan ?? planLabel2(data.plan_type), metrics, error: null, ...
|
|
1875
|
+
return { plan: auth.plan ?? planLabel2(data.plan_type), metrics, error: null, ...identityFields(auth) };
|
|
1504
1876
|
} catch {
|
|
1505
1877
|
return null;
|
|
1506
1878
|
}
|
|
1507
1879
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1880
|
+
var SNAPSHOT_CANDIDATES = 8;
|
|
1881
|
+
var SNAPSHOT_STALE_MS = 24 * 36e5;
|
|
1882
|
+
async function newestRolloutFiles(homeDir) {
|
|
1883
|
+
const all = [];
|
|
1510
1884
|
for (const home of codexHomes(homeDir)) {
|
|
1511
|
-
const dir =
|
|
1885
|
+
const dir = join9(home, "sessions");
|
|
1512
1886
|
let listing;
|
|
1513
1887
|
try {
|
|
1514
1888
|
listing = await readdir3(dir, { recursive: true });
|
|
@@ -1517,19 +1891,17 @@ async function newestRolloutFile(homeDir) {
|
|
|
1517
1891
|
}
|
|
1518
1892
|
for (const f of listing) {
|
|
1519
1893
|
if (!f.endsWith(".jsonl") || !f.includes("rollout-")) continue;
|
|
1520
|
-
const path =
|
|
1894
|
+
const path = join9(dir, f);
|
|
1521
1895
|
try {
|
|
1522
1896
|
const s = await fsStat3(path);
|
|
1523
|
-
|
|
1897
|
+
all.push({ path, mtime: s.mtimeMs });
|
|
1524
1898
|
} catch {
|
|
1525
1899
|
}
|
|
1526
1900
|
}
|
|
1527
1901
|
}
|
|
1528
|
-
return
|
|
1902
|
+
return all.sort((a, b) => b.mtime - a.mtime).slice(0, SNAPSHOT_CANDIDATES);
|
|
1529
1903
|
}
|
|
1530
|
-
async function
|
|
1531
|
-
const path = await newestRolloutFile(homeDir);
|
|
1532
|
-
if (!path) return null;
|
|
1904
|
+
async function lastRateLimits(path) {
|
|
1533
1905
|
let last = null;
|
|
1534
1906
|
try {
|
|
1535
1907
|
const input = createReadStream3(path);
|
|
@@ -1547,20 +1919,23 @@ async function snapshotBilling(homeDir, auth = null) {
|
|
|
1547
1919
|
} catch {
|
|
1548
1920
|
return null;
|
|
1549
1921
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
metrics
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
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) };
|
|
1561
1937
|
}
|
|
1562
|
-
|
|
1563
|
-
return { plan: auth?.plan ?? planLabel2(last.plan_type), metrics, error: null, ...identityFields3(auth) };
|
|
1938
|
+
return null;
|
|
1564
1939
|
}
|
|
1565
1940
|
async function codexBilling(account) {
|
|
1566
1941
|
const auth = await getAuth2(account.homeDir);
|
|
@@ -1569,12 +1944,15 @@ async function codexBilling(account) {
|
|
|
1569
1944
|
if (live) return live;
|
|
1570
1945
|
}
|
|
1571
1946
|
const snap = await snapshotBilling(account.homeDir, auth);
|
|
1572
|
-
if (snap)
|
|
1947
|
+
if (snap && Date.now() - snap.asOfMs < SNAPSHOT_STALE_MS) {
|
|
1948
|
+
const { asOfMs: _asOfMs, ...result } = snap;
|
|
1949
|
+
return result;
|
|
1950
|
+
}
|
|
1573
1951
|
return {
|
|
1574
|
-
plan: auth?.plan ?? null,
|
|
1952
|
+
plan: auth?.plan ?? snap?.plan ?? null,
|
|
1575
1953
|
metrics: [],
|
|
1576
1954
|
error: auth ? "Usage API failed \u2014 run codex to refresh" : "Not logged in \u2014 run codex",
|
|
1577
|
-
...
|
|
1955
|
+
...identityFields(auth)
|
|
1578
1956
|
};
|
|
1579
1957
|
}
|
|
1580
1958
|
|
|
@@ -1751,17 +2129,17 @@ var cursorProvider = {
|
|
|
1751
2129
|
hasBilling: true,
|
|
1752
2130
|
detect: (homeDir) => detectCursor(homeDir),
|
|
1753
2131
|
fetchTable: (account, tz) => cursorTable(tz, account.homeDir),
|
|
1754
|
-
fetchBilling: (account) => cursorBilling(account)
|
|
2132
|
+
fetchBilling: (account, tz) => cursorBilling(account, tz)
|
|
1755
2133
|
};
|
|
1756
2134
|
|
|
1757
2135
|
// src/providers/pi/usage.ts
|
|
1758
2136
|
import { readdir as readdir4, stat as fsStat4, access as access4 } from "fs/promises";
|
|
1759
2137
|
import { createReadStream as createReadStream4 } from "fs";
|
|
1760
2138
|
import { createInterface as createInterface4 } from "readline";
|
|
1761
|
-
import { join as
|
|
1762
|
-
import { homedir as
|
|
2139
|
+
import { join as join10 } from "path";
|
|
2140
|
+
import { homedir as homedir7 } from "os";
|
|
1763
2141
|
function piSessionsDir(homeDir) {
|
|
1764
|
-
return
|
|
2142
|
+
return join10(homeDir ?? homedir7(), ".pi", "agent", "sessions");
|
|
1765
2143
|
}
|
|
1766
2144
|
async function detectPi(homeDir) {
|
|
1767
2145
|
try {
|
|
@@ -1771,6 +2149,11 @@ async function detectPi(homeDir) {
|
|
|
1771
2149
|
return false;
|
|
1772
2150
|
}
|
|
1773
2151
|
}
|
|
2152
|
+
function timestampMs3(value) {
|
|
2153
|
+
if (typeof value === "number" && Number.isFinite(value)) return value > 1e10 ? value : value * 1e3;
|
|
2154
|
+
if (typeof value === "string" && value.trim()) return new Date(value.trim()).getTime();
|
|
2155
|
+
return NaN;
|
|
2156
|
+
}
|
|
1774
2157
|
async function parseFile3(path) {
|
|
1775
2158
|
const entries = [];
|
|
1776
2159
|
const rl = createInterface4({ input: createReadStream4(path), crlfDelay: Infinity });
|
|
@@ -1783,7 +2166,7 @@ async function parseFile3(path) {
|
|
|
1783
2166
|
const msg = obj.message;
|
|
1784
2167
|
if (msg?.role !== "assistant" || !msg?.usage) continue;
|
|
1785
2168
|
const u = msg.usage;
|
|
1786
|
-
const ts =
|
|
2169
|
+
const ts = timestampMs3(obj.timestamp ?? msg.timestamp);
|
|
1787
2170
|
if (!Number.isFinite(ts)) continue;
|
|
1788
2171
|
const input = safeNum(u.input);
|
|
1789
2172
|
const output = safeNum(u.output);
|
|
@@ -1821,7 +2204,7 @@ async function loadEntries3(since, homeDir) {
|
|
|
1821
2204
|
}
|
|
1822
2205
|
for (const f of listing) {
|
|
1823
2206
|
if (!f.endsWith(".jsonl")) continue;
|
|
1824
|
-
const path =
|
|
2207
|
+
const path = join10(dir, f);
|
|
1825
2208
|
try {
|
|
1826
2209
|
const s = await fsStat4(path);
|
|
1827
2210
|
if (s.mtimeMs < since) continue;
|
|
@@ -1857,17 +2240,17 @@ var piProvider = {
|
|
|
1857
2240
|
|
|
1858
2241
|
// src/providers/opencode/usage.ts
|
|
1859
2242
|
import { access as access5 } from "fs/promises";
|
|
1860
|
-
import { join as
|
|
1861
|
-
import { homedir as
|
|
2243
|
+
import { join as join11 } from "path";
|
|
2244
|
+
import { homedir as homedir8 } from "os";
|
|
1862
2245
|
function opencodeDbPaths(homeDir) {
|
|
1863
|
-
const base = homeDir ??
|
|
2246
|
+
const base = homeDir ?? homedir8();
|
|
1864
2247
|
const paths = [];
|
|
1865
|
-
if (!homeDir && process.env.XDG_DATA_HOME) paths.push(
|
|
1866
|
-
paths.push(
|
|
1867
|
-
if (process.platform === "darwin") paths.push(
|
|
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"));
|
|
1868
2251
|
if (process.platform === "win32") {
|
|
1869
|
-
const lad = homeDir ?
|
|
1870
|
-
if (lad) paths.push(
|
|
2252
|
+
const lad = homeDir ? join11(homeDir, "AppData", "Local") : process.env.LOCALAPPDATA;
|
|
2253
|
+
if (lad) paths.push(join11(lad, "opencode", "opencode.db"));
|
|
1871
2254
|
}
|
|
1872
2255
|
return [...new Set(paths)];
|
|
1873
2256
|
}
|
|
@@ -1887,7 +2270,7 @@ async function detectOpencode(homeDir) {
|
|
|
1887
2270
|
async function loadEntries4(since, homeDir) {
|
|
1888
2271
|
const db = await findDb(homeDir);
|
|
1889
2272
|
if (!db) return [];
|
|
1890
|
-
const sql = "SELECT time_created AS ts, json_extract(data,'$.modelID') AS model, json_extract(data,'$.cost') AS cost, json_extract(data,'$.tokens.input') AS input, json_extract(data,'$.tokens.output') AS output, json_extract(data,'$.tokens.reasoning') AS reasoning, json_extract(data,'$.tokens.cache.read') AS cacheRead, json_extract(data,'$.tokens.cache.write') AS cacheWrite FROM message WHERE json_valid(data) AND json_extract(data,'$.role')='assistant' AND json_type(data,'$.tokens')='object' AND time_created >= ?;";
|
|
2273
|
+
const sql = "SELECT CASE WHEN time_created < 10000000000 THEN time_created * 1000 ELSE time_created END AS ts, json_extract(data,'$.modelID') AS model, json_extract(data,'$.cost') AS cost, json_extract(data,'$.tokens.input') AS input, json_extract(data,'$.tokens.output') AS output, json_extract(data,'$.tokens.reasoning') AS reasoning, json_extract(data,'$.tokens.cache.read') AS cacheRead, json_extract(data,'$.tokens.cache.write') AS cacheWrite FROM message WHERE json_valid(data) AND json_extract(data,'$.role')='assistant' AND json_type(data,'$.tokens')='object' AND (CASE WHEN time_created < 10000000000 THEN time_created * 1000 ELSE time_created END) >= ?;";
|
|
1891
2274
|
const res = await runSqlite(db, sql, [Math.floor(since)]);
|
|
1892
2275
|
if (res.status !== "ok") return [];
|
|
1893
2276
|
const entries = [];
|
|
@@ -1895,7 +2278,7 @@ async function loadEntries4(since, homeDir) {
|
|
|
1895
2278
|
const ts = finitePositive(row.ts);
|
|
1896
2279
|
if (!ts) continue;
|
|
1897
2280
|
const input = finitePositive(row.input);
|
|
1898
|
-
const output = finitePositive(row.output);
|
|
2281
|
+
const output = finitePositive(row.output) + finitePositive(row.reasoning);
|
|
1899
2282
|
const cacheRead = finitePositive(row.cacheRead);
|
|
1900
2283
|
const cacheCreate = finitePositive(row.cacheWrite);
|
|
1901
2284
|
if (input + output + cacheRead + cacheCreate === 0) continue;
|
|
@@ -1935,107 +2318,6 @@ var opencodeProvider = {
|
|
|
1935
2318
|
import { access as access6, readFile as readFile4, readdir as readdir5 } from "fs/promises";
|
|
1936
2319
|
import { join as join12 } from "path";
|
|
1937
2320
|
import { homedir as homedir9 } from "os";
|
|
1938
|
-
|
|
1939
|
-
// src/providers/detect.ts
|
|
1940
|
-
import { accessSync as accessSync2, constants as constants2, existsSync } from "fs";
|
|
1941
|
-
import { join as join11, delimiter as delimiter2, isAbsolute as isAbsolute2 } from "path";
|
|
1942
|
-
import { homedir as homedir8 } from "os";
|
|
1943
|
-
function searchDirs() {
|
|
1944
|
-
const home = homedir8();
|
|
1945
|
-
const fromEnv = (process.env.PATH ?? "").split(delimiter2).filter(Boolean);
|
|
1946
|
-
const extra = process.platform === "win32" ? [
|
|
1947
|
-
process.env.APPDATA && join11(process.env.APPDATA, "npm"),
|
|
1948
|
-
process.env.LOCALAPPDATA && join11(process.env.LOCALAPPDATA, "pnpm"),
|
|
1949
|
-
join11(home, "scoop", "shims")
|
|
1950
|
-
] : [
|
|
1951
|
-
"/opt/homebrew/bin",
|
|
1952
|
-
"/usr/local/bin",
|
|
1953
|
-
"/usr/bin",
|
|
1954
|
-
"/bin",
|
|
1955
|
-
"/opt/local/bin",
|
|
1956
|
-
join11(home, ".local", "bin"),
|
|
1957
|
-
join11(home, "bin"),
|
|
1958
|
-
join11(home, ".npm-global", "bin"),
|
|
1959
|
-
join11(home, ".bun", "bin"),
|
|
1960
|
-
join11(home, ".local", "share", "pnpm")
|
|
1961
|
-
];
|
|
1962
|
-
return [.../* @__PURE__ */ new Set([...fromEnv, ...extra.filter((d) => !!d)])];
|
|
1963
|
-
}
|
|
1964
|
-
function isExec2(p) {
|
|
1965
|
-
try {
|
|
1966
|
-
accessSync2(p, process.platform === "win32" ? constants2.F_OK : constants2.X_OK);
|
|
1967
|
-
return true;
|
|
1968
|
-
} catch {
|
|
1969
|
-
return false;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
function onPath(names) {
|
|
1973
|
-
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").map((e) => e.toLowerCase()).concat("") : [""];
|
|
1974
|
-
for (const dir of searchDirs()) {
|
|
1975
|
-
for (const n of names) {
|
|
1976
|
-
for (const e of exts) {
|
|
1977
|
-
if (isExec2(join11(dir, n + e))) return true;
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
return false;
|
|
1982
|
-
}
|
|
1983
|
-
function anyExists(paths) {
|
|
1984
|
-
return paths.some((p) => !!p && isExec2(p));
|
|
1985
|
-
}
|
|
1986
|
-
function installSignals(id) {
|
|
1987
|
-
const home = homedir8();
|
|
1988
|
-
const pf = process.env.ProgramFiles;
|
|
1989
|
-
const pf86 = process.env["ProgramFiles(x86)"];
|
|
1990
|
-
const lad = process.env.LOCALAPPDATA;
|
|
1991
|
-
switch (id) {
|
|
1992
|
-
case "claude":
|
|
1993
|
-
return onPath(["claude"]) || anyExists([
|
|
1994
|
-
"/Applications/Claude.app",
|
|
1995
|
-
join11(home, "Applications", "Claude.app"),
|
|
1996
|
-
lad && join11(lad, "Programs", "claude", "Claude.exe")
|
|
1997
|
-
]);
|
|
1998
|
-
case "codex": {
|
|
1999
|
-
const bin = process.env.CODEX_BIN;
|
|
2000
|
-
if (bin && isAbsolute2(bin) && isExec2(bin)) return true;
|
|
2001
|
-
return onPath(["codex"]) || anyExists([
|
|
2002
|
-
lad && join11(lad, "Programs", "OpenAI", "Codex", "bin", "codex.exe"),
|
|
2003
|
-
lad && join11(lad, "Programs", "OpenAI", "Codex", "codex.exe"),
|
|
2004
|
-
lad && join11(lad, "Programs", "codex", "codex.exe"),
|
|
2005
|
-
pf && join11(pf, "OpenAI", "Codex", "bin", "codex.exe")
|
|
2006
|
-
]) || existsSync(join11(home, ".codex", "sessions")) || existsSync(join11(home, ".codex", "auth.json"));
|
|
2007
|
-
}
|
|
2008
|
-
case "cursor":
|
|
2009
|
-
return onPath(["cursor", "cursor-agent"]) || anyExists([
|
|
2010
|
-
"/Applications/Cursor.app",
|
|
2011
|
-
join11(home, "Applications", "Cursor.app"),
|
|
2012
|
-
lad && join11(lad, "Programs", "cursor", "Cursor.exe"),
|
|
2013
|
-
pf && join11(pf, "Cursor", "Cursor.exe"),
|
|
2014
|
-
pf86 && join11(pf86, "Cursor", "Cursor.exe"),
|
|
2015
|
-
"/opt/Cursor/cursor",
|
|
2016
|
-
"/usr/share/cursor/cursor",
|
|
2017
|
-
"/usr/bin/cursor"
|
|
2018
|
-
]);
|
|
2019
|
-
case "pi":
|
|
2020
|
-
return onPath(["pi"]);
|
|
2021
|
-
case "opencode":
|
|
2022
|
-
return onPath(["opencode"]);
|
|
2023
|
-
case "copilot":
|
|
2024
|
-
return onPath(["gh"]);
|
|
2025
|
-
case "antigravity":
|
|
2026
|
-
return onPath(["antigravity"]) || anyExists([
|
|
2027
|
-
"/Applications/Antigravity.app",
|
|
2028
|
-
join11(home, "Applications", "Antigravity.app"),
|
|
2029
|
-
lad && join11(lad, "Programs", "Antigravity", "Antigravity.exe")
|
|
2030
|
-
]);
|
|
2031
|
-
case "gemini":
|
|
2032
|
-
return onPath(["gemini"]);
|
|
2033
|
-
default:
|
|
2034
|
-
return false;
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
// src/providers/copilot/billing.ts
|
|
2039
2321
|
var USAGE_URL3 = "https://api.github.com/copilot_internal/user";
|
|
2040
2322
|
var GH_KEYCHAIN_SERVICE = "gh:github.com";
|
|
2041
2323
|
function ghConfigDir(homeDir) {
|
|
@@ -2054,12 +2336,24 @@ function ghHostsPath(homeDir) {
|
|
|
2054
2336
|
return join12(ghConfigDir(homeDir), "hosts.yml");
|
|
2055
2337
|
}
|
|
2056
2338
|
async function detectCopilot(homeDir) {
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
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"));
|
|
2061
2348
|
}
|
|
2062
|
-
|
|
2349
|
+
for (const path of candidates) {
|
|
2350
|
+
try {
|
|
2351
|
+
await access6(path);
|
|
2352
|
+
return true;
|
|
2353
|
+
} catch {
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
return false;
|
|
2063
2357
|
}
|
|
2064
2358
|
function unquoteYamlValue(value) {
|
|
2065
2359
|
const trimmed = value.trim();
|
|
@@ -2148,7 +2442,7 @@ async function loadTokenFromVsCode(homeDir) {
|
|
|
2148
2442
|
return null;
|
|
2149
2443
|
}
|
|
2150
2444
|
async function loadToken(homeDir) {
|
|
2151
|
-
return await loadTokenFromHosts(homeDir) || await loadTokenFromGhKeychain() || await loadTokenFromVsCode(homeDir);
|
|
2445
|
+
return await loadTokenFromHosts(homeDir) || (homeDir ? null : await loadTokenFromGhKeychain()) || await loadTokenFromVsCode(homeDir);
|
|
2152
2446
|
}
|
|
2153
2447
|
function redactToken(token) {
|
|
2154
2448
|
return token.length <= 4 ? "****" : `****${token.slice(-4)}`;
|
|
@@ -2156,13 +2450,27 @@ function redactToken(token) {
|
|
|
2156
2450
|
function resetDate(value) {
|
|
2157
2451
|
return typeof value === "string" && value.trim() ? resetIn(value) : null;
|
|
2158
2452
|
}
|
|
2453
|
+
function boolValue2(value) {
|
|
2454
|
+
if (typeof value === "boolean") return value;
|
|
2455
|
+
if (typeof value === "number") return value !== 0;
|
|
2456
|
+
if (typeof value === "string") {
|
|
2457
|
+
const s = value.trim().toLowerCase();
|
|
2458
|
+
if (s === "true" || s === "1") return true;
|
|
2459
|
+
if (s === "false" || s === "0") return false;
|
|
2460
|
+
}
|
|
2461
|
+
return void 0;
|
|
2462
|
+
}
|
|
2159
2463
|
function percentMetric2(label, snapshot, reset, primary) {
|
|
2160
|
-
if (!snapshot
|
|
2161
|
-
|
|
2162
|
-
const
|
|
2464
|
+
if (!snapshot) return null;
|
|
2465
|
+
const entitlement = numberValue(snapshot.entitlement);
|
|
2466
|
+
const remaining = numberValue(snapshot.remaining);
|
|
2467
|
+
if (boolValue2(snapshot.unlimited) === true || entitlement === -1 || remaining === -1 || entitlement === 0) return null;
|
|
2468
|
+
const percentRemaining = numberValue(snapshot.percent_remaining);
|
|
2469
|
+
const used = percentRemaining !== void 0 ? 100 - percentRemaining : entitlement !== void 0 && entitlement > 0 && remaining !== void 0 ? 100 - remaining / entitlement * 100 : void 0;
|
|
2470
|
+
if (used === void 0) return null;
|
|
2163
2471
|
return {
|
|
2164
2472
|
label,
|
|
2165
|
-
used,
|
|
2473
|
+
used: Math.min(100, Math.max(0, used)),
|
|
2166
2474
|
limit: 100,
|
|
2167
2475
|
format: { kind: "percent" },
|
|
2168
2476
|
resetsAt: reset,
|
|
@@ -2170,15 +2478,26 @@ function percentMetric2(label, snapshot, reset, primary) {
|
|
|
2170
2478
|
};
|
|
2171
2479
|
}
|
|
2172
2480
|
function countMetric(label, remaining, total, reset) {
|
|
2173
|
-
|
|
2481
|
+
const remainingCount = numberValue(remaining);
|
|
2482
|
+
const totalCount = numberValue(total);
|
|
2483
|
+
if (remainingCount === void 0 || totalCount === void 0 || totalCount <= 0) return null;
|
|
2174
2484
|
return {
|
|
2175
2485
|
label,
|
|
2176
|
-
used: Math.max(0,
|
|
2177
|
-
limit:
|
|
2486
|
+
used: Math.max(0, totalCount - remainingCount),
|
|
2487
|
+
limit: totalCount,
|
|
2178
2488
|
format: { kind: "count" },
|
|
2179
2489
|
resetsAt: reset
|
|
2180
2490
|
};
|
|
2181
2491
|
}
|
|
2492
|
+
function overageMetric(snapshot) {
|
|
2493
|
+
if (!snapshot || boolValue2(snapshot.overage_permitted) !== true) return null;
|
|
2494
|
+
return {
|
|
2495
|
+
label: "Extra",
|
|
2496
|
+
used: Math.max(0, numberValue(snapshot.overage_count) ?? 0),
|
|
2497
|
+
limit: null,
|
|
2498
|
+
format: { kind: "count" }
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2182
2501
|
async function fetchUsage(token) {
|
|
2183
2502
|
try {
|
|
2184
2503
|
const res = await fetch(USAGE_URL3, {
|
|
@@ -2213,18 +2532,24 @@ async function copilotBilling(account) {
|
|
|
2213
2532
|
const metrics = [];
|
|
2214
2533
|
const quotaReset = resetDate(data.quota_reset_date);
|
|
2215
2534
|
const snapshots = data.quota_snapshots;
|
|
2216
|
-
const premium = percentMetric2("
|
|
2535
|
+
const premium = percentMetric2("Credits", snapshots?.premium_interactions, quotaReset, true);
|
|
2217
2536
|
if (premium) metrics.push(premium);
|
|
2537
|
+
const overage = overageMetric(snapshots?.premium_interactions);
|
|
2538
|
+
if (overage) metrics.push(overage);
|
|
2218
2539
|
const chat = percentMetric2("Chat", snapshots?.chat, quotaReset);
|
|
2219
2540
|
if (chat) metrics.push(chat);
|
|
2220
|
-
|
|
2541
|
+
const completionsSnapshot = percentMetric2("Completions", snapshots?.completions, quotaReset);
|
|
2542
|
+
if (completionsSnapshot) metrics.push(completionsSnapshot);
|
|
2543
|
+
if (metrics.length === 0 && data.limited_user_quotas && data.monthly_quotas) {
|
|
2221
2544
|
const reset = resetDate(data.limited_user_reset_date);
|
|
2222
2545
|
const limitedChat = countMetric("Chat", data.limited_user_quotas.chat, data.monthly_quotas.chat, reset);
|
|
2223
2546
|
if (limitedChat) metrics.push(limitedChat);
|
|
2224
2547
|
const completions = countMetric("Completions", data.limited_user_quotas.completions, data.monthly_quotas.completions, reset);
|
|
2225
2548
|
if (completions) metrics.push(completions);
|
|
2226
2549
|
}
|
|
2227
|
-
if (metrics.length === 0)
|
|
2550
|
+
if (metrics.length === 0) {
|
|
2551
|
+
return { plan, metrics: [], error: boolValue2(data.token_based_billing) === true ? null : "No usage data" };
|
|
2552
|
+
}
|
|
2228
2553
|
return { plan, metrics, error: null };
|
|
2229
2554
|
}
|
|
2230
2555
|
|
|
@@ -2244,35 +2569,17 @@ import { access as access7, readdir as readdir7 } from "fs/promises";
|
|
|
2244
2569
|
import { join as join14 } from "path";
|
|
2245
2570
|
import { homedir as homedir11 } from "os";
|
|
2246
2571
|
|
|
2247
|
-
// src/providers/cloud-code.ts
|
|
2572
|
+
// src/providers/cloud-code/auth.ts
|
|
2248
2573
|
import { readFile as readFile5, readdir as readdir6, realpath, stat } from "fs/promises";
|
|
2249
2574
|
import { spawnSync } from "child_process";
|
|
2250
2575
|
import { homedir as homedir10 } from "os";
|
|
2251
2576
|
import { dirname, join as join13 } from "path";
|
|
2252
|
-
var CLOUD_CODE_URLS = [
|
|
2253
|
-
"https://daily-cloudcode-pa.googleapis.com",
|
|
2254
|
-
"https://cloudcode-pa.googleapis.com"
|
|
2255
|
-
];
|
|
2256
|
-
var LOAD_CODE_ASSIST_PATH = "/v1internal:loadCodeAssist";
|
|
2257
|
-
var FETCH_MODELS_PATH = "/v1internal:fetchAvailableModels";
|
|
2258
|
-
var RETRIEVE_QUOTA_PATH = "/v1internal:retrieveUserQuota";
|
|
2259
2577
|
var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
|
|
2260
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;
|
|
2261
2579
|
var MAX_BUNDLE_READ = 32 * 1024 * 1024;
|
|
2262
2580
|
var cachedClient;
|
|
2263
2581
|
var OAUTH_TOKEN_KEY = "antigravityUnifiedStateSync.oauthToken";
|
|
2264
2582
|
var OAUTH_TOKEN_SENTINEL = "oauthTokenInfoSentinelKey";
|
|
2265
|
-
var CC_MODEL_BLACKLIST = {
|
|
2266
|
-
MODEL_CHAT_20706: true,
|
|
2267
|
-
MODEL_CHAT_23310: true,
|
|
2268
|
-
MODEL_GOOGLE_GEMINI_2_5_FLASH: true,
|
|
2269
|
-
MODEL_GOOGLE_GEMINI_2_5_FLASH_THINKING: true,
|
|
2270
|
-
MODEL_GOOGLE_GEMINI_2_5_FLASH_LITE: true,
|
|
2271
|
-
MODEL_GOOGLE_GEMINI_2_5_PRO: true,
|
|
2272
|
-
MODEL_PLACEHOLDER_M19: true,
|
|
2273
|
-
MODEL_PLACEHOLDER_M9: true,
|
|
2274
|
-
MODEL_PLACEHOLDER_M12: true
|
|
2275
|
-
};
|
|
2276
2583
|
function readVarint(bytes, start) {
|
|
2277
2584
|
let value = 0;
|
|
2278
2585
|
let shift = 0;
|
|
@@ -2366,10 +2673,6 @@ async function readAntigravityOAuthToken(db) {
|
|
|
2366
2673
|
if (!accessToken && !refreshToken) return { token: null, status: "ok" };
|
|
2367
2674
|
return { token: { accessToken, refreshToken, expirySeconds }, status: "ok" };
|
|
2368
2675
|
}
|
|
2369
|
-
function redact(token) {
|
|
2370
|
-
if (!token) return "none";
|
|
2371
|
-
return `...${token.slice(-4)}`;
|
|
2372
|
-
}
|
|
2373
2676
|
function geminiBundleCandidates() {
|
|
2374
2677
|
const candidates = [];
|
|
2375
2678
|
const addBundle = (nodeModulesRoot) => {
|
|
@@ -2481,6 +2784,30 @@ async function refreshAccessToken(refreshToken) {
|
|
|
2481
2784
|
return null;
|
|
2482
2785
|
}
|
|
2483
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
|
+
}
|
|
2484
2811
|
async function requestCloudCodeJson(path, token, body) {
|
|
2485
2812
|
for (const base of CLOUD_CODE_URLS) {
|
|
2486
2813
|
try {
|
|
@@ -2586,14 +2913,16 @@ async function fetchCloudCodeQuota(token, expiredMessage = "Token expired") {
|
|
|
2586
2913
|
if (!refreshed) return { ok: false, plan: result.plan, error: expiredMessage };
|
|
2587
2914
|
return fetchWithAccessToken(refreshed);
|
|
2588
2915
|
}
|
|
2916
|
+
|
|
2917
|
+
// src/providers/cloud-code/index.ts
|
|
2589
2918
|
function normalizeLabel(label) {
|
|
2590
2919
|
return label.replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
2591
2920
|
}
|
|
2592
|
-
function poolLabel(label) {
|
|
2921
|
+
function poolLabel(label, fullGeminiLabels = false) {
|
|
2593
2922
|
const lower = normalizeLabel(label).toLowerCase();
|
|
2594
2923
|
if (lower.includes("claude")) return "Claude";
|
|
2595
|
-
if (lower.includes("gemini") && lower.includes("pro")) return "Pro";
|
|
2596
|
-
if (lower.includes("gemini") && lower.includes("flash")) return "Flash";
|
|
2924
|
+
if (lower.includes("gemini") && lower.includes("pro")) return fullGeminiLabels ? "Gemini Pro" : "Pro";
|
|
2925
|
+
if (lower.includes("gemini") && lower.includes("flash")) return fullGeminiLabels ? "Gemini Flash" : "Flash";
|
|
2597
2926
|
if (lower.includes("gemini")) return "Gemini";
|
|
2598
2927
|
return normalizeLabel(label) || "Other";
|
|
2599
2928
|
}
|
|
@@ -2609,11 +2938,11 @@ function sortKey(label) {
|
|
|
2609
2938
|
if (lower.includes("claude")) return `1b_${label}`;
|
|
2610
2939
|
return `2_${label}`;
|
|
2611
2940
|
}
|
|
2612
|
-
function cloudCodeBucketsToMetrics(buckets) {
|
|
2941
|
+
function cloudCodeBucketsToMetrics(buckets, options = {}) {
|
|
2613
2942
|
const pooled = /* @__PURE__ */ new Map();
|
|
2614
2943
|
for (const bucket of buckets) {
|
|
2615
2944
|
if (!Number.isFinite(bucket.remainingFraction)) continue;
|
|
2616
|
-
const label = poolLabel(bucket.modelId);
|
|
2945
|
+
const label = poolLabel(bucket.modelId, options.fullGeminiLabels === true);
|
|
2617
2946
|
const existing = pooled.get(label);
|
|
2618
2947
|
if (!existing || bucket.remainingFraction < existing.remainingFraction) {
|
|
2619
2948
|
pooled.set(label, { ...bucket, modelId: label });
|
|
@@ -2690,7 +3019,7 @@ async function antigravityBilling(account) {
|
|
|
2690
3019
|
if (!token) return { plan: null, metrics: [], error: cloudCodeSqliteError(status) };
|
|
2691
3020
|
const quota = await fetchCloudCodeQuota(token, "Token expired \u2014 open Antigravity");
|
|
2692
3021
|
if (!quota.ok) return { plan: quota.plan, metrics: [], error: quota.error };
|
|
2693
|
-
return { plan: quota.plan, metrics: cloudCodeBucketsToMetrics(quota.buckets), error: null };
|
|
3022
|
+
return { plan: quota.plan, metrics: cloudCodeBucketsToMetrics(quota.buckets, { fullGeminiLabels: true }), error: null };
|
|
2694
3023
|
} catch {
|
|
2695
3024
|
return { plan: null, metrics: [], error: "Antigravity billing unavailable" };
|
|
2696
3025
|
}
|
|
@@ -2733,7 +3062,7 @@ var PRICING3 = {
|
|
|
2733
3062
|
"gemini-2.0-flash": { in: 1e-7, out: 4e-7, cr: 25e-9 }
|
|
2734
3063
|
};
|
|
2735
3064
|
var PRICE_KEYS3 = Object.keys(PRICING3).sort((a, b) => b.length - a.length);
|
|
2736
|
-
var
|
|
3065
|
+
var ZERO_PRICE2 = { in: 0, out: 0, cr: 0 };
|
|
2737
3066
|
function geminiTmpDir(homeDir) {
|
|
2738
3067
|
return join15(homeDir ?? homedir12(), ".gemini", "tmp");
|
|
2739
3068
|
}
|
|
@@ -2744,7 +3073,7 @@ function priceFor3(model) {
|
|
|
2744
3073
|
const rest = m.slice(key.length);
|
|
2745
3074
|
if (rest === "" || rest[0] === "-") return PRICING3[key];
|
|
2746
3075
|
}
|
|
2747
|
-
return
|
|
3076
|
+
return ZERO_PRICE2;
|
|
2748
3077
|
}
|
|
2749
3078
|
function shortModel2(model) {
|
|
2750
3079
|
return model.replace(/(-preview|-customtools)+$/, "");
|
|
@@ -2890,17 +3219,8 @@ async function hasGeminiChatSessions(homeDir) {
|
|
|
2890
3219
|
}
|
|
2891
3220
|
return listing.some((path) => /(^|[\\/])chats[\\/]session-.*\.jsonl$/.test(path));
|
|
2892
3221
|
}
|
|
2893
|
-
function decodeBase64UrlJson2(segment) {
|
|
2894
|
-
try {
|
|
2895
|
-
const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
2896
|
-
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
|
|
2897
|
-
return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
2898
|
-
} catch {
|
|
2899
|
-
return null;
|
|
2900
|
-
}
|
|
2901
|
-
}
|
|
2902
3222
|
function geminiIdentity(creds) {
|
|
2903
|
-
const tokenPayload = typeof creds?.id_token === "string" && creds.id_token.includes(".") ?
|
|
3223
|
+
const tokenPayload = typeof creds?.id_token === "string" && creds.id_token.includes(".") ? decodeBase64UrlJson(creds.id_token.split(".")[1]) : null;
|
|
2904
3224
|
const email = typeof creds?.email === "string" && creds.email.trim() || typeof tokenPayload?.email === "string" && tokenPayload.email.trim() || null;
|
|
2905
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;
|
|
2906
3226
|
return { email, displayName };
|
|
@@ -2951,6 +3271,113 @@ var geminiProvider = {
|
|
|
2951
3271
|
fetchBilling: (account) => geminiBilling(account)
|
|
2952
3272
|
};
|
|
2953
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
|
+
|
|
2954
3381
|
// src/providers/index.ts
|
|
2955
3382
|
var PROVIDER_ORDER = [...PROVIDER_IDS];
|
|
2956
3383
|
var PROVIDERS = {
|
|
@@ -2978,11 +3405,32 @@ async function detectProviders() {
|
|
|
2978
3405
|
}
|
|
2979
3406
|
|
|
2980
3407
|
// src/accounts.ts
|
|
2981
|
-
import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
|
|
2982
|
-
import { homedir as
|
|
2983
|
-
import { basename, join as
|
|
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
|
|
2984
3432
|
function accountKey(providerId, homeDir) {
|
|
2985
|
-
return `${providerId}:${homeDir ? resolve(expandHome(homeDir)) :
|
|
3433
|
+
return `${providerId}:${homeDir ? resolve(expandHome(homeDir)) : homedir15()}`;
|
|
2986
3434
|
}
|
|
2987
3435
|
function uniqueId(base, used) {
|
|
2988
3436
|
let id = slugify(base) || "account";
|
|
@@ -3001,23 +3449,11 @@ function uniqueId(base, used) {
|
|
|
3001
3449
|
used.add(id);
|
|
3002
3450
|
return id;
|
|
3003
3451
|
}
|
|
3004
|
-
function readClaudeIdentity2(homeDir) {
|
|
3005
|
-
try {
|
|
3006
|
-
const parsed = JSON.parse(readFileSync(join17(homeDir, ".claude.json"), "utf-8"));
|
|
3007
|
-
const oauth = parsed?.oauthAccount;
|
|
3008
|
-
return {
|
|
3009
|
-
email: typeof oauth?.emailAddress === "string" && oauth.emailAddress.trim() ? oauth.emailAddress.trim() : void 0,
|
|
3010
|
-
displayName: typeof oauth?.displayName === "string" && oauth.displayName.trim() ? oauth.displayName.trim() : void 0
|
|
3011
|
-
};
|
|
3012
|
-
} catch {
|
|
3013
|
-
return {};
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
3452
|
function hasClaudeState(homeDir) {
|
|
3017
|
-
return existsSync2(
|
|
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"));
|
|
3018
3454
|
}
|
|
3019
3455
|
function candidateAlternateHomes(prefix) {
|
|
3020
|
-
const home =
|
|
3456
|
+
const home = homedir15();
|
|
3021
3457
|
let entries;
|
|
3022
3458
|
try {
|
|
3023
3459
|
entries = readdirSync(home);
|
|
@@ -3028,7 +3464,7 @@ function candidateAlternateHomes(prefix) {
|
|
|
3028
3464
|
const pattern = new RegExp(`^\\.${prefix}[_-]`);
|
|
3029
3465
|
for (const name of entries) {
|
|
3030
3466
|
if (!pattern.test(name)) continue;
|
|
3031
|
-
const path =
|
|
3467
|
+
const path = join19(home, name);
|
|
3032
3468
|
try {
|
|
3033
3469
|
if (!statSync(path).isDirectory()) continue;
|
|
3034
3470
|
out.push(path);
|
|
@@ -3038,45 +3474,16 @@ function candidateAlternateHomes(prefix) {
|
|
|
3038
3474
|
return out.sort();
|
|
3039
3475
|
}
|
|
3040
3476
|
function labelForClaudeHome(homeDir) {
|
|
3041
|
-
const identity =
|
|
3477
|
+
const identity = readClaudeIdentity(homeDir);
|
|
3042
3478
|
if (identity.email) return `Claude ${identity.email}`;
|
|
3043
3479
|
if (identity.displayName) return `Claude ${identity.displayName}`;
|
|
3044
3480
|
const raw = basename(homeDir).replace(/^\.claude[_-]?/, "").replace(/[_-]+/g, " ").trim();
|
|
3045
3481
|
return raw ? `Claude ${raw}` : "Claude";
|
|
3046
3482
|
}
|
|
3047
|
-
function decodeBase64UrlJson3(segment) {
|
|
3048
|
-
try {
|
|
3049
|
-
const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
3050
|
-
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
|
|
3051
|
-
return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
3052
|
-
} catch {
|
|
3053
|
-
return null;
|
|
3054
|
-
}
|
|
3055
|
-
}
|
|
3056
|
-
function codexAuthPaths(homeDir) {
|
|
3057
|
-
return [join17(homeDir, ".codex", "auth.json"), join17(homeDir, "auth.json")];
|
|
3058
|
-
}
|
|
3059
|
-
function readCodexIdentity(homeDir) {
|
|
3060
|
-
for (const path of codexAuthPaths(homeDir)) {
|
|
3061
|
-
try {
|
|
3062
|
-
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
3063
|
-
const idToken = parsed?.tokens?.id_token;
|
|
3064
|
-
if (typeof idToken !== "string" || !idToken.includes(".")) continue;
|
|
3065
|
-
const payload = decodeBase64UrlJson3(idToken.split(".")[1]);
|
|
3066
|
-
if (!payload || typeof payload !== "object") continue;
|
|
3067
|
-
return {
|
|
3068
|
-
email: typeof payload?.email === "string" && payload.email.trim() ? payload.email.trim() : void 0,
|
|
3069
|
-
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
|
|
3070
|
-
};
|
|
3071
|
-
} catch {
|
|
3072
|
-
}
|
|
3073
|
-
}
|
|
3074
|
-
return {};
|
|
3075
|
-
}
|
|
3076
3483
|
function hasCodexAuth(homeDir) {
|
|
3077
3484
|
for (const path of codexAuthPaths(homeDir)) {
|
|
3078
3485
|
try {
|
|
3079
|
-
const parsed = JSON.parse(
|
|
3486
|
+
const parsed = JSON.parse(readFileSync3(path, "utf-8"));
|
|
3080
3487
|
const accessToken = parsed?.tokens?.access_token;
|
|
3081
3488
|
if (typeof accessToken === "string" && accessToken.trim()) return true;
|
|
3082
3489
|
} catch {
|