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.
Files changed (117) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-CKNb_cwg.js → AchievementBadge-BYxdWhS1.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-u9P8DXJI.js → AchievementsPage-Bv9o7Aj8.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-BSCUJ4DR.js → AchievementsSection-DE5bZGxg.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-Bg7cE3Zq.js → BadgeDetailModal-CgZaSVTp.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-C7D2WbQA.js → BotAnimated-CwhPfUBI.js} +1 -1
  6. package/dashboard/dist/assets/{Card-B4IqAhai.js → Card-Y7qaxMuE.js} +1 -1
  7. package/dashboard/dist/assets/{CloudCheckoutPage-P75pH9Ym.js → CloudCheckoutPage-DVl7jk6b.js} +1 -1
  8. package/dashboard/dist/assets/{CloudPage-DDEZfmZg.js → CloudPage-BnIvpHGH.js} +2 -2
  9. package/dashboard/dist/assets/{CommandPalette-CER_AklA.js → CommandPalette-CzMv2Zxn.js} +1 -1
  10. package/dashboard/dist/assets/{CommunityStatsModal-Cbb74eib.js → CommunityStatsModal-D8sOZqUc.js} +1 -1
  11. package/dashboard/dist/assets/{ConfirmModal-Q1ZQdZHt.js → ConfirmModal-Bun5xKMm.js} +1 -1
  12. package/dashboard/dist/assets/{Controls-DaGOfcoz.js → Controls-ltfnvkFW.js} +1 -1
  13. package/dashboard/dist/assets/{DashboardPage-CXI7RmxO.js → DashboardPage-C3Xu6_3t.js} +1 -1
  14. package/dashboard/dist/assets/{DateRangePopover-DSS1Ira_.js → DateRangePopover-e3gJYqFJ.js} +1 -1
  15. package/dashboard/dist/assets/{DevicePage-6zPLYydh.js → DevicePage-JgT16dEp.js} +1 -1
  16. package/dashboard/dist/assets/{DialogClose-DdCQ36LK.js → DialogClose-DeWert7h.js} +1 -1
  17. package/dashboard/dist/assets/DialogDescription-Ca6W3nCT.js +1 -0
  18. package/dashboard/dist/assets/{DialogTitle-BjP217Y1.js → DialogTitle-BfK_yOeR.js} +1 -1
  19. package/dashboard/dist/assets/{ElectricLogo-Dhq8PM4h.js → ElectricLogo-Bbev0Y8Q.js} +1 -1
  20. package/dashboard/dist/assets/{FadeIn-CGNbED4E.js → FadeIn-fypYWLDK.js} +1 -1
  21. package/dashboard/dist/assets/{HeaderGithubStar-DwqEFzb6.js → HeaderGithubStar-Cnil2Lbo.js} +1 -1
  22. package/dashboard/dist/assets/{HoverTooltip-CQZTgyQy.js → HoverTooltip-Bw_8tru-.js} +1 -1
  23. package/dashboard/dist/assets/{IpCheckPage-Bz09HXLn.js → IpCheckPage-CHnhUgA2.js} +1 -1
  24. package/dashboard/dist/assets/{LandingPage-DC-lZsYR.js → LandingPage-Bq4iQS6S.js} +2 -2
  25. package/dashboard/dist/assets/{LeaderboardPage-D8MpLM_t.js → LeaderboardPage-D42lUdw-.js} +2 -2
  26. package/dashboard/dist/assets/{LeaderboardProBadge-pdXgokhE.js → LeaderboardProBadge-eTKceT90.js} +1 -1
  27. package/dashboard/dist/assets/{LeaderboardProfileModal-nHZLiE0m.js → LeaderboardProfileModal-D-3tRrOR.js} +3 -3
  28. package/dashboard/dist/assets/{LeaderboardProfilePage-BKv3oIAW.js → LeaderboardProfilePage-DkRCJoiA.js} +1 -1
  29. package/dashboard/dist/assets/{LimitsPage-TmRTgSCb.js → LimitsPage-dUXjP3_1.js} +1 -1
  30. package/dashboard/dist/assets/{LocalOnlyNotice-aF01udyR.js → LocalOnlyNotice-CtpjB6Z2.js} +1 -1
  31. package/dashboard/dist/assets/{LoginCard-BaroD0Ct.js → LoginCard-zrRd8o54.js} +1 -1
  32. package/dashboard/dist/assets/{LoginModal-0jgyMGVM.js → LoginModal-DNl5Rt1e.js} +1 -1
  33. package/dashboard/dist/assets/{LoginPage-BUx8U58i.js → LoginPage-BuB707vV.js} +1 -1
  34. package/dashboard/dist/assets/{PetPage-CpOjtJqg.js → PetPage-BpDriXkp.js} +2 -2
  35. package/dashboard/dist/assets/{PopoverPopup-BYjfer-M.js → PopoverPopup-rQNSs8R6.js} +1 -1
  36. package/dashboard/dist/assets/{ProviderIcon-NtYa2h0u.js → ProviderIcon-CumyO371.js} +1 -1
  37. package/dashboard/dist/assets/{ResetPasswordPage-CYUg7TR1.js → ResetPasswordPage-ecUHfJAk.js} +1 -1
  38. package/dashboard/dist/assets/{Select-CYP95o5y.js → Select-YVEqEhk4.js} +1 -1
  39. package/dashboard/dist/assets/{SelectItemText-BCDfusOM.js → SelectItemText-D1NBc4uc.js} +1 -1
  40. package/dashboard/dist/assets/{SelfHostPage-BmVc2rIT.js → SelfHostPage-8nAin68c.js} +1 -1
  41. package/dashboard/dist/assets/{ServiceStatusPage-Dyp8NwIn.js → ServiceStatusPage-D1zxuzOg.js} +1 -1
  42. package/dashboard/dist/assets/{SessionsPage-tGEj6Ztz.js → SessionsPage-B6ksEER_.js} +1 -1
  43. package/dashboard/dist/assets/{SettingsPage-CCMbq5wc.js → SettingsPage-COqP8ZbJ.js} +3 -3
  44. package/dashboard/dist/assets/{SkillsPage-C4O4fbaQ.js → SkillsPage-DCX7xTj0.js} +1 -1
  45. package/dashboard/dist/assets/TokenGalaxy-D4n7AR-R.js +4431 -0
  46. package/dashboard/dist/assets/{TooltipPopup-BJfFYesG.js → TooltipPopup-DvSoB_eS.js} +1 -1
  47. package/dashboard/dist/assets/{TrendMonitor-CElakFSU.js → TrendMonitor-5GVzh0Ye.js} +1 -1
  48. package/dashboard/dist/assets/{WidgetsPage-Cvd1LbJm.js → WidgetsPage-BIc4MBiu.js} +1 -1
  49. package/dashboard/dist/assets/{WrappedPage-wRC1tE_a.js → WrappedPage-BpQk4g0o.js} +1 -1
  50. package/dashboard/dist/assets/{__vite-browser-external-V4TPLCJG.js → __vite-browser-external-B_rKFTTN.js} +3 -3
  51. package/dashboard/dist/assets/{agent-logos-Ba57HF3w.js → agent-logos-DVg-sTm4.js} +1 -1
  52. package/dashboard/dist/assets/{arrow-left-B1bKnxXL.js → arrow-left-Ch6Q7eU5.js} +1 -1
  53. package/dashboard/dist/assets/{arrow-right-DrOlKzX5.js → arrow-right-B2B9GvSI.js} +1 -1
  54. package/dashboard/dist/assets/{arrow-up-right-hlYpo3yw.js → arrow-up-right-DwNO_uSb.js} +1 -1
  55. package/dashboard/dist/assets/{calendar-days-CSY6SEsI.js → calendar-days---NjJlBi.js} +1 -1
  56. package/dashboard/dist/assets/{check-xrP1gLw5.js → check-D3mhgRrW.js} +1 -1
  57. package/dashboard/dist/assets/{chevron-down-6-zT5n9d.js → chevron-down-x-b39Zto.js} +1 -1
  58. package/dashboard/dist/assets/{cloud-checkout-BZymZD4K.js → cloud-checkout-CycWyTsg.js} +1 -1
  59. package/dashboard/dist/assets/{composite-C9izWafa.js → composite-CnA1qtYn.js} +1 -1
  60. package/dashboard/dist/assets/{copy-Dkas5wZX.js → copy-JRnRMgdX.js} +1 -1
  61. package/dashboard/dist/assets/{download-CagGRs0_.js → download-CRLqGD62.js} +1 -1
  62. package/dashboard/dist/assets/{external-link-DRl10hxd.js → external-link-BrmaWV_t.js} +1 -1
  63. package/dashboard/dist/assets/{flame-CkvmsTFB.js → flame-meQ5-vRD.js} +1 -1
  64. package/dashboard/dist/assets/{format-tokens-BtwhO3mV.js → format-tokens-D8mIJGz1.js} +1 -1
  65. package/dashboard/dist/assets/{gift-YzjXkMQx.js → gift-Cd0FPXgO.js} +1 -1
  66. package/dashboard/dist/assets/{icons-CHhKYs0u.js → icons-D-PTKw7b.js} +1 -1
  67. package/dashboard/dist/assets/{index-B0MQZTrM.js → index-CMV9UKNf.js} +1 -1
  68. package/dashboard/dist/assets/{index-DojzoMqd.js → index-C_6gEM83.js} +1 -1
  69. package/dashboard/dist/assets/{index-CJknN9Jd.js → index-DCIZaLNC.js} +1 -1
  70. package/dashboard/dist/assets/index-HLYcwmPD.js +502 -0
  71. package/dashboard/dist/assets/{info-Torep-Ti.js → info-e0tP9P3D.js} +1 -1
  72. package/dashboard/dist/assets/{link-2-DE5Yu-z6.js → link-2-DZ2TQhER.js} +1 -1
  73. package/dashboard/dist/assets/{loader-circle-BL8LxD9n.js → loader-circle-DjflRntu.js} +1 -1
  74. package/dashboard/dist/assets/{local-usage-transfer-8X1InWix.js → local-usage-transfer-XTZLA3XH.js} +1 -1
  75. package/dashboard/dist/assets/{modifiers.esm-D60-6UQa.js → modifiers.esm-BpEQ8-Gt.js} +1 -1
  76. package/dashboard/dist/assets/module-BbVsRaE2.js +3 -0
  77. package/dashboard/dist/assets/{monitor-smartphone-C-IFPjqk.js → monitor-smartphone-VVBcI30G.js} +1 -1
  78. package/dashboard/dist/assets/{play-n6uWrdH9.js → play-DN4Sb3W_.js} +1 -1
  79. package/dashboard/dist/assets/{plus-B2f6TaNj.js → plus-Cz_k7arH.js} +1 -1
  80. package/dashboard/dist/assets/{provider-display-DWqCEcsP.js → provider-display-S1mSaydF.js} +1 -1
  81. package/dashboard/dist/assets/{react-B-zfi_j6.js → react-MQBcjrn_.js} +1 -1
  82. package/dashboard/dist/assets/{safePolygon-C2PsvI-v.js → safePolygon-InKxYcXd.js} +1 -1
  83. package/dashboard/dist/assets/{search-DZOaTLF9.js → search-C-N0RDi0.js} +1 -1
  84. package/dashboard/dist/assets/{server-DIp4Qfsv.js → server-CZkn8vIZ.js} +1 -1
  85. package/dashboard/dist/assets/{shield-check-Cs2M8-iT.js → shield-check-DPrGFouZ.js} +1 -1
  86. package/dashboard/dist/assets/{skills-api-CoYy6Tov.js → skills-api-wMwM-GpF.js} +1 -1
  87. package/dashboard/dist/assets/{sortable.esm-B5_hjIfs.js → sortable.esm-CUPP1ihT.js} +1 -1
  88. package/dashboard/dist/assets/{store-BLXqtJzb.js → store-BpVdRYnQ.js} +1 -1
  89. package/dashboard/dist/assets/{terminal-CEMKX3ba.js → terminal-C-7TFeKg.js} +1 -1
  90. package/dashboard/dist/assets/{trash-DOWYaXjK.js → trash-Be8cd4HD.js} +1 -1
  91. package/dashboard/dist/assets/{use-community-stats-BjRI1Bzz.js → use-community-stats-cG0WrB4Y.js} +1 -1
  92. package/dashboard/dist/assets/{use-latest-request-guard-T7L5qBU6.js → use-latest-request-guard-C91enyPD.js} +1 -1
  93. package/dashboard/dist/assets/{use-limits-display-prefs-vNIrCPZA.js → use-limits-display-prefs-DdwoLqPD.js} +1 -1
  94. package/dashboard/dist/assets/{use-native-settings-Cg5Zb4q_.js → use-native-settings-BR-NiMKh.js} +1 -1
  95. package/dashboard/dist/assets/{use-ordered-list-BF-wIvmG.js → use-ordered-list-oqV724BP.js} +1 -1
  96. package/dashboard/dist/assets/use-reduced-motion-DW5YHBBI.js +1 -0
  97. package/dashboard/dist/assets/{use-transform-CbJ1IcNc.js → use-transform-BsNSgVD6.js} +1 -1
  98. package/dashboard/dist/assets/{use-usage-limits-Dgg3jpC4.js → use-usage-limits-Dk8LcMU-.js} +1 -1
  99. package/dashboard/dist/assets/{useCurrency-3oBHWfSe.js → useCurrency-FxerRfd2.js} +1 -1
  100. package/dashboard/dist/assets/{usePositioner-D0YrIW0A.js → usePositioner-D2J-rSfX.js} +1 -1
  101. package/dashboard/dist/assets/{useTokenFormat-DZAHWE_R.js → useTokenFormat-CaI2nl2C.js} +1 -1
  102. package/dashboard/dist/assets/{zap-kLub6Jiv.js → zap-D3C37XAj.js} +1 -1
  103. package/dashboard/dist/index.html +1 -1
  104. package/dashboard/dist/ip-check.html +1 -1
  105. package/dashboard/dist/leaderboard.html +1 -1
  106. package/dashboard/dist/share.html +1 -1
  107. package/package.json +4 -4
  108. package/src/lib/cursor-config.js +15 -1
  109. package/src/lib/pricing/curated-overrides.json +14 -0
  110. package/src/lib/pricing/seed-snapshot.json +1 -1
  111. package/src/lib/rollout.js +13 -4
  112. package/src/lib/usage-limits.js +266 -10
  113. package/dashboard/dist/assets/DialogDescription-DztHQvj-.js +0 -1
  114. package/dashboard/dist/assets/TokenGalaxy-D2d738xr.js +0 -4426
  115. package/dashboard/dist/assets/index-C2DK8av-.js +0 -502
  116. package/dashboard/dist/assets/module-ZyVsxBG_.js +0 -3
  117. package/dashboard/dist/assets/use-reduced-motion-B9K5mIx4.js +0 -1
@@ -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. Used by the mimo/zcode discriminators so they work
5591
- // against both generations without duplicating the resolution logic.
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
- if (isAllZeroUsage(delta)) continue;
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
 
@@ -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
- throw new Error("Not logged in to Gemini. Run 'gemini' in Terminal to authenticate.");
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 { configured: true, error: "Gemini API key auth not supported. Use Google account (OAuth) instead." };
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 { configured: true, error: "Gemini Vertex AI auth not supported. Use Google account (OAuth) instead." };
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 { configured: true, error: "Not logged in to Gemini. Run 'gemini' in Terminal to authenticate." };
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
- throw new Error("Not logged in to Gemini. Run 'gemini' in Terminal to authenticate.");
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
- const payload = await fetchAntigravityQuotaSummaryJson(fetchImpl, accessToken, signal);
4061
- const windows = normalizeAntigravityQuotaSummary(payload);
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(gemini, gemini.account_plan, "Gemini"),
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};