tokentracker-cli 0.96.1 → 0.96.3
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 +34 -25
- package/README.ja.md +38 -27
- package/README.ko.md +39 -28
- package/README.md +36 -28
- package/README.zh-CN.md +36 -25
- package/dashboard/dist/assets/{AchievementBadge-DKyElm0T.js → AchievementBadge-BX4iBBYM.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-BvCx3wND.js → AchievementsPage-DRYRuCbh.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DN69LzXX.js → AchievementsSection-BM_JWWhJ.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-DGiKdZfz.js → BadgeDetailModal-DxoxXst2.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-zN8rH7_K.js → BotAnimated-BVpFxG8u.js} +1 -1
- package/dashboard/dist/assets/{Card-COWo-ClV.js → Card-BJJs701t.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-BWsB7Eat.js → ClawdAnimated-46wPZ_Tc.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-BfvIwuoJ.js → CommandPalette-B_zkc7IU.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-BQWg7R_y.js → CommunityStatsModal-M2k5rIf1.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-C7ABSI-O.js → ConfirmModal-DBvOTcAQ.js} +1 -1
- package/dashboard/dist/assets/{Controls-vMzEZzej.js → Controls-BCkjPMSH.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-3bHhou09.js +1 -0
- package/dashboard/dist/assets/{DevicePage-3ejUBLWM.js → DevicePage-DFBUce8C.js} +1 -1
- package/dashboard/dist/assets/DialogClose-kvDd8h3V.js +1 -0
- package/dashboard/dist/assets/DialogDescription-BMyOicfz.js +1 -0
- package/dashboard/dist/assets/DialogTitle-DL5nvD31.js +1 -0
- package/dashboard/dist/assets/{FadeIn-D7Jcfz1N.js → FadeIn-B-Bg6SEy.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-D--Rx5BS.js → HeaderGithubStar-BExz0hh4.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-n1W0z3Wr.js → HoverTooltip-Dj5zemYT.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-D202tl9B.js → IpCheckPage-DN7FuJ_v.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-D6yL2EZo.js → LandingPage-9dKgSBRy.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-ChFg2mtA.js → LeaderboardAvatar-00V95XIg.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BXHwGs8r.js → LeaderboardPage-DkKfUt0Z.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-B5LoOpCk.js → LeaderboardProfileModal-C9sIHBgN.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-jsukhRz8.js → LeaderboardProfilePage-BAiLMsEb.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-BuenTaHa.js +8 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-Du0lz657.js → LocalOnlyNotice-CKhY27Lr.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-BFEX9x7-.js → LoginCard-BbvoBx5N.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-DqC7_d35.js → LoginModal-P7zIkBhO.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-Dzx8N7HE.js → LoginPage-Bo0bR26x.js} +1 -1
- package/dashboard/dist/assets/PetPage-C6i9bSSl.js +1 -0
- package/dashboard/dist/assets/PopoverPopup-h0gf36vg.js +1 -0
- package/dashboard/dist/assets/ProviderIcon-Dm8fwPdj.js +1 -0
- package/dashboard/dist/assets/{ResetPasswordPage-x_GR79sR.js → ResetPasswordPage-DvSJ8kg0.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-BxDiDV4R.js → SegmentedControl-CP7oiGZE.js} +1 -1
- package/dashboard/dist/assets/Select-iwP92x9C.js +1 -0
- package/dashboard/dist/assets/SelectItemText-DzljA15_.js +1 -0
- package/dashboard/dist/assets/{ServiceStatusPage-BVQuVdjg.js → ServiceStatusPage-uD4D0Tid.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-DE39wNIO.js → SessionsPage-Cmb1ssNP.js} +2 -2
- package/dashboard/dist/assets/SettingsPage-D8ryg1Pv.js +2 -0
- package/dashboard/dist/assets/SkillsPage-eXI09q03.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-BPM779Wb.js → TokenGalaxy-f2djrlUv.js} +1 -1
- package/dashboard/dist/assets/ToolbarRootContext-CDxCPYLv.js +1 -0
- package/dashboard/dist/assets/{TrendMonitor-BkNy-o1D.js → TrendMonitor-C419X22G.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-Bpg6H-ZB.js → WidgetsPage-AYq__pOO.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-C6-NM-RO.js → WrappedPage-Bsm9k5XI.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-CvMMIjhi.css → __vite-browser-external-5P7me0Qq.css} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-BDkeh90N.js +45 -0
- package/dashboard/dist/assets/{agent-logos-DpbkRqXT.js → agent-logos-B6Vg-4_1.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-BjOtWUVs.js → arrow-up-right-DIQXwaOD.js} +1 -1
- package/dashboard/dist/assets/{check-BlxnX5jZ.js → check-CgD_bV39.js} +1 -1
- package/dashboard/dist/assets/composite-BIgEqWZD.js +1 -0
- package/dashboard/dist/assets/{copy-CIsWjLY5.js → copy-DlM1BHdp.js} +1 -1
- package/dashboard/dist/assets/{download-QDaQbQdd.js → download-CQFG-VIY.js} +1 -1
- package/dashboard/dist/assets/{external-link-DPV4VU3m.js → external-link-BQyDqGxz.js} +1 -1
- package/dashboard/dist/assets/{flame-B_X-7VNp.js → flame-BamzyXzF.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-DEx2TJj8.js → format-tokens-C4G-_xYt.js} +1 -1
- package/dashboard/dist/assets/{icons-983RyGSl.js → icons-Ntscvl1a.js} +1 -1
- package/dashboard/dist/assets/{index-qtonw_x4.js → index-CUrcPqgp.js} +1 -1
- package/dashboard/dist/assets/{index-BONYA8HZ.js → index-DGxgsNoQ.js} +1 -1
- package/dashboard/dist/assets/{index-Cjs5stC0.js → index-FRIt5mlv.js} +1 -1
- package/dashboard/dist/assets/{info-DpVOhXZI.js → info-CXytWcZi.js} +1 -1
- package/dashboard/dist/assets/{link-2-CBro-upz.js → link-2-Dexoo_jO.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-wYFdSyOd.js → loader-circle-Cn0JHc8t.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-DhTyQ4ly.js → maximize-2-DLh7gYuP.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-CPljwVhN.js → modifiers.esm-DOh_xtyt.js} +1 -1
- package/dashboard/dist/assets/module-DUSXqJtA.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-D_wr-HOR.js → monitor-smartphone-CpqS9Ior.js} +1 -1
- package/dashboard/dist/assets/{plus-BNjfkBEF.js → plus-bfQV7nyv.js} +1 -1
- package/dashboard/dist/assets/provider-display-Bf9pW06U.js +42 -0
- package/dashboard/dist/assets/{react-yHv9MTUA.js → react-BmFZKA_b.js} +1 -1
- package/dashboard/dist/assets/{search-Dfak92ZA.js → search-l3jJ2fzC.js} +1 -1
- package/dashboard/dist/assets/{skills-api-By2PdNa1.js → skills-api-CWmLnaxn.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-B9xy66_v.js → sortable.esm-DLgyXLHM.js} +1 -1
- package/dashboard/dist/assets/store-JM-a8ElZ.js +1 -0
- package/dashboard/dist/assets/{terminal-pbXWBmZM.js → terminal-6aY2Sn8P.js} +1 -1
- package/dashboard/dist/assets/{trash-2-CboDPwbW.js → trash-D5oon5pT.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-D6HTdnHq.js → use-community-stats-ZWUMFwzc.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-hwSr9Cam.js → use-latest-request-guard-BDaXr4gK.js} +1 -1
- package/dashboard/dist/assets/use-limits-display-prefs-yXXToNIc.js +1 -0
- package/dashboard/dist/assets/{use-ordered-list-OtfOqrNb.js → use-ordered-list-C6RG8N6c.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-BqDKim_3.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-BScvtS8F.js → use-session-efficiency-pref-DpgZO_Lx.js} +1 -1
- package/dashboard/dist/assets/{use-transform-Cy1ykjEs.js → use-transform-D42P7jkd.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-CV6puFwq.js +1 -0
- package/dashboard/dist/assets/{useCurrency-VqF3cy1C.js → useCurrency-GWBmOrCR.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-Cgub4MXu.js → useTokenFormat-Beybkb0v.js} +1 -1
- package/dashboard/dist/assets/{zap-DStzvQJI.js → zap-CEq2616W.js} +1 -1
- package/dashboard/dist/brand-logos/acode-dark.png +0 -0
- package/dashboard/dist/brand-logos/acode.png +0 -0
- package/dashboard/dist/brand-logos/commandcode.svg +1 -0
- package/dashboard/dist/brand-logos/devin.svg +6 -0
- package/dashboard/dist/index.html +20 -16
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/llms.txt +4 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +2 -2
- package/src/commands/init.js +114 -4
- package/src/commands/status.js +37 -0
- package/src/commands/sync.js +92 -10
- package/src/commands/uninstall.js +23 -0
- package/src/lib/codex-config.js +42 -0
- package/src/lib/codex-rollout-parser.js +2 -2
- package/src/lib/commandcode-limits.js +317 -0
- package/src/lib/devin-limits.js +249 -0
- package/src/lib/devin-usage.js +212 -0
- package/src/lib/local-api.js +14 -0
- package/src/lib/passive-mode.js +13 -1
- package/src/lib/pricing/curated-overrides.json +78 -1
- package/src/lib/pricing/index.js +11 -9
- package/src/lib/pricing/matcher.js +28 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1473 -147
- package/src/lib/session-analytics.js +2 -2
- package/src/lib/skills-manager.js +5 -0
- package/src/lib/subscriptions.js +70 -16
- package/src/lib/usage-limits.js +240 -49
- package/dashboard/dist/assets/DashboardPage-QBhbCMZF.js +0 -1
- package/dashboard/dist/assets/DialogClose-D_dvtfGV.js +0 -1
- package/dashboard/dist/assets/DialogDescription-ryGYdQjX.js +0 -1
- package/dashboard/dist/assets/DialogTitle-_ahf0z8b.js +0 -1
- package/dashboard/dist/assets/LimitsPage-PvqkOOwp.js +0 -6
- package/dashboard/dist/assets/PetPage-CNlWHF4z.js +0 -1
- package/dashboard/dist/assets/PopoverPopup-D9jadD3j.js +0 -1
- package/dashboard/dist/assets/ProviderIcon-Caz5PuAm.js +0 -1
- package/dashboard/dist/assets/Select-B5o-1sEl.js +0 -1
- package/dashboard/dist/assets/SelectItemText-CiA6L7Wk.js +0 -1
- package/dashboard/dist/assets/SettingsPage-DJrb_fsK.js +0 -2
- package/dashboard/dist/assets/SkillsPage-COk-YQQ5.js +0 -1
- package/dashboard/dist/assets/ToolbarRootContext-CozJT6Vs.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-BGnq8Ebm.js +0 -45
- package/dashboard/dist/assets/composite-Dxmivlk4.js +0 -1
- package/dashboard/dist/assets/limits-providers-BUoquSA-.js +0 -1
- package/dashboard/dist/assets/module-xpSFyNBK.js +0 -3
- package/dashboard/dist/assets/provider-display-BOBtUyFi.js +0 -42
- package/dashboard/dist/assets/store-oQZR4d9M.js +0 -1
- package/dashboard/dist/assets/use-limits-display-prefs-B-pbwUS8.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-DGXcZBX3.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-CSMb2zYW.js +0 -1
package/src/lib/usage-limits.js
CHANGED
|
@@ -11,7 +11,7 @@ const { promisify } = require("node:util");
|
|
|
11
11
|
const {
|
|
12
12
|
detectClaudeCodeCredentialsPresence,
|
|
13
13
|
detectClaudeCodeSubscriptionDetails,
|
|
14
|
-
|
|
14
|
+
readClaudeCodeOauthToken,
|
|
15
15
|
readCodexAccessToken,
|
|
16
16
|
readCodexAuthBundle,
|
|
17
17
|
} = require("./subscriptions");
|
|
@@ -30,6 +30,8 @@ const {
|
|
|
30
30
|
const { fetchGrokLimits } = require("./grok-limits");
|
|
31
31
|
const { fetchZcodeLimits } = require("./zcode-limits");
|
|
32
32
|
const { fetchOpencodeGoLimits } = require("./opencode-go-limits");
|
|
33
|
+
const { fetchCommandcodeLimits } = require("./commandcode-limits");
|
|
34
|
+
const { fetchDevinLimits } = require("./devin-limits");
|
|
33
35
|
const { fetchQoderLimits, fetchQoderCnLimits } = require("./qoder-limits");
|
|
34
36
|
const { fetchArkCodingPlanLimits } = require("./ark-coding-plan-limits");
|
|
35
37
|
const { fetchArkAgentPlanLimits } = require("./ark-agent-plan-limits");
|
|
@@ -46,7 +48,15 @@ const execFileAsync = promisify(cp.execFile);
|
|
|
46
48
|
// 2-minute in-memory cache. It also expires early at the earliest upcoming window
|
|
47
49
|
// reset in the cached data (see cacheExpiresAtMs), floored so a provider reporting
|
|
48
50
|
// a reset "right now" can't turn every poll into a full upstream round.
|
|
49
|
-
|
|
51
|
+
// Partitioned by the Devin opt-in selection so a request made while Devin is
|
|
52
|
+
// off is never served (or joined onto) a response fetched while it was on.
|
|
53
|
+
const cacheByDevinSelection = {
|
|
54
|
+
off: { data: null, expiresAtMs: 0 },
|
|
55
|
+
on: { data: null, expiresAtMs: 0 },
|
|
56
|
+
};
|
|
57
|
+
function devinSelectionKey(options) {
|
|
58
|
+
return options?.devinEnabled === true ? "on" : "off";
|
|
59
|
+
}
|
|
50
60
|
const CACHE_TTL_MS = 2 * 60 * 1000;
|
|
51
61
|
// Must stay below the macOS app's post-reset re-fetch grace (10s in
|
|
52
62
|
// DashboardViewModel.resetBoundaryGrace), or that targeted refresh would be
|
|
@@ -56,10 +66,10 @@ const DEFAULT_PROVIDER_TIMEOUT_MS = 15_000;
|
|
|
56
66
|
const ANTIGRAVITY_LIMITS_CACHE_FILE = "usage-limits-cache.json";
|
|
57
67
|
const ANTIGRAVITY_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
58
68
|
const ANTIGRAVITY_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS = 12 * 60 * 60 * 1000;
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
69
|
+
// Same client id PokeTokenBar and the agy binary embed. This client requires a
|
|
70
|
+
// client_secret; without it a refresh is rejected as 400 invalid_request, so
|
|
71
|
+
// remote renewal is unavailable. After expiry, quota depends on a local
|
|
72
|
+
// Antigravity/agy process or the user signing in again.
|
|
63
73
|
const ANTIGRAVITY_OAUTH_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
64
74
|
const ANTIGRAVITY_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
65
75
|
const ANTIGRAVITY_LOAD_CODE_ASSIST_URL = "https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist";
|
|
@@ -2573,22 +2583,44 @@ function resolveClaudeRateLimitPath({ home } = {}) {
|
|
|
2573
2583
|
return path.join(home || os.homedir(), ".tokentracker", "tracker", CLAUDE_RATE_LIMIT_FILE);
|
|
2574
2584
|
}
|
|
2575
2585
|
|
|
2586
|
+
function claudeTokenExpiryStamp(tokenExpiresAtMs) {
|
|
2587
|
+
return Number.isFinite(tokenExpiresAtMs)
|
|
2588
|
+
? new Date(tokenExpiresAtMs).toISOString()
|
|
2589
|
+
: null;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2576
2592
|
// Returns the cooldown expiry in ms if a 429 cooldown is still active, else null.
|
|
2577
|
-
|
|
2593
|
+
// A cooldown armed for a previous access token must not outlive that token: after
|
|
2594
|
+
// the user refreshes the Claude Code login, forceRefresh still cannot punch through
|
|
2595
|
+
// this file, so a cooldown stamped with a different token expiry is discarded.
|
|
2596
|
+
// The token's own expiry identifies the credential without persisting anything
|
|
2597
|
+
// derived from the secret.
|
|
2598
|
+
function readClaudeRateLimitRetryAtMs({ home, nowMs = Date.now(), tokenExpiresAtMs } = {}) {
|
|
2599
|
+
const cachePath = resolveClaudeRateLimitPath({ home });
|
|
2578
2600
|
try {
|
|
2579
|
-
const parsed = JSON.parse(fs.readFileSync(
|
|
2601
|
+
const parsed = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
2580
2602
|
const retryAtMs = parseTimeMs(parsed?.retry_at);
|
|
2581
|
-
if (retryAtMs
|
|
2603
|
+
if (retryAtMs === null || retryAtMs <= nowMs) return null;
|
|
2604
|
+
const stampedExpiry = typeof parsed.token_expires_at === "string"
|
|
2605
|
+
? parsed.token_expires_at
|
|
2606
|
+
: null;
|
|
2607
|
+
if (stampedExpiry && stampedExpiry !== claudeTokenExpiryStamp(tokenExpiresAtMs)) {
|
|
2608
|
+
clearClaudeRateLimitCooldown({ home });
|
|
2609
|
+
return null;
|
|
2610
|
+
}
|
|
2611
|
+
return retryAtMs;
|
|
2582
2612
|
} catch (_error) {}
|
|
2583
2613
|
return null;
|
|
2584
2614
|
}
|
|
2585
2615
|
|
|
2586
|
-
function writeClaudeRateLimitCooldown(retryAfterSec, { home, nowMs = Date.now() } = {}) {
|
|
2616
|
+
function writeClaudeRateLimitCooldown(retryAfterSec, { home, nowMs = Date.now(), tokenExpiresAtMs } = {}) {
|
|
2587
2617
|
const sec = Number.isFinite(retryAfterSec) && retryAfterSec > 0
|
|
2588
2618
|
? Math.min(retryAfterSec, CLAUDE_RATE_LIMIT_MAX_COOLDOWN_SEC)
|
|
2589
2619
|
: CLAUDE_RATE_LIMIT_DEFAULT_COOLDOWN_SEC;
|
|
2590
2620
|
const cachePath = resolveClaudeRateLimitPath({ home });
|
|
2591
2621
|
const payload = { retry_at: new Date(nowMs + sec * 1000).toISOString() };
|
|
2622
|
+
const expiryStamp = claudeTokenExpiryStamp(tokenExpiresAtMs);
|
|
2623
|
+
if (expiryStamp) payload.token_expires_at = expiryStamp;
|
|
2592
2624
|
try {
|
|
2593
2625
|
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
2594
2626
|
const tmpPath = `${cachePath}.${process.pid}.tmp`;
|
|
@@ -3127,18 +3159,41 @@ function parseAntigravityCredentialPayload(raw) {
|
|
|
3127
3159
|
};
|
|
3128
3160
|
}
|
|
3129
3161
|
|
|
3130
|
-
function
|
|
3162
|
+
function isAntigravityCredentialFresh(creds, nowMs) {
|
|
3163
|
+
return creds.expiryMs != null && creds.expiryMs > nowMs + ANTIGRAVITY_TOKEN_REFRESH_SKEW_MS;
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
function pickLatestAntigravityExpiry(candidates) {
|
|
3167
|
+
let best = candidates[0];
|
|
3168
|
+
for (let i = 1; i < candidates.length; i += 1) {
|
|
3169
|
+
const expiry = candidates[i].expiryMs;
|
|
3170
|
+
if (expiry != null && (best.expiryMs == null || expiry > best.expiryMs)) {
|
|
3171
|
+
best = candidates[i];
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
return best;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
function pickAntigravityCredentials(candidates, nowMs) {
|
|
3178
|
+
if (candidates.length === 0) return null;
|
|
3179
|
+
const fresh = candidates.filter((creds) => isAntigravityCredentialFresh(creds, nowMs));
|
|
3180
|
+
if (fresh.length > 0) return pickLatestAntigravityExpiry(fresh);
|
|
3181
|
+
const unknown = candidates.filter((creds) => creds.expiryMs == null);
|
|
3182
|
+
if (unknown.length > 0) return unknown[0];
|
|
3183
|
+
return pickLatestAntigravityExpiry(candidates);
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
function collectAntigravityFileCredentials({ home } = {}) {
|
|
3187
|
+
const candidates = [];
|
|
3131
3188
|
for (const credPath of listAntigravityCredentialPaths(home)) {
|
|
3132
3189
|
try {
|
|
3133
3190
|
const parsed = parseAntigravityCredentialPayload(fs.readFileSync(credPath, "utf8"));
|
|
3134
|
-
if (parsed) {
|
|
3135
|
-
return { ...parsed, source: "file", path: credPath };
|
|
3136
|
-
}
|
|
3191
|
+
if (parsed) candidates.push({ ...parsed, source: "file", path: credPath });
|
|
3137
3192
|
} catch {
|
|
3138
3193
|
// missing or unreadable
|
|
3139
3194
|
}
|
|
3140
3195
|
}
|
|
3141
|
-
return
|
|
3196
|
+
return candidates;
|
|
3142
3197
|
}
|
|
3143
3198
|
|
|
3144
3199
|
function readAntigravityKeychainRaw({ securityRunner, timeoutMs = 2000 } = {}) {
|
|
@@ -3169,14 +3224,18 @@ function readAntigravityKeychainRaw({ securityRunner, timeoutMs = 2000 } = {}) {
|
|
|
3169
3224
|
}
|
|
3170
3225
|
}
|
|
3171
3226
|
|
|
3172
|
-
function loadAntigravityCredentials({
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3227
|
+
function loadAntigravityCredentials({
|
|
3228
|
+
home,
|
|
3229
|
+
platform = process.platform,
|
|
3230
|
+
securityRunner,
|
|
3231
|
+
nowMs = Date.now(),
|
|
3232
|
+
} = {}) {
|
|
3233
|
+
const candidates = collectAntigravityFileCredentials({ home });
|
|
3234
|
+
if (platform === "darwin" || typeof securityRunner === "function") {
|
|
3235
|
+
const parsed = parseAntigravityCredentialPayload(readAntigravityKeychainRaw({ securityRunner }));
|
|
3236
|
+
if (parsed) candidates.push({ ...parsed, source: "keychain", path: null });
|
|
3237
|
+
}
|
|
3238
|
+
return pickAntigravityCredentials(candidates, nowMs);
|
|
3180
3239
|
}
|
|
3181
3240
|
|
|
3182
3241
|
function persistAntigravityCredentials(creds, next, { nowMs = Date.now() } = {}) {
|
|
@@ -3316,9 +3375,12 @@ async function fetchAntigravityRemoteLimits({
|
|
|
3316
3375
|
fetchImpl = fetch,
|
|
3317
3376
|
nowMs = Date.now(),
|
|
3318
3377
|
signal,
|
|
3378
|
+
creds,
|
|
3319
3379
|
} = {}) {
|
|
3320
|
-
const
|
|
3321
|
-
|
|
3380
|
+
const resolvedCreds = creds !== undefined
|
|
3381
|
+
? creds
|
|
3382
|
+
: loadAntigravityCredentials({ home, platform, securityRunner, nowMs });
|
|
3383
|
+
if (!resolvedCreds) return null;
|
|
3322
3384
|
|
|
3323
3385
|
const loadWithToken = async (accessToken) => {
|
|
3324
3386
|
const payload = await fetchAntigravityQuotaSummaryJson(fetchImpl, accessToken, signal);
|
|
@@ -3336,21 +3398,41 @@ async function fetchAntigravityRemoteLimits({
|
|
|
3336
3398
|
};
|
|
3337
3399
|
};
|
|
3338
3400
|
|
|
3339
|
-
let accessToken = await resolveAntigravityAccessToken(
|
|
3401
|
+
let accessToken = await resolveAntigravityAccessToken(resolvedCreds, { fetchImpl, nowMs, signal });
|
|
3340
3402
|
try {
|
|
3341
3403
|
return await loadWithToken(accessToken);
|
|
3342
3404
|
} catch (error) {
|
|
3343
|
-
if (error?.code !== "AUTH_EXPIRED" || !
|
|
3344
|
-
accessToken = await resolveAntigravityAccessToken(
|
|
3405
|
+
if (error?.code !== "AUTH_EXPIRED" || !resolvedCreds.refreshToken) throw error;
|
|
3406
|
+
accessToken = await resolveAntigravityAccessToken(resolvedCreds, {
|
|
3407
|
+
fetchImpl,
|
|
3408
|
+
nowMs,
|
|
3409
|
+
forceRefresh: true,
|
|
3410
|
+
signal,
|
|
3411
|
+
});
|
|
3345
3412
|
return await loadWithToken(accessToken);
|
|
3346
3413
|
}
|
|
3347
3414
|
}
|
|
3348
3415
|
|
|
3349
|
-
function
|
|
3416
|
+
function antigravityCredentialsNeedReauth(creds, { nowMs, remoteError } = {}) {
|
|
3417
|
+
if (remoteError?.code === "AUTH_EXPIRED") return true;
|
|
3418
|
+
return Boolean(
|
|
3419
|
+
creds
|
|
3420
|
+
&& creds.expiryMs != null
|
|
3421
|
+
&& creds.expiryMs <= nowMs + ANTIGRAVITY_TOKEN_REFRESH_SKEW_MS,
|
|
3422
|
+
);
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
function antigravityUnavailableResult({ home, nowMs, platform, securityRunner, remoteError, creds } = {}) {
|
|
3350
3426
|
const cached = readAntigravityLimitsCache({ home, nowMs });
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3427
|
+
const resolvedCreds = creds !== undefined
|
|
3428
|
+
? creds
|
|
3429
|
+
: loadAntigravityCredentials({ home, platform, securityRunner, nowMs });
|
|
3430
|
+
if (cached) {
|
|
3431
|
+
return antigravityCredentialsNeedReauth(resolvedCreds, { nowMs, remoteError })
|
|
3432
|
+
? { ...cached, auth_action_required: "reauth" }
|
|
3433
|
+
: cached;
|
|
3434
|
+
}
|
|
3435
|
+
if (!hasAntigravityInstallEvidence({ home }) && !resolvedCreds) {
|
|
3354
3436
|
return { configured: false };
|
|
3355
3437
|
}
|
|
3356
3438
|
if (remoteError) {
|
|
@@ -3360,7 +3442,7 @@ function antigravityUnavailableResult({ home, nowMs, platform, securityRunner, r
|
|
|
3360
3442
|
: raw;
|
|
3361
3443
|
return { configured: true, error: message };
|
|
3362
3444
|
}
|
|
3363
|
-
if (
|
|
3445
|
+
if (resolvedCreds) {
|
|
3364
3446
|
return { configured: true, error: ANTIGRAVITY_AUTH_EXPIRED_MESSAGE };
|
|
3365
3447
|
}
|
|
3366
3448
|
return { configured: true, error: ANTIGRAVITY_NOT_RUNNING_MESSAGE };
|
|
@@ -3389,6 +3471,7 @@ async function fetchAntigravityLimits({
|
|
|
3389
3471
|
securityRunner,
|
|
3390
3472
|
signal,
|
|
3391
3473
|
} = {}) {
|
|
3474
|
+
const creds = loadAntigravityCredentials({ home, platform, securityRunner, nowMs });
|
|
3392
3475
|
const startedAtMs = performance.now();
|
|
3393
3476
|
// min(this step's ceiling, budget left after reserving the fallback guard).
|
|
3394
3477
|
// 0 means "no time left" — the caller must skip the call, not issue it.
|
|
@@ -3429,7 +3512,15 @@ async function fetchAntigravityLimits({
|
|
|
3429
3512
|
if (remoteTimeoutMs > 0) {
|
|
3430
3513
|
try {
|
|
3431
3514
|
const remote = await withProviderTimeout(
|
|
3432
|
-
fetchAntigravityRemoteLimits({
|
|
3515
|
+
fetchAntigravityRemoteLimits({
|
|
3516
|
+
home,
|
|
3517
|
+
platform,
|
|
3518
|
+
securityRunner,
|
|
3519
|
+
fetchImpl,
|
|
3520
|
+
nowMs,
|
|
3521
|
+
signal,
|
|
3522
|
+
creds,
|
|
3523
|
+
}),
|
|
3433
3524
|
"Antigravity",
|
|
3434
3525
|
remoteTimeoutMs,
|
|
3435
3526
|
);
|
|
@@ -3454,7 +3545,14 @@ async function fetchAntigravityLimits({
|
|
|
3454
3545
|
signal,
|
|
3455
3546
|
});
|
|
3456
3547
|
if (!processInfo.configured) {
|
|
3457
|
-
return antigravityUnavailableResult({
|
|
3548
|
+
return antigravityUnavailableResult({
|
|
3549
|
+
home,
|
|
3550
|
+
nowMs,
|
|
3551
|
+
platform,
|
|
3552
|
+
securityRunner,
|
|
3553
|
+
remoteError,
|
|
3554
|
+
creds,
|
|
3555
|
+
});
|
|
3458
3556
|
}
|
|
3459
3557
|
if (processInfo.error) {
|
|
3460
3558
|
return { configured: true, error: processInfo.error };
|
|
@@ -3560,6 +3658,7 @@ async function fetchAntigravityLimits({
|
|
|
3560
3658
|
platform,
|
|
3561
3659
|
securityRunner,
|
|
3562
3660
|
remoteError: remoteError || error,
|
|
3661
|
+
creds,
|
|
3563
3662
|
});
|
|
3564
3663
|
}
|
|
3565
3664
|
}
|
|
@@ -3596,7 +3695,7 @@ function withPlanLabel(obj, raw, brand) {
|
|
|
3596
3695
|
// hammered). Survives an external resetUsageLimitsCache() (refresh=1 path in
|
|
3597
3696
|
// local-api.js): a refresh arriving while a fetch is already running reuses that
|
|
3598
3697
|
// in-flight fetch and returns its result.
|
|
3599
|
-
|
|
3698
|
+
const inFlightByDevinSelection = { off: null, on: null };
|
|
3600
3699
|
|
|
3601
3700
|
// Codex stamps reset_at as unix seconds; every other provider (and Claude's
|
|
3602
3701
|
// resets_at) uses ISO strings. Numbers that look like epoch milliseconds are
|
|
@@ -3634,17 +3733,19 @@ function cacheExpiresAtMs(data, fetchedAtMs) {
|
|
|
3634
3733
|
}
|
|
3635
3734
|
|
|
3636
3735
|
async function getUsageLimits(options = {}) {
|
|
3736
|
+
const selection = devinSelectionKey(options);
|
|
3737
|
+
const cache = cacheByDevinSelection[selection];
|
|
3637
3738
|
const nowMs = Date.now();
|
|
3638
3739
|
if (cache.data && nowMs < cache.expiresAtMs) {
|
|
3639
3740
|
return cache.data;
|
|
3640
3741
|
}
|
|
3641
|
-
if (
|
|
3642
|
-
return
|
|
3742
|
+
if (inFlightByDevinSelection[selection]) {
|
|
3743
|
+
return inFlightByDevinSelection[selection];
|
|
3643
3744
|
}
|
|
3644
3745
|
const promise = fetchUsageLimitsUncached(options).finally(() => {
|
|
3645
|
-
if (
|
|
3746
|
+
if (inFlightByDevinSelection[selection] === promise) inFlightByDevinSelection[selection] = null;
|
|
3646
3747
|
});
|
|
3647
|
-
|
|
3748
|
+
inFlightByDevinSelection[selection] = promise;
|
|
3648
3749
|
return promise;
|
|
3649
3750
|
}
|
|
3650
3751
|
|
|
@@ -3659,14 +3760,17 @@ async function fetchUsageLimitsUncached({
|
|
|
3659
3760
|
now = new Date(),
|
|
3660
3761
|
providerTimeoutMs = DEFAULT_PROVIDER_TIMEOUT_MS,
|
|
3661
3762
|
forceRefresh = false,
|
|
3763
|
+
devinEnabled = false,
|
|
3662
3764
|
} = {}) {
|
|
3663
3765
|
const nowMs = Date.now();
|
|
3664
3766
|
|
|
3665
|
-
const [
|
|
3666
|
-
Promise.resolve().then(() =>
|
|
3767
|
+
const [claudeOauth, claudeSubscription, codexAuth] = await Promise.all([
|
|
3768
|
+
Promise.resolve().then(() => readClaudeCodeOauthToken({ platform, securityRunner, home, nowMs })),
|
|
3667
3769
|
Promise.resolve().then(() => detectClaudeCodeSubscriptionDetails({ platform, securityRunner, home })),
|
|
3668
3770
|
readCodexAuthBundle({ home, env }),
|
|
3669
3771
|
]);
|
|
3772
|
+
const claudeToken = claudeOauth?.accessToken || null;
|
|
3773
|
+
const claudeTokenExpiresAtMs = claudeOauth?.expiresAtMs ?? null;
|
|
3670
3774
|
const claudePlanType = claudeSubscription?.planType || null;
|
|
3671
3775
|
|
|
3672
3776
|
// Match the official Codex CLI: prefer the access token's JWT expiry and refresh only
|
|
@@ -3705,7 +3809,9 @@ async function fetchUsageLimitsUncached({
|
|
|
3705
3809
|
|
|
3706
3810
|
// Skip the upstream Claude call entirely while a 429 cooldown is active — calling again
|
|
3707
3811
|
// just renews the penalty. The result handling below serves cache or a cooldown message.
|
|
3708
|
-
const claudeRetryAtMs = claudeToken
|
|
3812
|
+
const claudeRetryAtMs = claudeToken
|
|
3813
|
+
? readClaudeRateLimitRetryAtMs({ home, nowMs, tokenExpiresAtMs: claudeTokenExpiresAtMs })
|
|
3814
|
+
: null;
|
|
3709
3815
|
// Also avoid cross-process hammering after a recent successful read: embedded-server
|
|
3710
3816
|
// restarts and background polls read the disk cache instead of spending another Claude
|
|
3711
3817
|
// OAuth usage request. An explicit user refresh (refresh=1 → forceRefresh) punches
|
|
@@ -3716,7 +3822,7 @@ async function fetchUsageLimitsUncached({
|
|
|
3716
3822
|
: null;
|
|
3717
3823
|
|
|
3718
3824
|
const providerFetch = withFetchTimeout(fetchImpl, providerTimeoutMs);
|
|
3719
|
-
const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGoRaw, qoder, qoderCn, codingPlan, agentPlan, claudeServiceStatus] = await Promise.all([
|
|
3825
|
+
const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGoRaw, qoder, qoderCn, codingPlan, agentPlan, commandCodeRaw, devinRaw, claudeServiceStatus] = await Promise.all([
|
|
3720
3826
|
claudeToken && !freshClaudeCache && !claudeRetryAtMs
|
|
3721
3827
|
? withProviderTimeout(fetchClaudeUsageLimits(claudeToken, { fetchImpl: providerFetch, maxAttempts: 1 }), "Claude", providerTimeoutMs).then(
|
|
3722
3828
|
(value) => ({ status: "fulfilled", value }),
|
|
@@ -3817,6 +3923,29 @@ async function fetchUsageLimitsUncached({
|
|
|
3817
3923
|
"Ark Agent Plan",
|
|
3818
3924
|
providerTimeoutMs,
|
|
3819
3925
|
).catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
3926
|
+
// CommandCode (commandcode.ai): official subscription windows (5h + weekly)
|
|
3927
|
+
// from the CLI's own alpha endpoints, keyed by the apiKey the CLI stores in
|
|
3928
|
+
// ~/.commandcode/auth.json (or COMMAND_CODE_API_KEY). No local fallback —
|
|
3929
|
+
// window state lives server-side. fetchCommandcodeLimits throws on auth
|
|
3930
|
+
// expiry so the assemble step below can flag auth_action_required. The
|
|
3931
|
+
// outer race bounds the whole slot: without it three sequential per-request
|
|
3932
|
+
// timeouts (whoami, then credits+subscriptions) could hold the aggregate
|
|
3933
|
+
// ~2x longer than any sibling provider.
|
|
3934
|
+
withProviderTimeout(fetchCommandcodeLimits({ home, env, fetchImpl: providerFetch }), "CommandCode", providerTimeoutMs)
|
|
3935
|
+
.then(
|
|
3936
|
+
(value) => ({ status: "fulfilled", value }),
|
|
3937
|
+
(reason) => ({ status: "rejected", reason }),
|
|
3938
|
+
),
|
|
3939
|
+
// Devin (devin.ai): daily/weekly subscription quota from the official
|
|
3940
|
+
// GetPlanStatus RPC, keyed by the session token the Devin CLI stores in
|
|
3941
|
+
// ~/.local/share/devin/credentials.toml. No local fallback — window state
|
|
3942
|
+
// lives server-side. fetchDevinLimits throws on auth expiry so the
|
|
3943
|
+
// assemble step below can flag auth_action_required.
|
|
3944
|
+
withProviderTimeout(fetchDevinLimits({ home, env, enabled: devinEnabled === true, fetchImpl: providerFetch }), "Devin", providerTimeoutMs)
|
|
3945
|
+
.then(
|
|
3946
|
+
(value) => ({ status: "fulfilled", value }),
|
|
3947
|
+
(reason) => ({ status: "rejected", reason }),
|
|
3948
|
+
),
|
|
3820
3949
|
// Public status-page probe (fail-soft, own 5-min cache in provider-status.js).
|
|
3821
3950
|
// Only probed for configured accounts — without a token the Claude section
|
|
3822
3951
|
// never renders, so the reading would have nowhere to go.
|
|
@@ -3864,13 +3993,21 @@ async function fetchUsageLimitsUncached({
|
|
|
3864
3993
|
// surface an accurate "retry in ~Nm" message rather than the misleading hardcoded one.
|
|
3865
3994
|
const reason = claudeResult?.reason;
|
|
3866
3995
|
if (reason?.code === "RATE_LIMITED") {
|
|
3867
|
-
writeClaudeRateLimitCooldown(reason.retryAfterSec, {
|
|
3996
|
+
writeClaudeRateLimitCooldown(reason.retryAfterSec, {
|
|
3997
|
+
home,
|
|
3998
|
+
nowMs,
|
|
3999
|
+
tokenExpiresAtMs: claudeTokenExpiresAtMs,
|
|
4000
|
+
});
|
|
3868
4001
|
}
|
|
3869
4002
|
const cached = readClaudeLimitsCache({ home, nowMs });
|
|
3870
4003
|
if (cached) {
|
|
3871
4004
|
claude = cached;
|
|
3872
4005
|
} else {
|
|
3873
|
-
const retryAtMs = readClaudeRateLimitRetryAtMs({
|
|
4006
|
+
const retryAtMs = readClaudeRateLimitRetryAtMs({
|
|
4007
|
+
home,
|
|
4008
|
+
nowMs,
|
|
4009
|
+
tokenExpiresAtMs: claudeTokenExpiresAtMs,
|
|
4010
|
+
}) || claudeRetryAtMs;
|
|
3874
4011
|
claude = {
|
|
3875
4012
|
configured: true,
|
|
3876
4013
|
error: retryAtMs
|
|
@@ -3893,7 +4030,11 @@ async function fetchUsageLimitsUncached({
|
|
|
3893
4030
|
// cool-down just armed by this cycle's 429 is included; a successful read above
|
|
3894
4031
|
// cleared the file, so this is null in the happy path.
|
|
3895
4032
|
if (claude.configured) {
|
|
3896
|
-
const claudeCooldownMs = readClaudeRateLimitRetryAtMs({
|
|
4033
|
+
const claudeCooldownMs = readClaudeRateLimitRetryAtMs({
|
|
4034
|
+
home,
|
|
4035
|
+
nowMs,
|
|
4036
|
+
tokenExpiresAtMs: claudeTokenExpiresAtMs,
|
|
4037
|
+
});
|
|
3897
4038
|
if (claudeCooldownMs) {
|
|
3898
4039
|
claude.retry_at = new Date(claudeCooldownMs).toISOString();
|
|
3899
4040
|
}
|
|
@@ -3993,6 +4134,44 @@ async function fetchUsageLimitsUncached({
|
|
|
3993
4134
|
}
|
|
3994
4135
|
}
|
|
3995
4136
|
|
|
4137
|
+
// CommandCode: official windows straight from the API — nothing to cache on
|
|
4138
|
+
// disk beyond the shared 2-minute in-memory cache, so keep the assembly
|
|
4139
|
+
// simple. A fulfilled `configured: false` means no apiKey found; a rejected
|
|
4140
|
+
// fetch surfaced an auth-expired error the panel should act on.
|
|
4141
|
+
let commandCodeObj;
|
|
4142
|
+
if (commandCodeRaw?.status === "fulfilled") {
|
|
4143
|
+
const value = commandCodeRaw.value;
|
|
4144
|
+
commandCodeObj = value && value.configured === false
|
|
4145
|
+
? value
|
|
4146
|
+
: { ...value, stale: false, cached_at: new Date(nowMs).toISOString() };
|
|
4147
|
+
} else {
|
|
4148
|
+
const reason = commandCodeRaw?.reason || null;
|
|
4149
|
+
const authExpired = Boolean(
|
|
4150
|
+
reason &&
|
|
4151
|
+
(reason?.code === "AUTH_EXPIRED" ||
|
|
4152
|
+
/token expired|run `cmd login`|Not authenticated/i.test(reason?.message || "")),
|
|
4153
|
+
);
|
|
4154
|
+
commandCodeObj = authExpired
|
|
4155
|
+
? { configured: true, error: reason?.message || "Unknown error", auth_action_required: "reauth" }
|
|
4156
|
+
: { configured: true, error: reason?.message || "Unknown error" };
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
// Devin: server-owned quota windows like CommandCode — a fulfilled
|
|
4160
|
+
// `configured: false` means no CLI credentials; a rejected fetch with
|
|
4161
|
+
// AUTH_EXPIRED flags auth_action_required for the re-sign-in hint.
|
|
4162
|
+
let devinObj;
|
|
4163
|
+
if (devinRaw?.status === "fulfilled") {
|
|
4164
|
+
const value = devinRaw.value;
|
|
4165
|
+
devinObj = value && value.configured === false
|
|
4166
|
+
? value
|
|
4167
|
+
: { ...value, stale: false, cached_at: new Date(nowMs).toISOString() };
|
|
4168
|
+
} else {
|
|
4169
|
+
const reason = devinRaw?.reason || null;
|
|
4170
|
+
devinObj = reason?.code === "AUTH_EXPIRED"
|
|
4171
|
+
? { configured: true, error: reason?.message || "Unknown error", auth_action_required: "reauth" }
|
|
4172
|
+
: { configured: true, error: reason?.message || "Unknown error" };
|
|
4173
|
+
}
|
|
4174
|
+
|
|
3996
4175
|
const data = {
|
|
3997
4176
|
fetched_at: new Date(nowMs).toISOString(),
|
|
3998
4177
|
claude: withPlanLabel(claude, claudePlanType, "Claude"),
|
|
@@ -4010,6 +4189,13 @@ async function fetchUsageLimitsUncached({
|
|
|
4010
4189
|
grok: withPlanLabel(grok, null, "Grok"),
|
|
4011
4190
|
zcode: withPlanLabel(zcode, zcode.plan_label, "ZCode"),
|
|
4012
4191
|
opencodeGo: withPlanLabel(opencodeGo, opencodeGo?.plan_label, "OpenCode Go"),
|
|
4192
|
+
// CommandCode tiers are acronym brands (GOAT/Pro/Max) — the fetcher already
|
|
4193
|
+
// maps plan ids to the CLI's exact display strings, so skip the shared
|
|
4194
|
+
// Title-Case normalization ("Goat") and surface them as-is.
|
|
4195
|
+
commandCode: commandCodeObj,
|
|
4196
|
+
// Devin's planName ("Pro", "Max") is already the official display name —
|
|
4197
|
+
// Title-Case normalization is still harmless and strips any brand prefix.
|
|
4198
|
+
devin: withPlanLabel(devinObj, devinObj?.plan_label, "Devin"),
|
|
4013
4199
|
qoder: withPlanLabel(qoder, qoder?.plan_label, "Qoder"),
|
|
4014
4200
|
qoderCn: withPlanLabel(qoderCn, qoderCn?.plan_label, "Qoder CN"),
|
|
4015
4201
|
codingPlan: withPlanLabel(codingPlan, codingPlan?.plan_label, "Ark Coding Plan"),
|
|
@@ -4037,12 +4223,16 @@ async function fetchUsageLimitsUncached({
|
|
|
4037
4223
|
};
|
|
4038
4224
|
}
|
|
4039
4225
|
|
|
4040
|
-
|
|
4226
|
+
cacheByDevinSelection[devinSelectionKey({ devinEnabled })] = {
|
|
4227
|
+
data,
|
|
4228
|
+
expiresAtMs: cacheExpiresAtMs(data, nowMs),
|
|
4229
|
+
};
|
|
4041
4230
|
return data;
|
|
4042
4231
|
}
|
|
4043
4232
|
|
|
4044
4233
|
function resetUsageLimitsCache() {
|
|
4045
|
-
|
|
4234
|
+
cacheByDevinSelection.off = { data: null, expiresAtMs: 0 };
|
|
4235
|
+
cacheByDevinSelection.on = { data: null, expiresAtMs: 0 };
|
|
4046
4236
|
}
|
|
4047
4237
|
|
|
4048
4238
|
module.exports = {
|
|
@@ -4080,6 +4270,7 @@ module.exports = {
|
|
|
4080
4270
|
fetchGrokLimits,
|
|
4081
4271
|
fetchZcodeLimits,
|
|
4082
4272
|
fetchOpencodeGoLimits,
|
|
4273
|
+
fetchCommandcodeLimits,
|
|
4083
4274
|
fetchQoderLimits,
|
|
4084
4275
|
fetchQoderCnLimits,
|
|
4085
4276
|
};
|