tokentracker-cli 1.5.4 → 1.5.5
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/dashboard/dist/assets/{AchievementBadge-CKNb_cwg.js → AchievementBadge-BYxdWhS1.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-u9P8DXJI.js → AchievementsPage-Bv9o7Aj8.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-BSCUJ4DR.js → AchievementsSection-DE5bZGxg.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-Bg7cE3Zq.js → BadgeDetailModal-CgZaSVTp.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-C7D2WbQA.js → BotAnimated-CwhPfUBI.js} +1 -1
- package/dashboard/dist/assets/{Card-B4IqAhai.js → Card-Y7qaxMuE.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-P75pH9Ym.js → CloudCheckoutPage-DVl7jk6b.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-DDEZfmZg.js → CloudPage-BnIvpHGH.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-CER_AklA.js → CommandPalette-CzMv2Zxn.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-Cbb74eib.js → CommunityStatsModal-D8sOZqUc.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-Q1ZQdZHt.js → ConfirmModal-Bun5xKMm.js} +1 -1
- package/dashboard/dist/assets/{Controls-DaGOfcoz.js → Controls-ltfnvkFW.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-CXI7RmxO.js → DashboardPage-C3Xu6_3t.js} +1 -1
- package/dashboard/dist/assets/{DateRangePopover-DSS1Ira_.js → DateRangePopover-e3gJYqFJ.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-6zPLYydh.js → DevicePage-JgT16dEp.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-DdCQ36LK.js → DialogClose-DeWert7h.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-Ca6W3nCT.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-BjP217Y1.js → DialogTitle-BfK_yOeR.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-Dhq8PM4h.js → ElectricLogo-Bbev0Y8Q.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CGNbED4E.js → FadeIn-fypYWLDK.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DwqEFzb6.js → HeaderGithubStar-Cnil2Lbo.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-CQZTgyQy.js → HoverTooltip-Bw_8tru-.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-Bz09HXLn.js → IpCheckPage-CHnhUgA2.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-DC-lZsYR.js → LandingPage-Bq4iQS6S.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardPage-D8MpLM_t.js → LeaderboardPage-D42lUdw-.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-pdXgokhE.js → LeaderboardProBadge-eTKceT90.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-nHZLiE0m.js → LeaderboardProfileModal-D-3tRrOR.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-BKv3oIAW.js → LeaderboardProfilePage-DkRCJoiA.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-TmRTgSCb.js → LimitsPage-dUXjP3_1.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-aF01udyR.js → LocalOnlyNotice-CtpjB6Z2.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-BaroD0Ct.js → LoginCard-zrRd8o54.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-0jgyMGVM.js → LoginModal-DNl5Rt1e.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BUx8U58i.js → LoginPage-BuB707vV.js} +1 -1
- package/dashboard/dist/assets/{PetPage-CpOjtJqg.js → PetPage-BpDriXkp.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-BYjfer-M.js → PopoverPopup-rQNSs8R6.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-NtYa2h0u.js → ProviderIcon-CumyO371.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-CYUg7TR1.js → ResetPasswordPage-ecUHfJAk.js} +1 -1
- package/dashboard/dist/assets/{Select-CYP95o5y.js → Select-YVEqEhk4.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-BCDfusOM.js → SelectItemText-D1NBc4uc.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-BmVc2rIT.js → SelfHostPage-8nAin68c.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-Dyp8NwIn.js → ServiceStatusPage-D1zxuzOg.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-tGEj6Ztz.js → SessionsPage-B6ksEER_.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-CCMbq5wc.js → SettingsPage-COqP8ZbJ.js} +3 -3
- package/dashboard/dist/assets/{SkillsPage-C4O4fbaQ.js → SkillsPage-DCX7xTj0.js} +1 -1
- package/dashboard/dist/assets/TokenGalaxy-D4n7AR-R.js +4431 -0
- package/dashboard/dist/assets/{TooltipPopup-BJfFYesG.js → TooltipPopup-DvSoB_eS.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-CElakFSU.js → TrendMonitor-5GVzh0Ye.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-Cvd1LbJm.js → WidgetsPage-BIc4MBiu.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-wRC1tE_a.js → WrappedPage-BpQk4g0o.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-V4TPLCJG.js → __vite-browser-external-B_rKFTTN.js} +3 -3
- package/dashboard/dist/assets/{agent-logos-Ba57HF3w.js → agent-logos-DVg-sTm4.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-B1bKnxXL.js → arrow-left-Ch6Q7eU5.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-DrOlKzX5.js → arrow-right-B2B9GvSI.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-hlYpo3yw.js → arrow-up-right-DwNO_uSb.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-CSY6SEsI.js → calendar-days---NjJlBi.js} +1 -1
- package/dashboard/dist/assets/{check-xrP1gLw5.js → check-D3mhgRrW.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-6-zT5n9d.js → chevron-down-x-b39Zto.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-BZymZD4K.js → cloud-checkout-CycWyTsg.js} +1 -1
- package/dashboard/dist/assets/{composite-C9izWafa.js → composite-CnA1qtYn.js} +1 -1
- package/dashboard/dist/assets/{copy-Dkas5wZX.js → copy-JRnRMgdX.js} +1 -1
- package/dashboard/dist/assets/{download-CagGRs0_.js → download-CRLqGD62.js} +1 -1
- package/dashboard/dist/assets/{external-link-DRl10hxd.js → external-link-BrmaWV_t.js} +1 -1
- package/dashboard/dist/assets/{flame-CkvmsTFB.js → flame-meQ5-vRD.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-BtwhO3mV.js → format-tokens-D8mIJGz1.js} +1 -1
- package/dashboard/dist/assets/{gift-YzjXkMQx.js → gift-Cd0FPXgO.js} +1 -1
- package/dashboard/dist/assets/{icons-CHhKYs0u.js → icons-D-PTKw7b.js} +1 -1
- package/dashboard/dist/assets/{index-B0MQZTrM.js → index-CMV9UKNf.js} +1 -1
- package/dashboard/dist/assets/{index-DojzoMqd.js → index-C_6gEM83.js} +1 -1
- package/dashboard/dist/assets/{index-CJknN9Jd.js → index-DCIZaLNC.js} +1 -1
- package/dashboard/dist/assets/index-HLYcwmPD.js +502 -0
- package/dashboard/dist/assets/{info-Torep-Ti.js → info-e0tP9P3D.js} +1 -1
- package/dashboard/dist/assets/{link-2-DE5Yu-z6.js → link-2-DZ2TQhER.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-BL8LxD9n.js → loader-circle-DjflRntu.js} +1 -1
- package/dashboard/dist/assets/{local-usage-transfer-8X1InWix.js → local-usage-transfer-XTZLA3XH.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-D60-6UQa.js → modifiers.esm-BpEQ8-Gt.js} +1 -1
- package/dashboard/dist/assets/module-BbVsRaE2.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-C-IFPjqk.js → monitor-smartphone-VVBcI30G.js} +1 -1
- package/dashboard/dist/assets/{play-n6uWrdH9.js → play-DN4Sb3W_.js} +1 -1
- package/dashboard/dist/assets/{plus-B2f6TaNj.js → plus-Cz_k7arH.js} +1 -1
- package/dashboard/dist/assets/{provider-display-DWqCEcsP.js → provider-display-S1mSaydF.js} +1 -1
- package/dashboard/dist/assets/{react-B-zfi_j6.js → react-MQBcjrn_.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-C2PsvI-v.js → safePolygon-InKxYcXd.js} +1 -1
- package/dashboard/dist/assets/{search-DZOaTLF9.js → search-C-N0RDi0.js} +1 -1
- package/dashboard/dist/assets/{server-DIp4Qfsv.js → server-CZkn8vIZ.js} +1 -1
- package/dashboard/dist/assets/{shield-check-Cs2M8-iT.js → shield-check-DPrGFouZ.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CoYy6Tov.js → skills-api-wMwM-GpF.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-B5_hjIfs.js → sortable.esm-CUPP1ihT.js} +1 -1
- package/dashboard/dist/assets/{store-BLXqtJzb.js → store-BpVdRYnQ.js} +1 -1
- package/dashboard/dist/assets/{terminal-CEMKX3ba.js → terminal-C-7TFeKg.js} +1 -1
- package/dashboard/dist/assets/{trash-DOWYaXjK.js → trash-Be8cd4HD.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-BjRI1Bzz.js → use-community-stats-cG0WrB4Y.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-T7L5qBU6.js → use-latest-request-guard-C91enyPD.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-vNIrCPZA.js → use-limits-display-prefs-DdwoLqPD.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-Cg5Zb4q_.js → use-native-settings-BR-NiMKh.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-BF-wIvmG.js → use-ordered-list-oqV724BP.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-DW5YHBBI.js +1 -0
- package/dashboard/dist/assets/{use-transform-CbJ1IcNc.js → use-transform-BsNSgVD6.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-Dgg3jpC4.js → use-usage-limits-Dk8LcMU-.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-3oBHWfSe.js → useCurrency-FxerRfd2.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-D0YrIW0A.js → usePositioner-D2J-rSfX.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-DZAHWE_R.js → useTokenFormat-CaI2nl2C.js} +1 -1
- package/dashboard/dist/assets/{zap-kLub6Jiv.js → zap-D3C37XAj.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +4 -4
- package/src/lib/cursor-config.js +15 -1
- package/src/lib/pricing/curated-overrides.json +14 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +13 -4
- package/src/lib/usage-limits.js +266 -10
- package/dashboard/dist/assets/DialogDescription-DztHQvj-.js +0 -1
- package/dashboard/dist/assets/TokenGalaxy-D2d738xr.js +0 -4426
- package/dashboard/dist/assets/index-C2DK8av-.js +0 -502
- package/dashboard/dist/assets/module-ZyVsxBG_.js +0 -3
- package/dashboard/dist/assets/use-reduced-motion-B9K5mIx4.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -5587,10 +5587,14 @@ function detectOpencodeMessageLayout(dbPath, sqliteOptions = {}, { propagateRead
|
|
|
5587
5587
|
}
|
|
5588
5588
|
|
|
5589
5589
|
// Shared provider resolver: v1 rows carry a top-level providerID, v2 rows nest
|
|
5590
|
-
// it under model.providerID.
|
|
5591
|
-
//
|
|
5590
|
+
// it under model.providerID. ZCode renamed the flat field to `providerId`
|
|
5591
|
+
// (lowercase d, ~2026-09, issue #1008); rows written since then only carry the
|
|
5592
|
+
// new spelling, so without it every post-rename native turn resolves to "" and
|
|
5593
|
+
// the zcode legacy-history leg silently drops it. Used by the mimo/zcode
|
|
5594
|
+
// discriminators so they work against all generations without duplicating the
|
|
5595
|
+
// resolution logic.
|
|
5592
5596
|
function opencodeMessageProvider(data) {
|
|
5593
|
-
return data?.providerID || data?.model?.providerID || "";
|
|
5597
|
+
return data?.providerID || data?.model?.providerID || data?.providerId || "";
|
|
5594
5598
|
}
|
|
5595
5599
|
|
|
5596
5600
|
// readSqliteJsonRows wraps its two backend errors into the thrown Error, so a
|
|
@@ -7743,7 +7747,12 @@ async function parseCursorApiIncremental({
|
|
|
7743
7747
|
|
|
7744
7748
|
const { normalizeCursorUsage } = require("./cursor-config");
|
|
7745
7749
|
const delta = normalizeCursorUsage(record);
|
|
7746
|
-
|
|
7750
|
+
// isAllZeroUsage reads only the token fields, so a request-only row (0
|
|
7751
|
+
// tokens across every column — e.g. bot/automation requests, issue #998)
|
|
7752
|
+
// must not be dropped here: it is kept as a 1-request record pairing
|
|
7753
|
+
// with the #997 cloud-side request retention. Rows that are all-zero
|
|
7754
|
+
// AND carry no model identity are degenerate and stay skipped.
|
|
7755
|
+
if (isAllZeroUsage(delta) && !String(record.model || "").trim()) continue;
|
|
7747
7756
|
|
|
7748
7757
|
delta.conversation_count = 1;
|
|
7749
7758
|
|
package/src/lib/usage-limits.js
CHANGED
|
@@ -131,6 +131,7 @@ const ANTIGRAVITY_QUOTA_SUMMARY_URLS = Object.freeze([
|
|
|
131
131
|
"https://daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary",
|
|
132
132
|
"https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary",
|
|
133
133
|
]);
|
|
134
|
+
const ANTIGRAVITY_AVAILABLE_MODELS_URL = "https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels";
|
|
134
135
|
const ANTIGRAVITY_USER_AGENT = "antigravity/2.9.1";
|
|
135
136
|
const ANTIGRAVITY_TOKEN_REFRESH_SKEW_MS = 60 * 1000;
|
|
136
137
|
// Per-step ceilings for the Antigravity serial chain. `providerTimeoutMs` bounds the
|
|
@@ -179,6 +180,12 @@ const OPENCODE_GO_LOCAL_ESTIMATE_CACHE_MAX_AGE_MS = 60 * 60 * 1000;
|
|
|
179
180
|
// (mirrors Claude / Codex / OpenCode Go).
|
|
180
181
|
const CURSOR_LIMITS_CACHE_FILE = "cursor-usage-limits-cache.json";
|
|
181
182
|
const CURSOR_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
183
|
+
// Gemini's Code Assist quota RPCs (cloudcode-pa.googleapis.com) are routinely
|
|
184
|
+
// degraded on GFW-affected routes; a timeout or 5xx previously blanked the card
|
|
185
|
+
// until the next poll. Persist the last successful windows so a blip serves
|
|
186
|
+
// stale bars instead (mirrors Cursor / Codex / OpenCode Go).
|
|
187
|
+
const GEMINI_LIMITS_CACHE_FILE = "gemini-usage-limits-cache.json";
|
|
188
|
+
const GEMINI_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
182
189
|
// The Kimi coding API (api.kimi.com / api.kimi.ai) is another proxied route where
|
|
183
190
|
// a timeout or 5xx previously blanked the card until the next poll. Persist the
|
|
184
191
|
// last successful windows so a blip serves stale bars instead
|
|
@@ -1245,6 +1252,12 @@ async function extractGeminiOauthClientCredentials({ commandRunner, home } = {})
|
|
|
1245
1252
|
return { ...GEMINI_CLI_FALLBACK_OAUTH_CLIENT };
|
|
1246
1253
|
}
|
|
1247
1254
|
|
|
1255
|
+
// Shared by the refresh-401 path, the missing-credentials path, and the quota
|
|
1256
|
+
// endpoint's 401: all three mean the Google OAuth session is gone. Re-login is
|
|
1257
|
+
// actionable, so none of them may be hidden behind the stale disk-cache
|
|
1258
|
+
// fallback (opencode-go auth_error precedent).
|
|
1259
|
+
const GEMINI_NOT_LOGGED_IN_MESSAGE = "Not logged in to Gemini. Run 'gemini' in Terminal to authenticate.";
|
|
1260
|
+
|
|
1248
1261
|
async function refreshGeminiAccessToken({
|
|
1249
1262
|
refreshToken,
|
|
1250
1263
|
home,
|
|
@@ -1270,7 +1283,14 @@ async function refreshGeminiAccessToken({
|
|
|
1270
1283
|
body,
|
|
1271
1284
|
});
|
|
1272
1285
|
if (!res.ok) {
|
|
1273
|
-
|
|
1286
|
+
const error = new Error(GEMINI_NOT_LOGGED_IN_MESSAGE);
|
|
1287
|
+
// Confirmed credential failures only (invalid_grant = 400, invalid_client
|
|
1288
|
+
// = 401/403): a transient 429/5xx from the token endpoint must stay
|
|
1289
|
+
// cache-eligible so the stale fallback can still serve bars.
|
|
1290
|
+
if (res.status === 400 || res.status === 401 || res.status === 403) {
|
|
1291
|
+
error.code = "AUTH_EXPIRED";
|
|
1292
|
+
}
|
|
1293
|
+
throw error;
|
|
1274
1294
|
}
|
|
1275
1295
|
|
|
1276
1296
|
const json = await res.json();
|
|
@@ -1411,18 +1431,38 @@ async function fetchGeminiLimits({ home, env, fetchImpl = fetch, commandRunner }
|
|
|
1411
1431
|
if (!settings && !credentials) {
|
|
1412
1432
|
return { configured: false };
|
|
1413
1433
|
}
|
|
1434
|
+
// An unsupported auth mode is a stable, actionable configuration state: the
|
|
1435
|
+
// quota RPC can never succeed here and stale OAuth-era bars would be
|
|
1436
|
+
// misleading, so it punches through the cache like an auth error.
|
|
1414
1437
|
if (selectedType === "api-key") {
|
|
1415
|
-
return {
|
|
1438
|
+
return {
|
|
1439
|
+
configured: true,
|
|
1440
|
+
error: "Gemini API key auth not supported. Use Google account (OAuth) instead.",
|
|
1441
|
+
auth_error: true,
|
|
1442
|
+
};
|
|
1416
1443
|
}
|
|
1417
1444
|
if (selectedType === "vertex-ai") {
|
|
1418
|
-
return {
|
|
1445
|
+
return {
|
|
1446
|
+
configured: true,
|
|
1447
|
+
error: "Gemini Vertex AI auth not supported. Use Google account (OAuth) instead.",
|
|
1448
|
+
auth_error: true,
|
|
1449
|
+
};
|
|
1419
1450
|
}
|
|
1420
1451
|
|
|
1421
1452
|
const creds = credentials;
|
|
1422
1453
|
if (!creds?.access_token) {
|
|
1423
|
-
return {
|
|
1454
|
+
return {
|
|
1455
|
+
configured: true,
|
|
1456
|
+
error: GEMINI_NOT_LOGGED_IN_MESSAGE,
|
|
1457
|
+
auth_error: true,
|
|
1458
|
+
};
|
|
1424
1459
|
}
|
|
1425
1460
|
|
|
1461
|
+
// The Google account email from the local id_token JWT partitions the disk
|
|
1462
|
+
// cache (zcode login-identity precedent). Decoded before the network work —
|
|
1463
|
+
// it cannot throw — so the failure path below still knows who we serve for.
|
|
1464
|
+
const claims = decodeJwtPayload(creds.id_token);
|
|
1465
|
+
|
|
1426
1466
|
try {
|
|
1427
1467
|
let accessToken = creds.access_token;
|
|
1428
1468
|
const expiry = Number(creds.expiry_date);
|
|
@@ -1436,7 +1476,6 @@ async function fetchGeminiLimits({ home, env, fetchImpl = fetch, commandRunner }
|
|
|
1436
1476
|
});
|
|
1437
1477
|
}
|
|
1438
1478
|
|
|
1439
|
-
const claims = decodeJwtPayload(creds.id_token);
|
|
1440
1479
|
const codeAssist = await loadGeminiCodeAssistStatus(accessToken, { fetchImpl });
|
|
1441
1480
|
const res = await fetchImpl("https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota", {
|
|
1442
1481
|
method: "POST",
|
|
@@ -1447,7 +1486,18 @@ async function fetchGeminiLimits({ home, env, fetchImpl = fetch, commandRunner }
|
|
|
1447
1486
|
body: JSON.stringify(codeAssist.projectId ? { project: codeAssist.projectId } : {}),
|
|
1448
1487
|
});
|
|
1449
1488
|
if (res.status === 401) {
|
|
1450
|
-
|
|
1489
|
+
const error = new Error(GEMINI_NOT_LOGGED_IN_MESSAGE);
|
|
1490
|
+
error.code = "AUTH_EXPIRED";
|
|
1491
|
+
throw error;
|
|
1492
|
+
}
|
|
1493
|
+
if (res.status === 403) {
|
|
1494
|
+
// PERMISSION_DENIED (seen for Enterprise accounts, gemini-cli#25512):
|
|
1495
|
+
// re-login cannot fix it, but stale bars for a project the account can
|
|
1496
|
+
// no longer see would mislead — punch through like an auth error while
|
|
1497
|
+
// keeping the displayed message unchanged.
|
|
1498
|
+
const error = new Error(`Gemini API error: HTTP ${res.status}`);
|
|
1499
|
+
error.code = "AUTH_EXPIRED";
|
|
1500
|
+
throw error;
|
|
1451
1501
|
}
|
|
1452
1502
|
if (!res.ok) {
|
|
1453
1503
|
throw new Error(`Gemini API error: HTTP ${res.status}`);
|
|
@@ -1461,11 +1511,19 @@ async function fetchGeminiLimits({ home, env, fetchImpl = fetch, commandRunner }
|
|
|
1461
1511
|
email: claims?.email || null,
|
|
1462
1512
|
tier: codeAssist.tier,
|
|
1463
1513
|
}),
|
|
1514
|
+
// Internal cache-partition key (stripped before the payload is served):
|
|
1515
|
+
// the same non-secret id the payload already surfaces as account_email.
|
|
1516
|
+
cache_identity: claims?.email || null,
|
|
1464
1517
|
};
|
|
1465
1518
|
} catch (error) {
|
|
1466
1519
|
return {
|
|
1467
1520
|
configured: true,
|
|
1468
1521
|
error: error?.message || "Unknown error",
|
|
1522
|
+
// A session-expiry error is actionable (re-login) and must not be hidden
|
|
1523
|
+
// behind the stale disk-cache fallback. Internal marker — stripped
|
|
1524
|
+
// before the payload is served.
|
|
1525
|
+
...(error?.code === "AUTH_EXPIRED" ? { auth_error: true } : {}),
|
|
1526
|
+
cache_identity: claims?.email || null,
|
|
1469
1527
|
};
|
|
1470
1528
|
}
|
|
1471
1529
|
}
|
|
@@ -2911,6 +2969,106 @@ function resolveCursorCacheIdentity({ home, env } = {}) {
|
|
|
2911
2969
|
}
|
|
2912
2970
|
}
|
|
2913
2971
|
|
|
2972
|
+
function resolveGeminiLimitsCachePath({ home } = {}) {
|
|
2973
|
+
return path.join(home || os.homedir(), ".tokentracker", "tracker", GEMINI_LIMITS_CACHE_FILE);
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
function hasGeminiWindow(limits) {
|
|
2977
|
+
return Boolean(
|
|
2978
|
+
limits?.primary_window
|
|
2979
|
+
|| limits?.secondary_window
|
|
2980
|
+
|| limits?.tertiary_window,
|
|
2981
|
+
);
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
// Gemini windows carry `reset_at` as an ISO string (the Code Assist bucket's own
|
|
2985
|
+
// resetTime — daily for free-tier quotas). A window whose reset has already passed
|
|
2986
|
+
// is stale data, not a usable fallback, so drop it. Windows without a reset stamp
|
|
2987
|
+
// are kept — the overall cached_at max-age gate already bounds them.
|
|
2988
|
+
function isGeminiCacheWindowUsable(window, { nowMs } = {}) {
|
|
2989
|
+
if (!window || typeof window !== "object") return false;
|
|
2990
|
+
const resetAtMs = parseTimeMs(window.reset_at);
|
|
2991
|
+
if (resetAtMs === null) return true;
|
|
2992
|
+
return resetAtMs > nowMs;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
function normalizeGeminiCachedLimits(
|
|
2996
|
+
raw,
|
|
2997
|
+
{ nowMs = Date.now(), maxAgeMs = GEMINI_LIMITS_CACHE_MAX_AGE_MS } = {},
|
|
2998
|
+
) {
|
|
2999
|
+
const cachedAtMs = parseTimeMs(raw?.cached_at);
|
|
3000
|
+
if (!Number.isFinite(cachedAtMs)) return null;
|
|
3001
|
+
if (cachedAtMs > nowMs + 60_000) return null;
|
|
3002
|
+
if (nowMs - cachedAtMs > maxAgeMs) return null;
|
|
3003
|
+
|
|
3004
|
+
const cached = {
|
|
3005
|
+
configured: true,
|
|
3006
|
+
error: null,
|
|
3007
|
+
account_email: typeof raw?.account_email === "string" ? raw.account_email : null,
|
|
3008
|
+
account_plan: typeof raw?.account_plan === "string" ? raw.account_plan : null,
|
|
3009
|
+
primary_window: isGeminiCacheWindowUsable(raw?.primary_window, { nowMs }) ? raw.primary_window : null,
|
|
3010
|
+
secondary_window: isGeminiCacheWindowUsable(raw?.secondary_window, { nowMs }) ? raw.secondary_window : null,
|
|
3011
|
+
tertiary_window: isGeminiCacheWindowUsable(raw?.tertiary_window, { nowMs }) ? raw.tertiary_window : null,
|
|
3012
|
+
stale: true,
|
|
3013
|
+
cached_at: raw.cached_at,
|
|
3014
|
+
};
|
|
3015
|
+
return hasGeminiWindow(cached) ? cached : null;
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
// The signed-in Google account partitions the cache (zcode login-identity
|
|
3019
|
+
// precedent): the email decoded from the local id_token JWT is non-secret and is
|
|
3020
|
+
// the same id the payload already surfaces as account_email. GEMINI_HOME can aim
|
|
3021
|
+
// the credential lookup at a different installation, but the cache stays keyed by
|
|
3022
|
+
// account — a read requires the stored identity to match the account the current
|
|
3023
|
+
// credentials resolve to, and a write without an identity is refused: an
|
|
3024
|
+
// unreadable login must never pin another account's bars in place.
|
|
3025
|
+
function readGeminiLimitsCache({ home, nowMs = Date.now(), identity } = {}) {
|
|
3026
|
+
if (!identity) return null;
|
|
3027
|
+
try {
|
|
3028
|
+
const parsed = JSON.parse(fs.readFileSync(resolveGeminiLimitsCachePath({ home }), "utf8"));
|
|
3029
|
+
const entry = parsed?.gemini;
|
|
3030
|
+
if (entry?.identity !== identity) return null;
|
|
3031
|
+
return normalizeGeminiCachedLimits(entry, { nowMs });
|
|
3032
|
+
} catch (_error) {
|
|
3033
|
+
return null;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
function writeGeminiLimitsCache(limits, { home, nowMs = Date.now(), identity } = {}) {
|
|
3038
|
+
if (!identity) return;
|
|
3039
|
+
if (!limits?.configured || limits.error || !hasGeminiWindow(limits)) return;
|
|
3040
|
+
const cachePath = resolveGeminiLimitsCachePath({ home });
|
|
3041
|
+
const payload = {
|
|
3042
|
+
gemini: {
|
|
3043
|
+
identity,
|
|
3044
|
+
account_email: limits.account_email || null,
|
|
3045
|
+
account_plan: limits.account_plan || null,
|
|
3046
|
+
primary_window: limits.primary_window || null,
|
|
3047
|
+
secondary_window: limits.secondary_window || null,
|
|
3048
|
+
tertiary_window: limits.tertiary_window || null,
|
|
3049
|
+
cached_at: new Date(nowMs).toISOString(),
|
|
3050
|
+
},
|
|
3051
|
+
};
|
|
3052
|
+
try {
|
|
3053
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
3054
|
+
const tmpPath = `${cachePath}.${process.pid}.tmp`;
|
|
3055
|
+
fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
3056
|
+
fs.renameSync(tmpPath, cachePath);
|
|
3057
|
+
} catch (_error) {}
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
// Failure-only identity re-derivation for the cache fallback: when the provider
|
|
3061
|
+
// slot's result was produced by the outer timeout race, the fetcher's own
|
|
3062
|
+
// identity field is lost, so decode the (still-local) id_token once to learn
|
|
3063
|
+
// who we are serving for.
|
|
3064
|
+
function resolveGeminiCacheIdentity({ home, env } = {}) {
|
|
3065
|
+
try {
|
|
3066
|
+
return decodeJwtPayload(loadGeminiCredentials({ home, env })?.id_token)?.email || null;
|
|
3067
|
+
} catch (_error) {
|
|
3068
|
+
return null;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
|
|
2914
3072
|
// The active Kimi profile partitions the cache (ark profile_identity precedent):
|
|
2915
3073
|
// mainland and global logins — and any KIMI_HOME override — resolve to different
|
|
2916
3074
|
// endpoint-scoped credential slots, and one slot must never serve another's
|
|
@@ -3999,6 +4157,11 @@ async function fetchAntigravityQuotaSummaryJson(fetchImpl, accessToken, signal)
|
|
|
3999
4157
|
for (const url of ANTIGRAVITY_QUOTA_SUMMARY_URLS) {
|
|
4000
4158
|
try {
|
|
4001
4159
|
const res = await postAntigravityCloudCode(fetchImpl, url, accessToken, {}, signal);
|
|
4160
|
+
if (res.status === 403 && await isAntigravitySubscriptionRequired(res)) {
|
|
4161
|
+
const err = new Error("Antigravity quota summary requires a subscription.");
|
|
4162
|
+
err.code = "SUBSCRIPTION_REQUIRED";
|
|
4163
|
+
throw err;
|
|
4164
|
+
}
|
|
4002
4165
|
if (res.status === 401 || res.status === 403) {
|
|
4003
4166
|
const err = new Error(ANTIGRAVITY_AUTH_EXPIRED_MESSAGE);
|
|
4004
4167
|
err.code = "AUTH_EXPIRED";
|
|
@@ -4011,13 +4174,44 @@ async function fetchAntigravityQuotaSummaryJson(fetchImpl, accessToken, signal)
|
|
|
4011
4174
|
}
|
|
4012
4175
|
return await res.json();
|
|
4013
4176
|
} catch (error) {
|
|
4014
|
-
if (error?.code === "AUTH_EXPIRED") throw error;
|
|
4177
|
+
if (error?.code === "AUTH_EXPIRED" || error?.code === "SUBSCRIPTION_REQUIRED") throw error;
|
|
4015
4178
|
lastError = error;
|
|
4016
4179
|
}
|
|
4017
4180
|
}
|
|
4018
4181
|
throw lastError || new Error("Antigravity quota request failed.");
|
|
4019
4182
|
}
|
|
4020
4183
|
|
|
4184
|
+
// Free-tier ("Starter Quota") accounts get 403 SUBSCRIPTION_REQUIRED from
|
|
4185
|
+
// retrieveUserQuotaSummary even with a valid token — that is not an auth failure.
|
|
4186
|
+
async function isAntigravitySubscriptionRequired(res) {
|
|
4187
|
+
try {
|
|
4188
|
+
const body = await res.json();
|
|
4189
|
+
const details = Array.isArray(body?.error?.details) ? body.error.details : [];
|
|
4190
|
+
return details.some((d) => d?.reason === "SUBSCRIPTION_REQUIRED");
|
|
4191
|
+
} catch {
|
|
4192
|
+
return false;
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
// Per-model quotaInfo, available to every tier. Reshaped into the language
|
|
4197
|
+
// server's clientModelConfigs so normalizeAntigravityResponse can pick windows.
|
|
4198
|
+
async function fetchAntigravityAvailableModelsLimits(fetchImpl, accessToken, signal) {
|
|
4199
|
+
const res = await postAntigravityCloudCode(fetchImpl, ANTIGRAVITY_AVAILABLE_MODELS_URL, accessToken, {}, signal);
|
|
4200
|
+
if (res.status === 401 || res.status === 403) {
|
|
4201
|
+
const err = new Error(ANTIGRAVITY_AUTH_EXPIRED_MESSAGE);
|
|
4202
|
+
err.code = "AUTH_EXPIRED";
|
|
4203
|
+
err.status = res.status;
|
|
4204
|
+
throw err;
|
|
4205
|
+
}
|
|
4206
|
+
if (!res.ok) throw new Error(`Antigravity API error: HTTP ${res.status}`);
|
|
4207
|
+
const json = await res.json();
|
|
4208
|
+
const models = json?.models && typeof json.models === "object" ? json.models : {};
|
|
4209
|
+
const clientModelConfigs = Object.entries(models)
|
|
4210
|
+
.filter(([, m]) => typeof m?.displayName === "string" && m.displayName)
|
|
4211
|
+
.map(([id, m]) => ({ label: m.displayName, modelOrAlias: { model: id }, quotaInfo: m.quotaInfo }));
|
|
4212
|
+
return normalizeAntigravityResponse({ clientModelConfigs }, { fallbackToConfigs: true });
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4021
4215
|
/** Read the paid plan label without failing a successful quota response if this lookup fails. */
|
|
4022
4216
|
async function fetchAntigravityPlanLabel(fetchImpl, accessToken, signal) {
|
|
4023
4217
|
try {
|
|
@@ -4057,8 +4251,14 @@ async function fetchAntigravityRemoteLimits({
|
|
|
4057
4251
|
if (!resolvedCreds) return null;
|
|
4058
4252
|
|
|
4059
4253
|
const loadWithToken = async (accessToken) => {
|
|
4060
|
-
|
|
4061
|
-
|
|
4254
|
+
let windows;
|
|
4255
|
+
try {
|
|
4256
|
+
const payload = await fetchAntigravityQuotaSummaryJson(fetchImpl, accessToken, signal);
|
|
4257
|
+
windows = normalizeAntigravityQuotaSummary(payload);
|
|
4258
|
+
} catch (error) {
|
|
4259
|
+
if (error?.code !== "SUBSCRIPTION_REQUIRED") throw error;
|
|
4260
|
+
windows = await fetchAntigravityAvailableModelsLimits(fetchImpl, accessToken, signal);
|
|
4261
|
+
}
|
|
4062
4262
|
const accountPlan = windows.account_plan || await fetchAntigravityPlanLabel(fetchImpl, accessToken, signal);
|
|
4063
4263
|
return {
|
|
4064
4264
|
configured: true,
|
|
@@ -5092,6 +5292,62 @@ async function fetchUsageLimitsUncached({
|
|
|
5092
5292
|
kimiWithCache = kimiPayload;
|
|
5093
5293
|
}
|
|
5094
5294
|
|
|
5295
|
+
// Gemini: persist last successful windows so a transient timeout/5xx on the
|
|
5296
|
+
// cloudcode-pa.googleapis.com routes serves stale bars instead of a flashing
|
|
5297
|
+
// "fetch failed" (mirrors Cursor / Codex / OpenCode Go). Auth errors (a dead
|
|
5298
|
+
// OAuth session, or the unsupported api-key / vertex-ai auth modes) are
|
|
5299
|
+
// actionable and must not be hidden behind stale cache, so they pass through
|
|
5300
|
+
// untouched.
|
|
5301
|
+
let geminiWithCache;
|
|
5302
|
+
if (!gemini || gemini.configured === false) {
|
|
5303
|
+
geminiWithCache = gemini;
|
|
5304
|
+
} else if (gemini.auth_error === true) {
|
|
5305
|
+
geminiWithCache = gemini;
|
|
5306
|
+
} else if (!gemini.error && hasGeminiWindow(gemini)) {
|
|
5307
|
+
geminiWithCache = {
|
|
5308
|
+
...gemini,
|
|
5309
|
+
stale: false,
|
|
5310
|
+
cached_at: new Date(nowMs).toISOString(),
|
|
5311
|
+
};
|
|
5312
|
+
if (cacheGeneration === usageLimitsCacheGeneration) {
|
|
5313
|
+
// A slow in-flight fetch must not clobber the on-disk entry of an
|
|
5314
|
+
// account that has since been switched away: only write when the
|
|
5315
|
+
// snapshot's identity still matches the current credentials.
|
|
5316
|
+
const currentIdentity = resolveGeminiCacheIdentity({ home, env });
|
|
5317
|
+
const snapshotIdentity = typeof gemini.cache_identity === "string"
|
|
5318
|
+
? gemini.cache_identity
|
|
5319
|
+
: currentIdentity;
|
|
5320
|
+
if (snapshotIdentity && snapshotIdentity === currentIdentity) {
|
|
5321
|
+
writeGeminiLimitsCache(geminiWithCache, {
|
|
5322
|
+
home,
|
|
5323
|
+
nowMs,
|
|
5324
|
+
identity: snapshotIdentity,
|
|
5325
|
+
});
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
} else {
|
|
5329
|
+
// The slot's error object loses the fetcher's identity field when the outer
|
|
5330
|
+
// timeout race wins (the common failure mode this cache exists for). Always
|
|
5331
|
+
// re-derive from the CURRENT local credentials — the request may have
|
|
5332
|
+
// started under an account that has since been switched away, in which
|
|
5333
|
+
// case the read's identity gate refuses the old entry. Revalidate the auth
|
|
5334
|
+
// mode too: a mid-flight switch to api-key/vertex-ai never reached the
|
|
5335
|
+
// punch-through branch above, but stale OAuth bars would still mislead.
|
|
5336
|
+
const selectedType = loadGeminiSettings({ home, env })?.security?.auth?.selectedType ?? null;
|
|
5337
|
+
if (selectedType === "api-key" || selectedType === "vertex-ai") {
|
|
5338
|
+
geminiWithCache = gemini;
|
|
5339
|
+
} else {
|
|
5340
|
+
const identity = resolveGeminiCacheIdentity({ home, env });
|
|
5341
|
+
const cached = readGeminiLimitsCache({ home, nowMs, identity });
|
|
5342
|
+
geminiWithCache = cached || gemini;
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
// cache_identity / auth_error are internal cache-plumbing markers, not payload.
|
|
5346
|
+
if (geminiWithCache && ("cache_identity" in geminiWithCache || "auth_error" in geminiWithCache)) {
|
|
5347
|
+
const { cache_identity: _identity, auth_error: _authError, ...geminiPayload } = geminiWithCache;
|
|
5348
|
+
geminiWithCache = geminiPayload;
|
|
5349
|
+
}
|
|
5350
|
+
|
|
5095
5351
|
const data = {
|
|
5096
5352
|
fetched_at: new Date(nowMs).toISOString(),
|
|
5097
5353
|
claude: withPlanLabel(claude, claudePlanType, "Claude"),
|
|
@@ -5102,7 +5358,7 @@ async function fetchUsageLimitsUncached({
|
|
|
5102
5358
|
// authoritative human-readable plan mapping. Both rendered as garbage like
|
|
5103
5359
|
// "Kimi Type_event" (issue #130), so show the bare brand instead.
|
|
5104
5360
|
kimi: withPlanLabel(kimiWithCache, null, "Kimi"),
|
|
5105
|
-
gemini: withPlanLabel(
|
|
5361
|
+
gemini: withPlanLabel(geminiWithCache, geminiWithCache.account_plan, "Gemini"),
|
|
5106
5362
|
kiro: withPlanLabel(kiro, kiro.plan_name, "Kiro"),
|
|
5107
5363
|
antigravity: withPlanLabel(antigravity, antigravity.account_plan, "Antigravity"),
|
|
5108
5364
|
copilot: withPlanLabel(copilot, copilot.plan_name, "Copilot"),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,J as a}from"./__vite-browser-external-V4TPLCJG.js";import{u as p}from"./DialogTitle-BjP217Y1.js";import{u as c}from"./composite-C9izWafa.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,i=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};
|