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