tokentracker-cli 0.96.2 → 0.96.4
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-CgEitoX1.js → AchievementBadge-CCcvmz6R.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-dnnea0JH.js → AchievementsPage-C5jdktOK.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-CJqG0UP7.js → AchievementsSection-BFMtUzn1.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-BLV6SJoI.js → BadgeDetailModal-Be8gM7tM.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-Cm78yEbz.js → BotAnimated-CXTGR71z.js} +1 -1
- package/dashboard/dist/assets/{Card-IPGSYT6O.js → Card-CsUsp3CV.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-04w1U1sm.js → ClawdAnimated-BEjpZjEE.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-gSSa1V_s.js → CommandPalette-CHc_Z1r8.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-CICwUz14.js → CommunityStatsModal-Gs6kZ_Mp.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-DaWC0cY7.js → ConfirmModal-niHkpv2O.js} +1 -1
- package/dashboard/dist/assets/{Controls-BDihA03F.js → Controls-X2Z1UNUx.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-B66s3Exi.js +1 -0
- package/dashboard/dist/assets/{DevicePage-BPpo_eiY.js → DevicePage-DQdLdEI8.js} +1 -1
- package/dashboard/dist/assets/DialogClose-CCUjTxl8.js +1 -0
- package/dashboard/dist/assets/DialogDescription-CjMjYG3N.js +1 -0
- package/dashboard/dist/assets/DialogTitle-CgkUGAag.js +1 -0
- package/dashboard/dist/assets/{FadeIn-DmI9PRLa.js → FadeIn-D4QD1bPo.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CBO4cY7i.js → HeaderGithubStar-D0my-Z3a.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-CPFGhi9K.js → HoverTooltip-nSWgrKlK.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-Do7w_6z6.js → IpCheckPage-4hfqHAgi.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-at_Ti1ZE.js → LandingPage-wIc4tVRI.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-BH1cnKPp.js → LeaderboardAvatar-DGlN8Vy8.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-Cd30aXwL.js → LeaderboardPage-CybuzE87.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-CVsti_HN.js → LeaderboardProfileModal-D9la3tNd.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-BfSpDM9e.js → LeaderboardProfilePage-gxXSeJYb.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-CZH3G54Y.js +8 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-9-vRgsdn.js → LocalOnlyNotice-Ckc7zohC.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-DM_ZdFNl.js → LoginCard-BqeRHqWK.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-BaNMznwh.js → LoginModal-BjH6aIBG.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BMMthUf4.js → LoginPage-BOyPLNU3.js} +1 -1
- package/dashboard/dist/assets/PetPage-BXRXG40D.js +1 -0
- package/dashboard/dist/assets/PopoverPopup-Dad1ZYVd.js +1 -0
- package/dashboard/dist/assets/ProviderIcon-BDmrNJSl.js +1 -0
- package/dashboard/dist/assets/{ResetPasswordPage-Ca4SULSV.js → ResetPasswordPage-CZpE6qfa.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-Bbsn-qvZ.js → SegmentedControl-CE1hrQFi.js} +1 -1
- package/dashboard/dist/assets/Select-YVj7u6Nv.js +1 -0
- package/dashboard/dist/assets/SelectItemText-BMYGgQ3o.js +1 -0
- package/dashboard/dist/assets/{ServiceStatusPage-BPS8KJ3a.js → ServiceStatusPage-HehjDbZg.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-C1yt3TMZ.js → SessionsPage-Chvu4YqZ.js} +2 -2
- package/dashboard/dist/assets/SettingsPage-BXjS7u9_.js +2 -0
- package/dashboard/dist/assets/SkillsPage-q3Rjlj9h.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-DKvbJbnv.js → TokenGalaxy-BqZyj82r.js} +1 -1
- package/dashboard/dist/assets/ToolbarRootContext-Ch8ueI_C.js +1 -0
- package/dashboard/dist/assets/{TrendMonitor-YF5C_-L1.js → TrendMonitor-C2aTL9p1.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BKV3DgXg.js → WidgetsPage-DYaIai8j.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-s4IRoLf7.js → WrappedPage-51B0v-Sr.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-Cp7dNuCm.js +45 -0
- package/dashboard/dist/assets/{agent-logos-Hbt-wyl2.js → agent-logos-DXrPSAdX.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-KeIpKlw3.js → arrow-up-right-BFkf7l0U.js} +1 -1
- package/dashboard/dist/assets/{check-_QKyY1v_.js → check--N8wmgIJ.js} +1 -1
- package/dashboard/dist/assets/composite-zF5Pj94e.js +1 -0
- package/dashboard/dist/assets/{copy-DpLM3QMD.js → copy-BG0JMr6A.js} +1 -1
- package/dashboard/dist/assets/{download-C4NX-JnW.js → download-CPFJf-Sf.js} +1 -1
- package/dashboard/dist/assets/{external-link-CG-7FcOF.js → external-link-D2yF0kgP.js} +1 -1
- package/dashboard/dist/assets/{flame-CkWid7Ji.js → flame-l460AFx9.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-CbwWhioZ.js → format-tokens-DKO4ljuH.js} +1 -1
- package/dashboard/dist/assets/{icons-BZq17rU3.js → icons-RoxRGkz4.js} +1 -1
- package/dashboard/dist/assets/{index-CR9tDiiC.js → index--ouT5L4N.js} +1 -1
- package/dashboard/dist/assets/{index-DDGLE0dz.js → index-DhyLCvvv.js} +1 -1
- package/dashboard/dist/assets/{index-CSwEiUn4.js → index-PTJ4368-.js} +1 -1
- package/dashboard/dist/assets/{info-mkeRjzqc.js → info-DNWMO4Ca.js} +1 -1
- package/dashboard/dist/assets/{link-2-BjMw8csC.js → link-2-DzWXAz1h.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-cGWOJnWn.js → loader-circle-BzGMqXNu.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-Cg_S73tD.js → maximize-2-Dkpyk4x3.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-DNxTWxId.js → modifiers.esm-lggHUQpW.js} +1 -1
- package/dashboard/dist/assets/module-DUSXqJtA.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-BvE9Tt2U.js → monitor-smartphone-Ck0-gz9l.js} +1 -1
- package/dashboard/dist/assets/{plus-BC2wdPRr.js → plus-qTJpnn43.js} +1 -1
- package/dashboard/dist/assets/provider-display-psY26qmp.js +42 -0
- package/dashboard/dist/assets/{react-D0kcslTz.js → react-CYS24KmX.js} +1 -1
- package/dashboard/dist/assets/{search-BuBNF3E3.js → search-CUzE6zxr.js} +1 -1
- package/dashboard/dist/assets/{skills-api-DxAN5pNx.js → skills-api-D9SzxPFG.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-DoqMcEX-.js → sortable.esm-Bl2hAUyR.js} +1 -1
- package/dashboard/dist/assets/store-mHSJAJtE.js +1 -0
- package/dashboard/dist/assets/{terminal-COWFUJdU.js → terminal-BlQNEvn1.js} +1 -1
- package/dashboard/dist/assets/{trash-2-Cqjki3RI.js → trash-CHdgmOyk.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-DsrlOPDM.js → use-community-stats-DUiCs91N.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-ryRFL2gx.js → use-latest-request-guard-ehXfkvuU.js} +1 -1
- package/dashboard/dist/assets/use-limits-display-prefs-CcwwQ0Fq.js +1 -0
- package/dashboard/dist/assets/{use-ordered-list-D-Q86R1b.js → use-ordered-list-CCOtI3Wa.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-CDlahPqe.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-CrNz45y7.js → use-session-efficiency-pref-CYRDpcc_.js} +1 -1
- package/dashboard/dist/assets/{use-transform-CoGM21px.js → use-transform-btLlTJpj.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-BGr5sR-c.js +1 -0
- package/dashboard/dist/assets/{useCurrency-CgCfDktb.js → useCurrency-CNutuEKd.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-QKtzCKTL.js → useTokenFormat-C0bAJFMC.js} +1 -1
- package/dashboard/dist/assets/{zap-CTIobzgU.js → zap-B2fbyjbc.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/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/devin-limits.js +249 -0
- package/src/lib/devin-usage.js +212 -0
- package/src/lib/local-api.js +19 -0
- package/src/lib/passive-mode.js +13 -1
- package/src/lib/pricing/curated-overrides.json +67 -1
- package/src/lib/pricing/index.js +5 -2
- package/src/lib/pricing/matcher.js +28 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1310 -144
- package/src/lib/skills-manager.js +231 -30
- package/src/lib/subscriptions.js +70 -16
- package/src/lib/usage-limits.js +199 -49
- package/dashboard/dist/assets/DashboardPage-hV7wncKF.js +0 -1
- package/dashboard/dist/assets/DialogClose-C_dF-r3s.js +0 -1
- package/dashboard/dist/assets/DialogDescription-GClOSCaE.js +0 -1
- package/dashboard/dist/assets/DialogTitle-Buh_5688.js +0 -1
- package/dashboard/dist/assets/LimitsPage-_-BadDAj.js +0 -8
- package/dashboard/dist/assets/PetPage-Iq8Hswai.js +0 -1
- package/dashboard/dist/assets/PopoverPopup-DXr3JxVV.js +0 -1
- package/dashboard/dist/assets/ProviderIcon-sb2keSY2.js +0 -1
- package/dashboard/dist/assets/Select-vD-yj-qa.js +0 -1
- package/dashboard/dist/assets/SelectItemText-CRtFEuvg.js +0 -1
- package/dashboard/dist/assets/SettingsPage-CWVHrMer.js +0 -2
- package/dashboard/dist/assets/SkillsPage-DGWEH1xm.js +0 -1
- package/dashboard/dist/assets/ToolbarRootContext-D8yov1dW.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-_LUatzwU.js +0 -45
- package/dashboard/dist/assets/composite-BidfcMeb.js +0 -1
- package/dashboard/dist/assets/limits-providers-DRkcWQup.js +0 -1
- package/dashboard/dist/assets/module-xpSFyNBK.js +0 -3
- package/dashboard/dist/assets/provider-display-B7GggHQy.js +0 -42
- package/dashboard/dist/assets/store-Cg1HeUFM.js +0 -1
- package/dashboard/dist/assets/use-limits-display-prefs-DkSuu12m.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-D7NNqavo.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-C_6L1sO5.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");
|
|
@@ -31,6 +31,7 @@ const { fetchGrokLimits } = require("./grok-limits");
|
|
|
31
31
|
const { fetchZcodeLimits } = require("./zcode-limits");
|
|
32
32
|
const { fetchOpencodeGoLimits } = require("./opencode-go-limits");
|
|
33
33
|
const { fetchCommandcodeLimits } = require("./commandcode-limits");
|
|
34
|
+
const { fetchDevinLimits } = require("./devin-limits");
|
|
34
35
|
const { fetchQoderLimits, fetchQoderCnLimits } = require("./qoder-limits");
|
|
35
36
|
const { fetchArkCodingPlanLimits } = require("./ark-coding-plan-limits");
|
|
36
37
|
const { fetchArkAgentPlanLimits } = require("./ark-agent-plan-limits");
|
|
@@ -47,7 +48,15 @@ const execFileAsync = promisify(cp.execFile);
|
|
|
47
48
|
// 2-minute in-memory cache. It also expires early at the earliest upcoming window
|
|
48
49
|
// reset in the cached data (see cacheExpiresAtMs), floored so a provider reporting
|
|
49
50
|
// a reset "right now" can't turn every poll into a full upstream round.
|
|
50
|
-
|
|
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
|
+
}
|
|
51
60
|
const CACHE_TTL_MS = 2 * 60 * 1000;
|
|
52
61
|
// Must stay below the macOS app's post-reset re-fetch grace (10s in
|
|
53
62
|
// DashboardViewModel.resetBoundaryGrace), or that targeted refresh would be
|
|
@@ -57,10 +66,10 @@ const DEFAULT_PROVIDER_TIMEOUT_MS = 15_000;
|
|
|
57
66
|
const ANTIGRAVITY_LIMITS_CACHE_FILE = "usage-limits-cache.json";
|
|
58
67
|
const ANTIGRAVITY_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
59
68
|
const ANTIGRAVITY_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS = 12 * 60 * 60 * 1000;
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
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.
|
|
64
73
|
const ANTIGRAVITY_OAUTH_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
65
74
|
const ANTIGRAVITY_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
66
75
|
const ANTIGRAVITY_LOAD_CODE_ASSIST_URL = "https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist";
|
|
@@ -2574,22 +2583,44 @@ function resolveClaudeRateLimitPath({ home } = {}) {
|
|
|
2574
2583
|
return path.join(home || os.homedir(), ".tokentracker", "tracker", CLAUDE_RATE_LIMIT_FILE);
|
|
2575
2584
|
}
|
|
2576
2585
|
|
|
2586
|
+
function claudeTokenExpiryStamp(tokenExpiresAtMs) {
|
|
2587
|
+
return Number.isFinite(tokenExpiresAtMs)
|
|
2588
|
+
? new Date(tokenExpiresAtMs).toISOString()
|
|
2589
|
+
: null;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2577
2592
|
// Returns the cooldown expiry in ms if a 429 cooldown is still active, else null.
|
|
2578
|
-
|
|
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 });
|
|
2579
2600
|
try {
|
|
2580
|
-
const parsed = JSON.parse(fs.readFileSync(
|
|
2601
|
+
const parsed = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
2581
2602
|
const retryAtMs = parseTimeMs(parsed?.retry_at);
|
|
2582
|
-
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;
|
|
2583
2612
|
} catch (_error) {}
|
|
2584
2613
|
return null;
|
|
2585
2614
|
}
|
|
2586
2615
|
|
|
2587
|
-
function writeClaudeRateLimitCooldown(retryAfterSec, { home, nowMs = Date.now() } = {}) {
|
|
2616
|
+
function writeClaudeRateLimitCooldown(retryAfterSec, { home, nowMs = Date.now(), tokenExpiresAtMs } = {}) {
|
|
2588
2617
|
const sec = Number.isFinite(retryAfterSec) && retryAfterSec > 0
|
|
2589
2618
|
? Math.min(retryAfterSec, CLAUDE_RATE_LIMIT_MAX_COOLDOWN_SEC)
|
|
2590
2619
|
: CLAUDE_RATE_LIMIT_DEFAULT_COOLDOWN_SEC;
|
|
2591
2620
|
const cachePath = resolveClaudeRateLimitPath({ home });
|
|
2592
2621
|
const payload = { retry_at: new Date(nowMs + sec * 1000).toISOString() };
|
|
2622
|
+
const expiryStamp = claudeTokenExpiryStamp(tokenExpiresAtMs);
|
|
2623
|
+
if (expiryStamp) payload.token_expires_at = expiryStamp;
|
|
2593
2624
|
try {
|
|
2594
2625
|
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
2595
2626
|
const tmpPath = `${cachePath}.${process.pid}.tmp`;
|
|
@@ -3128,18 +3159,41 @@ function parseAntigravityCredentialPayload(raw) {
|
|
|
3128
3159
|
};
|
|
3129
3160
|
}
|
|
3130
3161
|
|
|
3131
|
-
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 = [];
|
|
3132
3188
|
for (const credPath of listAntigravityCredentialPaths(home)) {
|
|
3133
3189
|
try {
|
|
3134
3190
|
const parsed = parseAntigravityCredentialPayload(fs.readFileSync(credPath, "utf8"));
|
|
3135
|
-
if (parsed) {
|
|
3136
|
-
return { ...parsed, source: "file", path: credPath };
|
|
3137
|
-
}
|
|
3191
|
+
if (parsed) candidates.push({ ...parsed, source: "file", path: credPath });
|
|
3138
3192
|
} catch {
|
|
3139
3193
|
// missing or unreadable
|
|
3140
3194
|
}
|
|
3141
3195
|
}
|
|
3142
|
-
return
|
|
3196
|
+
return candidates;
|
|
3143
3197
|
}
|
|
3144
3198
|
|
|
3145
3199
|
function readAntigravityKeychainRaw({ securityRunner, timeoutMs = 2000 } = {}) {
|
|
@@ -3170,14 +3224,18 @@ function readAntigravityKeychainRaw({ securityRunner, timeoutMs = 2000 } = {}) {
|
|
|
3170
3224
|
}
|
|
3171
3225
|
}
|
|
3172
3226
|
|
|
3173
|
-
function loadAntigravityCredentials({
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
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);
|
|
3181
3239
|
}
|
|
3182
3240
|
|
|
3183
3241
|
function persistAntigravityCredentials(creds, next, { nowMs = Date.now() } = {}) {
|
|
@@ -3317,9 +3375,12 @@ async function fetchAntigravityRemoteLimits({
|
|
|
3317
3375
|
fetchImpl = fetch,
|
|
3318
3376
|
nowMs = Date.now(),
|
|
3319
3377
|
signal,
|
|
3378
|
+
creds,
|
|
3320
3379
|
} = {}) {
|
|
3321
|
-
const
|
|
3322
|
-
|
|
3380
|
+
const resolvedCreds = creds !== undefined
|
|
3381
|
+
? creds
|
|
3382
|
+
: loadAntigravityCredentials({ home, platform, securityRunner, nowMs });
|
|
3383
|
+
if (!resolvedCreds) return null;
|
|
3323
3384
|
|
|
3324
3385
|
const loadWithToken = async (accessToken) => {
|
|
3325
3386
|
const payload = await fetchAntigravityQuotaSummaryJson(fetchImpl, accessToken, signal);
|
|
@@ -3337,21 +3398,41 @@ async function fetchAntigravityRemoteLimits({
|
|
|
3337
3398
|
};
|
|
3338
3399
|
};
|
|
3339
3400
|
|
|
3340
|
-
let accessToken = await resolveAntigravityAccessToken(
|
|
3401
|
+
let accessToken = await resolveAntigravityAccessToken(resolvedCreds, { fetchImpl, nowMs, signal });
|
|
3341
3402
|
try {
|
|
3342
3403
|
return await loadWithToken(accessToken);
|
|
3343
3404
|
} catch (error) {
|
|
3344
|
-
if (error?.code !== "AUTH_EXPIRED" || !
|
|
3345
|
-
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
|
+
});
|
|
3346
3412
|
return await loadWithToken(accessToken);
|
|
3347
3413
|
}
|
|
3348
3414
|
}
|
|
3349
3415
|
|
|
3350
|
-
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 } = {}) {
|
|
3351
3426
|
const cached = readAntigravityLimitsCache({ home, nowMs });
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
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) {
|
|
3355
3436
|
return { configured: false };
|
|
3356
3437
|
}
|
|
3357
3438
|
if (remoteError) {
|
|
@@ -3361,7 +3442,7 @@ function antigravityUnavailableResult({ home, nowMs, platform, securityRunner, r
|
|
|
3361
3442
|
: raw;
|
|
3362
3443
|
return { configured: true, error: message };
|
|
3363
3444
|
}
|
|
3364
|
-
if (
|
|
3445
|
+
if (resolvedCreds) {
|
|
3365
3446
|
return { configured: true, error: ANTIGRAVITY_AUTH_EXPIRED_MESSAGE };
|
|
3366
3447
|
}
|
|
3367
3448
|
return { configured: true, error: ANTIGRAVITY_NOT_RUNNING_MESSAGE };
|
|
@@ -3390,6 +3471,7 @@ async function fetchAntigravityLimits({
|
|
|
3390
3471
|
securityRunner,
|
|
3391
3472
|
signal,
|
|
3392
3473
|
} = {}) {
|
|
3474
|
+
const creds = loadAntigravityCredentials({ home, platform, securityRunner, nowMs });
|
|
3393
3475
|
const startedAtMs = performance.now();
|
|
3394
3476
|
// min(this step's ceiling, budget left after reserving the fallback guard).
|
|
3395
3477
|
// 0 means "no time left" — the caller must skip the call, not issue it.
|
|
@@ -3430,7 +3512,15 @@ async function fetchAntigravityLimits({
|
|
|
3430
3512
|
if (remoteTimeoutMs > 0) {
|
|
3431
3513
|
try {
|
|
3432
3514
|
const remote = await withProviderTimeout(
|
|
3433
|
-
fetchAntigravityRemoteLimits({
|
|
3515
|
+
fetchAntigravityRemoteLimits({
|
|
3516
|
+
home,
|
|
3517
|
+
platform,
|
|
3518
|
+
securityRunner,
|
|
3519
|
+
fetchImpl,
|
|
3520
|
+
nowMs,
|
|
3521
|
+
signal,
|
|
3522
|
+
creds,
|
|
3523
|
+
}),
|
|
3434
3524
|
"Antigravity",
|
|
3435
3525
|
remoteTimeoutMs,
|
|
3436
3526
|
);
|
|
@@ -3455,7 +3545,14 @@ async function fetchAntigravityLimits({
|
|
|
3455
3545
|
signal,
|
|
3456
3546
|
});
|
|
3457
3547
|
if (!processInfo.configured) {
|
|
3458
|
-
return antigravityUnavailableResult({
|
|
3548
|
+
return antigravityUnavailableResult({
|
|
3549
|
+
home,
|
|
3550
|
+
nowMs,
|
|
3551
|
+
platform,
|
|
3552
|
+
securityRunner,
|
|
3553
|
+
remoteError,
|
|
3554
|
+
creds,
|
|
3555
|
+
});
|
|
3459
3556
|
}
|
|
3460
3557
|
if (processInfo.error) {
|
|
3461
3558
|
return { configured: true, error: processInfo.error };
|
|
@@ -3561,6 +3658,7 @@ async function fetchAntigravityLimits({
|
|
|
3561
3658
|
platform,
|
|
3562
3659
|
securityRunner,
|
|
3563
3660
|
remoteError: remoteError || error,
|
|
3661
|
+
creds,
|
|
3564
3662
|
});
|
|
3565
3663
|
}
|
|
3566
3664
|
}
|
|
@@ -3597,7 +3695,7 @@ function withPlanLabel(obj, raw, brand) {
|
|
|
3597
3695
|
// hammered). Survives an external resetUsageLimitsCache() (refresh=1 path in
|
|
3598
3696
|
// local-api.js): a refresh arriving while a fetch is already running reuses that
|
|
3599
3697
|
// in-flight fetch and returns its result.
|
|
3600
|
-
|
|
3698
|
+
const inFlightByDevinSelection = { off: null, on: null };
|
|
3601
3699
|
|
|
3602
3700
|
// Codex stamps reset_at as unix seconds; every other provider (and Claude's
|
|
3603
3701
|
// resets_at) uses ISO strings. Numbers that look like epoch milliseconds are
|
|
@@ -3635,17 +3733,19 @@ function cacheExpiresAtMs(data, fetchedAtMs) {
|
|
|
3635
3733
|
}
|
|
3636
3734
|
|
|
3637
3735
|
async function getUsageLimits(options = {}) {
|
|
3736
|
+
const selection = devinSelectionKey(options);
|
|
3737
|
+
const cache = cacheByDevinSelection[selection];
|
|
3638
3738
|
const nowMs = Date.now();
|
|
3639
3739
|
if (cache.data && nowMs < cache.expiresAtMs) {
|
|
3640
3740
|
return cache.data;
|
|
3641
3741
|
}
|
|
3642
|
-
if (
|
|
3643
|
-
return
|
|
3742
|
+
if (inFlightByDevinSelection[selection]) {
|
|
3743
|
+
return inFlightByDevinSelection[selection];
|
|
3644
3744
|
}
|
|
3645
3745
|
const promise = fetchUsageLimitsUncached(options).finally(() => {
|
|
3646
|
-
if (
|
|
3746
|
+
if (inFlightByDevinSelection[selection] === promise) inFlightByDevinSelection[selection] = null;
|
|
3647
3747
|
});
|
|
3648
|
-
|
|
3748
|
+
inFlightByDevinSelection[selection] = promise;
|
|
3649
3749
|
return promise;
|
|
3650
3750
|
}
|
|
3651
3751
|
|
|
@@ -3660,14 +3760,17 @@ async function fetchUsageLimitsUncached({
|
|
|
3660
3760
|
now = new Date(),
|
|
3661
3761
|
providerTimeoutMs = DEFAULT_PROVIDER_TIMEOUT_MS,
|
|
3662
3762
|
forceRefresh = false,
|
|
3763
|
+
devinEnabled = false,
|
|
3663
3764
|
} = {}) {
|
|
3664
3765
|
const nowMs = Date.now();
|
|
3665
3766
|
|
|
3666
|
-
const [
|
|
3667
|
-
Promise.resolve().then(() =>
|
|
3767
|
+
const [claudeOauth, claudeSubscription, codexAuth] = await Promise.all([
|
|
3768
|
+
Promise.resolve().then(() => readClaudeCodeOauthToken({ platform, securityRunner, home, nowMs })),
|
|
3668
3769
|
Promise.resolve().then(() => detectClaudeCodeSubscriptionDetails({ platform, securityRunner, home })),
|
|
3669
3770
|
readCodexAuthBundle({ home, env }),
|
|
3670
3771
|
]);
|
|
3772
|
+
const claudeToken = claudeOauth?.accessToken || null;
|
|
3773
|
+
const claudeTokenExpiresAtMs = claudeOauth?.expiresAtMs ?? null;
|
|
3671
3774
|
const claudePlanType = claudeSubscription?.planType || null;
|
|
3672
3775
|
|
|
3673
3776
|
// Match the official Codex CLI: prefer the access token's JWT expiry and refresh only
|
|
@@ -3706,7 +3809,9 @@ async function fetchUsageLimitsUncached({
|
|
|
3706
3809
|
|
|
3707
3810
|
// Skip the upstream Claude call entirely while a 429 cooldown is active — calling again
|
|
3708
3811
|
// just renews the penalty. The result handling below serves cache or a cooldown message.
|
|
3709
|
-
const claudeRetryAtMs = claudeToken
|
|
3812
|
+
const claudeRetryAtMs = claudeToken
|
|
3813
|
+
? readClaudeRateLimitRetryAtMs({ home, nowMs, tokenExpiresAtMs: claudeTokenExpiresAtMs })
|
|
3814
|
+
: null;
|
|
3710
3815
|
// Also avoid cross-process hammering after a recent successful read: embedded-server
|
|
3711
3816
|
// restarts and background polls read the disk cache instead of spending another Claude
|
|
3712
3817
|
// OAuth usage request. An explicit user refresh (refresh=1 → forceRefresh) punches
|
|
@@ -3717,7 +3822,7 @@ async function fetchUsageLimitsUncached({
|
|
|
3717
3822
|
: null;
|
|
3718
3823
|
|
|
3719
3824
|
const providerFetch = withFetchTimeout(fetchImpl, providerTimeoutMs);
|
|
3720
|
-
const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGoRaw, qoder, qoderCn, codingPlan, agentPlan, commandCodeRaw, 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([
|
|
3721
3826
|
claudeToken && !freshClaudeCache && !claudeRetryAtMs
|
|
3722
3827
|
? withProviderTimeout(fetchClaudeUsageLimits(claudeToken, { fetchImpl: providerFetch, maxAttempts: 1 }), "Claude", providerTimeoutMs).then(
|
|
3723
3828
|
(value) => ({ status: "fulfilled", value }),
|
|
@@ -3831,6 +3936,16 @@ async function fetchUsageLimitsUncached({
|
|
|
3831
3936
|
(value) => ({ status: "fulfilled", value }),
|
|
3832
3937
|
(reason) => ({ status: "rejected", reason }),
|
|
3833
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
|
+
),
|
|
3834
3949
|
// Public status-page probe (fail-soft, own 5-min cache in provider-status.js).
|
|
3835
3950
|
// Only probed for configured accounts — without a token the Claude section
|
|
3836
3951
|
// never renders, so the reading would have nowhere to go.
|
|
@@ -3878,13 +3993,21 @@ async function fetchUsageLimitsUncached({
|
|
|
3878
3993
|
// surface an accurate "retry in ~Nm" message rather than the misleading hardcoded one.
|
|
3879
3994
|
const reason = claudeResult?.reason;
|
|
3880
3995
|
if (reason?.code === "RATE_LIMITED") {
|
|
3881
|
-
writeClaudeRateLimitCooldown(reason.retryAfterSec, {
|
|
3996
|
+
writeClaudeRateLimitCooldown(reason.retryAfterSec, {
|
|
3997
|
+
home,
|
|
3998
|
+
nowMs,
|
|
3999
|
+
tokenExpiresAtMs: claudeTokenExpiresAtMs,
|
|
4000
|
+
});
|
|
3882
4001
|
}
|
|
3883
4002
|
const cached = readClaudeLimitsCache({ home, nowMs });
|
|
3884
4003
|
if (cached) {
|
|
3885
4004
|
claude = cached;
|
|
3886
4005
|
} else {
|
|
3887
|
-
const retryAtMs = readClaudeRateLimitRetryAtMs({
|
|
4006
|
+
const retryAtMs = readClaudeRateLimitRetryAtMs({
|
|
4007
|
+
home,
|
|
4008
|
+
nowMs,
|
|
4009
|
+
tokenExpiresAtMs: claudeTokenExpiresAtMs,
|
|
4010
|
+
}) || claudeRetryAtMs;
|
|
3888
4011
|
claude = {
|
|
3889
4012
|
configured: true,
|
|
3890
4013
|
error: retryAtMs
|
|
@@ -3907,7 +4030,11 @@ async function fetchUsageLimitsUncached({
|
|
|
3907
4030
|
// cool-down just armed by this cycle's 429 is included; a successful read above
|
|
3908
4031
|
// cleared the file, so this is null in the happy path.
|
|
3909
4032
|
if (claude.configured) {
|
|
3910
|
-
const claudeCooldownMs = readClaudeRateLimitRetryAtMs({
|
|
4033
|
+
const claudeCooldownMs = readClaudeRateLimitRetryAtMs({
|
|
4034
|
+
home,
|
|
4035
|
+
nowMs,
|
|
4036
|
+
tokenExpiresAtMs: claudeTokenExpiresAtMs,
|
|
4037
|
+
});
|
|
3911
4038
|
if (claudeCooldownMs) {
|
|
3912
4039
|
claude.retry_at = new Date(claudeCooldownMs).toISOString();
|
|
3913
4040
|
}
|
|
@@ -4029,6 +4156,22 @@ async function fetchUsageLimitsUncached({
|
|
|
4029
4156
|
: { configured: true, error: reason?.message || "Unknown error" };
|
|
4030
4157
|
}
|
|
4031
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
|
+
|
|
4032
4175
|
const data = {
|
|
4033
4176
|
fetched_at: new Date(nowMs).toISOString(),
|
|
4034
4177
|
claude: withPlanLabel(claude, claudePlanType, "Claude"),
|
|
@@ -4050,6 +4193,9 @@ async function fetchUsageLimitsUncached({
|
|
|
4050
4193
|
// maps plan ids to the CLI's exact display strings, so skip the shared
|
|
4051
4194
|
// Title-Case normalization ("Goat") and surface them as-is.
|
|
4052
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"),
|
|
4053
4199
|
qoder: withPlanLabel(qoder, qoder?.plan_label, "Qoder"),
|
|
4054
4200
|
qoderCn: withPlanLabel(qoderCn, qoderCn?.plan_label, "Qoder CN"),
|
|
4055
4201
|
codingPlan: withPlanLabel(codingPlan, codingPlan?.plan_label, "Ark Coding Plan"),
|
|
@@ -4077,12 +4223,16 @@ async function fetchUsageLimitsUncached({
|
|
|
4077
4223
|
};
|
|
4078
4224
|
}
|
|
4079
4225
|
|
|
4080
|
-
|
|
4226
|
+
cacheByDevinSelection[devinSelectionKey({ devinEnabled })] = {
|
|
4227
|
+
data,
|
|
4228
|
+
expiresAtMs: cacheExpiresAtMs(data, nowMs),
|
|
4229
|
+
};
|
|
4081
4230
|
return data;
|
|
4082
4231
|
}
|
|
4083
4232
|
|
|
4084
4233
|
function resetUsageLimitsCache() {
|
|
4085
|
-
|
|
4234
|
+
cacheByDevinSelection.off = { data: null, expiresAtMs: 0 };
|
|
4235
|
+
cacheByDevinSelection.on = { data: null, expiresAtMs: 0 };
|
|
4086
4236
|
}
|
|
4087
4237
|
|
|
4088
4238
|
module.exports = {
|