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