tokentracker-cli 0.73.0 → 0.74.1
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/README.de.md +3 -2
- package/README.ja.md +3 -2
- package/README.ko.md +3 -2
- package/README.md +3 -2
- package/README.zh-CN.md +3 -2
- package/dashboard/dist/assets/{ActivityHeatmap-C0txyttw.js → ActivityHeatmap-D5DFI_JT.js} +1 -1
- package/dashboard/dist/assets/{Card-BhbsF2sd.js → Card-C0dBE40G.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-DYuw3NEB.js → CommunityStatsModal-EoqdsEND.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-pSNHLvla.js → ConfirmModal-Dmmph0Fh.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-DWzdgrPp.js → DashboardPage-CCj2poZF.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-Bv_Rf-TG.js → DevicePage-DeAi9xZM.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-N80c6k5O.js → DialogClose-D3RkFfxL.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-SXe3669h.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DhnXV44R.js → DialogTitle-B_MCherE.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-D-iQ_Y2x.js → FadeIn-DNN2K13r.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-BfDCQeWv.js → HeaderGithubStar-lFcC5cJF.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-Dp4obb18.js → IpCheckPage-CeLaiU7v.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-CRjKRU6i.js → LandingPage-DAoPUMvI.js} +97 -37
- package/dashboard/dist/assets/{LeaderboardAvatar-Cgn3pcho.js → LeaderboardAvatar-FQi0zZEF.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-Bhq7LL3D.js → LeaderboardPage-D5b5VXQC.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-Bsls5fHq.js → LeaderboardProfileModal-B4zRhrdW.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-BYfd5SmB.js → LeaderboardProfilePage-Bq8rq1En.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-DAnTAkl9.js → LimitsPage-DWM80BaS.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-CVg_JUKd.js → LocalOnlyNotice-B8m8CUt0.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-D9neHZav.js → LoginPage-BUW1YAd4.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-CenmP6bc.js → PopoverPopup-Bpt95eH1.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-BGXobvFW.js → ResetPasswordPage-BbIfq0Oo.js} +1 -1
- package/dashboard/dist/assets/{Select-cCMkMvY9.js → Select-gTXpf9KQ.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-OO6aEsCS.js → SelectItemText-DEkgwu_p.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-zFmcMe2k.js → SettingsPage-DI9CabCH.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-wAVoEoqs.js → SkillsPage-BfCZDHaD.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CdxVbQxt.js → WidgetsPage-rU0VeGYf.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-HCMET3ON.js → WrappedPage-CZa-5-KZ.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-ngQzAIwh.js → agent-logos-EmiprqVo.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-DwNdVf8C.js → arrow-up-right-k-vRvS_D.js} +1 -1
- package/dashboard/dist/assets/check-BBoXV3Xq.js +1 -0
- package/dashboard/dist/assets/{copy-D5Tgbfx9.js → copy-syqMmWHW.js} +1 -1
- package/dashboard/dist/assets/{download-BkXuG4x3.js → download-DZcuOKDP.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-DpplPif4.js → format-tokens-D7k8OXQS.js} +1 -1
- package/dashboard/dist/assets/{info-bmDUStkp.js → info-CEBgCIZx.js} +1 -1
- package/dashboard/dist/assets/main--nUrKVF3.css +1 -0
- package/dashboard/dist/assets/{main-COdyqkJx.js → main-BALrkVr_.js} +2 -2
- package/dashboard/dist/assets/{use-community-stats-BSqhgTim.js → use-community-stats-DUttYzRH.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-DaL_M4gM.js → use-limits-display-prefs-BhIvYvqB.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-DO1hquUr.js → use-native-settings-BhOpUBnt.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-Dj5XtHo4.js → use-ordered-list-Cd6yagUF.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-TbW0hepx.js → use-quality-per-dollar-pref-DBeuBvln.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-BUth5S9D.js → use-usage-limits-DBhYqpBW.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-BmoIPMdl.js → useCurrency-Cy2Q8nWt.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-BYRX6K6Z.js → useScrollLock-C6Eqqoge.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/commands/status.js +47 -4
- package/src/commands/sync.js +72 -18
- package/src/lib/local-api.js +7 -2
- package/src/lib/pricing/curated-overrides.json +7 -2
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +385 -0
- package/src/lib/sqlite-reader.js +24 -6
- package/src/lib/usage-limits.js +224 -29
- package/src/lib/zcode-limits.js +573 -39
- package/dashboard/dist/assets/DialogDescription-lEwHWbK4.js +0 -1
- package/dashboard/dist/assets/check-B2E13r-e.js +0 -1
- package/dashboard/dist/assets/main-DXM2UPde.css +0 -1
package/src/lib/usage-limits.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require("node:path");
|
|
|
6
6
|
const http = require("node:http");
|
|
7
7
|
const https = require("node:https");
|
|
8
8
|
const { performance } = require("node:perf_hooks");
|
|
9
|
+
const { promisify } = require("node:util");
|
|
9
10
|
|
|
10
11
|
const {
|
|
11
12
|
detectClaudeCodeSubscriptionDetails,
|
|
@@ -26,7 +27,9 @@ const {
|
|
|
26
27
|
const { fetchGrokLimits } = require("./grok-limits");
|
|
27
28
|
const { fetchZcodeLimits } = require("./zcode-limits");
|
|
28
29
|
const { fetchOpencodeGoLimits } = require("./opencode-go-limits");
|
|
29
|
-
const { readSqliteJsonRows } = require("./sqlite-reader");
|
|
30
|
+
const { readSqliteJsonRows, readSqliteJsonRowsAsync } = require("./sqlite-reader");
|
|
31
|
+
|
|
32
|
+
const execFileAsync = promisify(cp.execFile);
|
|
30
33
|
|
|
31
34
|
// 2-minute in-memory cache. It also expires early at the earliest upcoming window
|
|
32
35
|
// reset in the cached data (see cacheExpiresAtMs), floored so a provider reporting
|
|
@@ -1418,11 +1421,11 @@ function parseKiroUsageOutput(output, { now = new Date() } = {}) {
|
|
|
1418
1421
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1419
1422
|
// GitHub Copilot — `GET https://api.github.com/copilot_internal/user`
|
|
1420
1423
|
// Reuses the OAuth token from the user's existing Copilot install. Older clients
|
|
1421
|
-
// keep it in plaintext (`~/.config/github-copilot/{apps,hosts}.json`);
|
|
1422
|
-
// copilot-language-server builds (Zed, copilot.vim) migrate it into
|
|
1423
|
-
//
|
|
1424
|
-
//
|
|
1425
|
-
// device flow needed either way.
|
|
1424
|
+
// keep it in plaintext (`~/.config/github-copilot/{apps,hosts}.json`); newer
|
|
1425
|
+
// copilot-language-server builds (Zed, copilot.vim) migrate it into SQLite
|
|
1426
|
+
// `auth.db`. Schema v0 stores the OAuth token as a plaintext BLOB, while later
|
|
1427
|
+
// schemas use AES-GCM ciphertext with the key in the OS credential store. Read
|
|
1428
|
+
// legacy plaintext first, then auth.db. No device flow needed either way.
|
|
1426
1429
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1427
1430
|
|
|
1428
1431
|
const MACOS_SECURITY_BIN = "/usr/bin/security";
|
|
@@ -1433,7 +1436,7 @@ const MACOS_SECURITY_BIN = "/usr/bin/security";
|
|
|
1433
1436
|
const COPILOT_LS_KEYCHAIN_SERVICE = "copilot-language-server";
|
|
1434
1437
|
const COPILOT_LS_KEYCHAIN_ACCOUNT = "oauth-token-key";
|
|
1435
1438
|
const COPILOT_AUTH_DB_SQL =
|
|
1436
|
-
"SELECT user_login, auth_authority, scopes, hex(token_ciphertext) AS token_hex, "
|
|
1439
|
+
"SELECT user_login, auth_authority, scopes, token_schema_version, hex(token_ciphertext) AS token_hex, "
|
|
1437
1440
|
+ "last_used_at, updated_at FROM oauth_tokens ORDER BY last_used_at DESC, updated_at DESC";
|
|
1438
1441
|
|
|
1439
1442
|
function readPlaintextCopilotOauthToken({ home }) {
|
|
@@ -1489,6 +1492,32 @@ function readMacosKeychainGenericPassword({ service, account, securityRunner } =
|
|
|
1489
1492
|
return trimmed.length > 0 ? trimmed : null;
|
|
1490
1493
|
}
|
|
1491
1494
|
|
|
1495
|
+
async function readMacosKeychainGenericPasswordAsync({ service, account, securityRunner } = {}) {
|
|
1496
|
+
if (typeof securityRunner === "function") {
|
|
1497
|
+
return readMacosKeychainGenericPassword({ service, account, securityRunner });
|
|
1498
|
+
}
|
|
1499
|
+
if (!fs.existsSync(MACOS_SECURITY_BIN)) return null;
|
|
1500
|
+
const args = ["find-generic-password", "-s", service];
|
|
1501
|
+
if (account) args.push("-a", account);
|
|
1502
|
+
args.push("-w");
|
|
1503
|
+
try {
|
|
1504
|
+
const result = await execFileAsync(MACOS_SECURITY_BIN, args, {
|
|
1505
|
+
timeout: 2000,
|
|
1506
|
+
encoding: "utf8",
|
|
1507
|
+
});
|
|
1508
|
+
const stdout =
|
|
1509
|
+
typeof result?.stdout === "string"
|
|
1510
|
+
? result.stdout
|
|
1511
|
+
: Buffer.isBuffer(result?.stdout)
|
|
1512
|
+
? result.stdout.toString("utf8")
|
|
1513
|
+
: "";
|
|
1514
|
+
const trimmed = stdout.trim();
|
|
1515
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1516
|
+
} catch (_e) {
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1492
1521
|
function decryptCopilotAuthDbToken(keyBase64, ciphertextHex) {
|
|
1493
1522
|
if (typeof keyBase64 !== "string" || typeof ciphertextHex !== "string") return null;
|
|
1494
1523
|
let key;
|
|
@@ -1515,34 +1544,163 @@ function decryptCopilotAuthDbToken(keyBase64, ciphertextHex) {
|
|
|
1515
1544
|
}
|
|
1516
1545
|
}
|
|
1517
1546
|
|
|
1518
|
-
function
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1547
|
+
function isLikelyGithubOauthToken(token) {
|
|
1548
|
+
if (typeof token !== "string") return false;
|
|
1549
|
+
const trimmed = token.trim();
|
|
1550
|
+
return /^gh[opsur]_[A-Za-z0-9]{20,}$/.test(trimmed)
|
|
1551
|
+
|| /^github_pat_[A-Za-z0-9_]{20,}$/.test(trimmed);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function parseCopilotAuthDbSchemaVersion(value) {
|
|
1555
|
+
if (value === null || value === undefined || value === "") return null;
|
|
1556
|
+
const version = Number(value);
|
|
1557
|
+
return Number.isInteger(version) ? version : null;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function decodeUtf8HexString(value) {
|
|
1561
|
+
if (typeof value !== "string" || !/^(?:[0-9a-f]{2})+$/i.test(value)) return null;
|
|
1527
1562
|
try {
|
|
1528
|
-
|
|
1563
|
+
return Buffer.from(value, "hex").toString("utf8").trim();
|
|
1529
1564
|
} catch (_e) {
|
|
1530
1565
|
return null;
|
|
1531
1566
|
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function decodeCopilotSchemaV0Token(row) {
|
|
1570
|
+
const candidates = [];
|
|
1571
|
+
const tokenHex = typeof row?.token_hex === "string" ? row.token_hex : null;
|
|
1572
|
+
if (tokenHex) candidates.push(decodeUtf8HexString(tokenHex));
|
|
1573
|
+
|
|
1574
|
+
const rawCiphertext = row?.token_ciphertext;
|
|
1575
|
+
if (Buffer.isBuffer(rawCiphertext)) {
|
|
1576
|
+
candidates.push(rawCiphertext.toString("utf8").trim());
|
|
1577
|
+
} else if (typeof rawCiphertext === "string") {
|
|
1578
|
+
candidates.push(rawCiphertext.trim());
|
|
1579
|
+
candidates.push(decodeUtf8HexString(rawCiphertext));
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
for (const candidate of candidates) {
|
|
1583
|
+
if (isLikelyGithubOauthToken(candidate)) return candidate.trim();
|
|
1584
|
+
}
|
|
1585
|
+
return null;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function orderCopilotAuthDbRows(rows) {
|
|
1589
|
+
const githubRows = [];
|
|
1590
|
+
const fallbackRows = [];
|
|
1591
|
+
for (const row of rows) {
|
|
1592
|
+
const host = String(row?.auth_authority || "").split(":")[0];
|
|
1593
|
+
if (host === "github.com") {
|
|
1594
|
+
githubRows.push(row);
|
|
1595
|
+
} else {
|
|
1596
|
+
fallbackRows.push(row);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
return githubRows.concat(fallbackRows);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function readCopilotAuthDbRows({ home, sqliteReader, asyncReader = false } = {}) {
|
|
1603
|
+
const resolvedHome = home || os.homedir();
|
|
1604
|
+
const dbPath = path.join(resolvedHome, ".config", "github-copilot", "auth.db");
|
|
1605
|
+
const reader = typeof sqliteReader === "function"
|
|
1606
|
+
? sqliteReader
|
|
1607
|
+
: asyncReader
|
|
1608
|
+
? readSqliteJsonRowsAsync
|
|
1609
|
+
: readSqliteJsonRows;
|
|
1610
|
+
return reader(dbPath, COPILOT_AUTH_DB_SQL, { label: "GitHub Copilot" });
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function resolveCopilotAuthDbTokenFromRows(rows, {
|
|
1614
|
+
platform = process.platform,
|
|
1615
|
+
securityRunner,
|
|
1616
|
+
keychainReader = readMacosKeychainGenericPassword,
|
|
1617
|
+
} = {}) {
|
|
1532
1618
|
if (!Array.isArray(rows) || rows.length === 0) return null;
|
|
1533
1619
|
// Prefer the public github.com host (mirrors the plaintext reader); rows are
|
|
1534
1620
|
// already ordered most-recently-used first.
|
|
1535
|
-
const
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1621
|
+
const orderedRows = orderCopilotAuthDbRows(rows);
|
|
1622
|
+
let keyBase64 = null;
|
|
1623
|
+
let attemptedKeychain = false;
|
|
1624
|
+
for (const row of orderedRows) {
|
|
1625
|
+
const schemaVersion = parseCopilotAuthDbSchemaVersion(row?.token_schema_version);
|
|
1626
|
+
if (schemaVersion === 0) {
|
|
1627
|
+
const plaintextToken = decodeCopilotSchemaV0Token(row);
|
|
1628
|
+
if (plaintextToken) return plaintextToken;
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
const ciphertextHex = typeof row?.token_hex === "string" ? row.token_hex : null;
|
|
1633
|
+
if (!ciphertextHex || platform !== "darwin") continue;
|
|
1634
|
+
// The decryption key lives in the macOS Keychain. On Linux/Windows the
|
|
1635
|
+
// copilot-language-server uses libsecret / Credential Manager instead, which
|
|
1636
|
+
// we don't read yet.
|
|
1637
|
+
if (!attemptedKeychain) {
|
|
1638
|
+
keyBase64 = keychainReader({
|
|
1639
|
+
service: COPILOT_LS_KEYCHAIN_SERVICE,
|
|
1640
|
+
account: COPILOT_LS_KEYCHAIN_ACCOUNT,
|
|
1641
|
+
securityRunner,
|
|
1642
|
+
});
|
|
1643
|
+
attemptedKeychain = true;
|
|
1644
|
+
}
|
|
1645
|
+
if (!keyBase64) continue;
|
|
1646
|
+
const token = decryptCopilotAuthDbToken(keyBase64, ciphertextHex);
|
|
1647
|
+
if (token) return token;
|
|
1648
|
+
}
|
|
1649
|
+
return null;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
async function resolveCopilotAuthDbTokenFromRowsAsync(rows, {
|
|
1653
|
+
platform = process.platform,
|
|
1654
|
+
securityRunner,
|
|
1655
|
+
keychainReader = readMacosKeychainGenericPasswordAsync,
|
|
1656
|
+
} = {}) {
|
|
1657
|
+
if (!Array.isArray(rows) || rows.length === 0) return null;
|
|
1658
|
+
const orderedRows = orderCopilotAuthDbRows(rows);
|
|
1659
|
+
let keyBase64 = null;
|
|
1660
|
+
let attemptedKeychain = false;
|
|
1661
|
+
for (const row of orderedRows) {
|
|
1662
|
+
const schemaVersion = parseCopilotAuthDbSchemaVersion(row?.token_schema_version);
|
|
1663
|
+
if (schemaVersion === 0) {
|
|
1664
|
+
const plaintextToken = decodeCopilotSchemaV0Token(row);
|
|
1665
|
+
if (plaintextToken) return plaintextToken;
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
const ciphertextHex = typeof row?.token_hex === "string" ? row.token_hex : null;
|
|
1670
|
+
if (!ciphertextHex || platform !== "darwin") continue;
|
|
1671
|
+
if (!attemptedKeychain) {
|
|
1672
|
+
keyBase64 = await keychainReader({
|
|
1673
|
+
service: COPILOT_LS_KEYCHAIN_SERVICE,
|
|
1674
|
+
account: COPILOT_LS_KEYCHAIN_ACCOUNT,
|
|
1675
|
+
securityRunner,
|
|
1676
|
+
});
|
|
1677
|
+
attemptedKeychain = true;
|
|
1678
|
+
}
|
|
1679
|
+
if (!keyBase64) continue;
|
|
1680
|
+
const token = decryptCopilotAuthDbToken(keyBase64, ciphertextHex);
|
|
1681
|
+
if (token) return token;
|
|
1682
|
+
}
|
|
1683
|
+
return null;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
function readCopilotAuthDbToken({ home, platform = process.platform, securityRunner, sqliteReader } = {}) {
|
|
1687
|
+
let rows;
|
|
1688
|
+
try {
|
|
1689
|
+
rows = readCopilotAuthDbRows({ home, sqliteReader });
|
|
1690
|
+
} catch (_e) {
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
return resolveCopilotAuthDbTokenFromRows(rows, { platform, securityRunner });
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
async function readCopilotAuthDbTokenAsync({ home, platform = process.platform, securityRunner, sqliteReader } = {}) {
|
|
1697
|
+
let rows;
|
|
1698
|
+
try {
|
|
1699
|
+
rows = await readCopilotAuthDbRows({ home, sqliteReader, asyncReader: true });
|
|
1700
|
+
} catch (_e) {
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1703
|
+
return resolveCopilotAuthDbTokenFromRowsAsync(rows, { platform, securityRunner });
|
|
1546
1704
|
}
|
|
1547
1705
|
|
|
1548
1706
|
function readCopilotOauthToken({
|
|
@@ -1557,6 +1715,17 @@ function readCopilotOauthToken({
|
|
|
1557
1715
|
return readCopilotAuthDbToken({ home, platform, securityRunner, sqliteReader });
|
|
1558
1716
|
}
|
|
1559
1717
|
|
|
1718
|
+
async function readCopilotOauthTokenAsync({
|
|
1719
|
+
home = os.homedir(),
|
|
1720
|
+
platform = process.platform,
|
|
1721
|
+
securityRunner,
|
|
1722
|
+
sqliteReader,
|
|
1723
|
+
} = {}) {
|
|
1724
|
+
const plaintext = readPlaintextCopilotOauthToken({ home });
|
|
1725
|
+
if (plaintext) return plaintext;
|
|
1726
|
+
return readCopilotAuthDbTokenAsync({ home, platform, securityRunner, sqliteReader });
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1560
1729
|
function copilotRequestHeaders(token) {
|
|
1561
1730
|
return {
|
|
1562
1731
|
Authorization: `token ${token}`,
|
|
@@ -1629,7 +1798,7 @@ async function fetchCopilotLimits({
|
|
|
1629
1798
|
sqliteReader,
|
|
1630
1799
|
} = {}) {
|
|
1631
1800
|
const otel = describeCopilotOtelStatus({ home, env });
|
|
1632
|
-
const token =
|
|
1801
|
+
const token = await readCopilotOauthTokenAsync({
|
|
1633
1802
|
home: home || (env.HOME || os.homedir()),
|
|
1634
1803
|
platform,
|
|
1635
1804
|
securityRunner,
|
|
@@ -2760,6 +2929,12 @@ async function fetchUsageLimitsUncached({
|
|
|
2760
2929
|
seven_day_opus: claudeResult.value.seven_day_opus,
|
|
2761
2930
|
weekly_scoped: claudeResult.value.weekly_scoped,
|
|
2762
2931
|
extra_usage: claudeResult.value.extra_usage,
|
|
2932
|
+
// A live read is current as of now. `stale`/`cached_at` are always present so
|
|
2933
|
+
// the client can render an "Updated Xm ago" age without guessing whether the
|
|
2934
|
+
// response was live or served from the disk-cache fallback (the cached paths
|
|
2935
|
+
// above already carry them). Mirrors the Codex live-success block below.
|
|
2936
|
+
stale: false,
|
|
2937
|
+
cached_at: new Date(nowMs).toISOString(),
|
|
2763
2938
|
};
|
|
2764
2939
|
writeClaudeLimitsCache(claude, { home, nowMs });
|
|
2765
2940
|
clearClaudeRateLimitCooldown({ home });
|
|
@@ -2785,6 +2960,19 @@ async function fetchUsageLimitsUncached({
|
|
|
2785
2960
|
}
|
|
2786
2961
|
}
|
|
2787
2962
|
|
|
2963
|
+
// Expose the active 429 cool-down expiry (if any) on the Claude object so the
|
|
2964
|
+
// client can (a) show when the next refresh is due next to stale bars and
|
|
2965
|
+
// (b) schedule a one-shot refresh the instant the cool-down lifts instead of
|
|
2966
|
+
// waiting out the poll interval. Re-read here (not the pre-fetch value) so a
|
|
2967
|
+
// cool-down just armed by this cycle's 429 is included; a successful read above
|
|
2968
|
+
// cleared the file, so this is null in the happy path.
|
|
2969
|
+
if (claude.configured) {
|
|
2970
|
+
const claudeCooldownMs = readClaudeRateLimitRetryAtMs({ home, nowMs });
|
|
2971
|
+
if (claudeCooldownMs) {
|
|
2972
|
+
claude.retry_at = new Date(claudeCooldownMs).toISOString();
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2788
2976
|
let codex;
|
|
2789
2977
|
if (!codexToken) {
|
|
2790
2978
|
codex = { configured: false };
|
|
@@ -2817,6 +3005,11 @@ async function fetchUsageLimitsUncached({
|
|
|
2817
3005
|
spark_primary_window: codexResult.value.spark_primary_window,
|
|
2818
3006
|
spark_secondary_window: codexResult.value.spark_secondary_window,
|
|
2819
3007
|
reset_credits: codexResult.value.reset_credits,
|
|
3008
|
+
// Live read is current as of now; the stale fallback path above serves the
|
|
3009
|
+
// disk cache with `stale: true`. Always emitting both lets the client show a
|
|
3010
|
+
// data-age label uniformly (see the Claude live-success block).
|
|
3011
|
+
stale: false,
|
|
3012
|
+
cached_at: new Date(nowMs).toISOString(),
|
|
2820
3013
|
};
|
|
2821
3014
|
writeCodexLimitsCache(codex, { home, nowMs });
|
|
2822
3015
|
}
|
|
@@ -2866,7 +3059,9 @@ module.exports = {
|
|
|
2866
3059
|
fetchAntigravityLimits,
|
|
2867
3060
|
fetchCopilotLimits,
|
|
2868
3061
|
readCopilotOauthToken,
|
|
3062
|
+
readCopilotOauthTokenAsync,
|
|
2869
3063
|
readCopilotAuthDbToken,
|
|
3064
|
+
readCopilotAuthDbTokenAsync,
|
|
2870
3065
|
decryptCopilotAuthDbToken,
|
|
2871
3066
|
describeCopilotOtelStatus,
|
|
2872
3067
|
fetchGrokLimits,
|