tokentracker-cli 0.49.3 → 0.50.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dashboard/dist/assets/{ActivityHeatmap-DByCSENs.js → ActivityHeatmap-Csg0JY68.js} +1 -1
  2. package/dashboard/dist/assets/{Card-B71GwGxO.js → Card-BDI8Fbh9.js} +1 -1
  3. package/dashboard/dist/assets/DashboardPage-woYtbruG.js +19 -0
  4. package/dashboard/dist/assets/{DevicePage-Cfj34qew.js → DevicePage-EjJbP8Br.js} +1 -1
  5. package/dashboard/dist/assets/{DialogTitle-BdNBXNZX.js → DialogTitle-D66QJAs3.js} +1 -1
  6. package/dashboard/dist/assets/{FadeIn-BuCfpXMc.js → FadeIn-D3DCx8y6.js} +1 -1
  7. package/dashboard/dist/assets/{HeaderGithubStar-DpZhDW9t.js → HeaderGithubStar-DpuKyZW3.js} +1 -1
  8. package/dashboard/dist/assets/{IpCheckPage-DJtvaczo.js → IpCheckPage-Dz3YjhM0.js} +1 -1
  9. package/dashboard/dist/assets/{LandingPage-DVR_EDlv.js → LandingPage-B5UjGCVn.js} +1 -1
  10. package/dashboard/dist/assets/{LeaderboardAvatar-C6KucnuS.js → LeaderboardAvatar-C1HvZXs0.js} +1 -1
  11. package/dashboard/dist/assets/{LeaderboardPage-DKHvUmGb.js → LeaderboardPage-CIp04vrj.js} +3 -3
  12. package/dashboard/dist/assets/{LeaderboardProfileModal-sb4nmMCj.js → LeaderboardProfileModal-BH31dBaK.js} +1 -1
  13. package/dashboard/dist/assets/{LeaderboardProfilePage-BK-1QWfg.js → LeaderboardProfilePage-DpXj5L5i.js} +1 -1
  14. package/dashboard/dist/assets/{LimitsPage-D7m20XVf.js → LimitsPage-DVg3GfTr.js} +1 -1
  15. package/dashboard/dist/assets/{LocalOnlyNotice-DZslFoj5.js → LocalOnlyNotice-BADyYIuM.js} +1 -1
  16. package/dashboard/dist/assets/LoginPage-BId7DDhf.js +1 -0
  17. package/dashboard/dist/assets/{PopoverPopup-BjP_x_N0.js → PopoverPopup-BQhCMi7D.js} +1 -1
  18. package/dashboard/dist/assets/ResetPasswordPage-Bgj2woMZ.js +1 -0
  19. package/dashboard/dist/assets/{Select-B5C3SgjS.js → Select-BRQHUMnI.js} +1 -1
  20. package/dashboard/dist/assets/{SelectItemText-XBZTRfTk.js → SelectItemText-DKPqC7Id.js} +1 -1
  21. package/dashboard/dist/assets/{SettingsPage-C3pgKsph.js → SettingsPage-D7LxfBPd.js} +1 -1
  22. package/dashboard/dist/assets/{SkillsPage-C884cEOL.js → SkillsPage-cFXqx_rg.js} +1 -1
  23. package/dashboard/dist/assets/{WidgetsPage-Wsg4rX9h.js → WidgetsPage-D6wEB36p.js} +1 -1
  24. package/dashboard/dist/assets/{WrappedPage-BSWIQwdE.js → WrappedPage-C6tq3AaH.js} +1 -1
  25. package/dashboard/dist/assets/{agent-logos-umK0ojkk.js → agent-logos-CjZOhWL1.js} +1 -1
  26. package/dashboard/dist/assets/{arrow-up-right-BGMGSKrn.js → arrow-up-right-BVQI0Szz.js} +1 -1
  27. package/dashboard/dist/assets/{download-DBdm8vu1.js → download-45UoPdKK.js} +1 -1
  28. package/dashboard/dist/assets/{info-Dc1md1UE.js → info-ClM58qOx.js} +1 -1
  29. package/dashboard/dist/assets/{main-Br5SsufY.css → main-B1lf1bLt.css} +1 -1
  30. package/dashboard/dist/assets/main-CR3s46Km.js +1009 -0
  31. package/dashboard/dist/assets/{use-limits-display-prefs-yZId6XkL.js → use-limits-display-prefs-Boy1oEqT.js} +1 -1
  32. package/dashboard/dist/assets/{use-native-settings-CquG9vh_.js → use-native-settings-CGKAtYGs.js} +1 -1
  33. package/dashboard/dist/assets/{use-usage-limits-BvhFrG8a.js → use-usage-limits-kGnhUHc-.js} +1 -1
  34. package/dashboard/dist/assets/{useCurrency-DCzLyxzN.js → useCurrency-xorCxnW3.js} +1 -1
  35. package/dashboard/dist/assets/{useScrollLock-C5gi8Y07.js → useScrollLock-B0eyFEUR.js} +1 -1
  36. package/dashboard/dist/index.html +2 -2
  37. package/dashboard/dist/share.html +2 -2
  38. package/package.json +1 -1
  39. package/src/lib/cloud-account.js +58 -7
  40. package/src/lib/local-api.js +6 -1
  41. package/src/lib/pricing/seed-snapshot.json +1 -1
  42. package/src/lib/rollout.js +11 -3
  43. package/src/lib/usage-limits.js +35 -6
  44. package/dashboard/dist/assets/DashboardPage-BjOx4W82.js +0 -19
  45. package/dashboard/dist/assets/LoginPage-HQ8ks6KL.js +0 -1
  46. package/dashboard/dist/assets/main-BTU7nBLK.js +0 -999
@@ -7590,15 +7590,23 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
7590
7590
 
7591
7591
  const inputRaw = toNonNegativeInt(attrs["gen_ai.usage.input_tokens"]);
7592
7592
  const output = toNonNegativeInt(attrs["gen_ai.usage.output_tokens"]);
7593
- const cacheRead = toNonNegativeInt(attrs["gen_ai.usage.cache_read.input_tokens"]);
7593
+ const cacheRead = toNonNegativeInt(
7594
+ attrs["gen_ai.usage.cache_read.input_tokens"] ??
7595
+ attrs["gen_ai.usage.cache_read_input_tokens"] ??
7596
+ attrs["gen_ai.usage.cached_input_tokens"],
7597
+ );
7594
7598
  // Copilot CLI: cache_write.input_tokens; Copilot Chat extension: cache_creation.input_tokens
7595
7599
  const cacheWrite = toNonNegativeInt(
7596
7600
  attrs["gen_ai.usage.cache_write.input_tokens"] ??
7597
- attrs["gen_ai.usage.cache_creation.input_tokens"],
7601
+ attrs["gen_ai.usage.cache_creation.input_tokens"] ??
7602
+ attrs["gen_ai.usage.cache_write_input_tokens"] ??
7603
+ attrs["gen_ai.usage.cache_creation_input_tokens"],
7598
7604
  );
7599
7605
  // Copilot CLI: reasoning.output_tokens; Copilot Chat extension: reasoning_tokens
7600
7606
  const reasoning = toNonNegativeInt(
7601
- attrs["gen_ai.usage.reasoning.output_tokens"] ?? attrs["gen_ai.usage.reasoning_tokens"],
7607
+ attrs["gen_ai.usage.reasoning.output_tokens"] ??
7608
+ attrs["gen_ai.usage.reasoning_tokens"] ??
7609
+ attrs["gen_ai.usage.reasoning_output_tokens"],
7602
7610
  );
7603
7611
  // OTEL input_tokens INCLUDES cache_read — subtract per project convention
7604
7612
  const cacheReadClamped = Math.min(cacheRead, inputRaw);
@@ -36,6 +36,7 @@ const ANTIGRAVITY_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS = 12 * 60 * 60 * 1000;
36
36
  // file with only its own key, so a shared file would clobber).
37
37
  const CLAUDE_LIMITS_CACHE_FILE = "claude-usage-limits-cache.json";
38
38
  const CLAUDE_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
39
+ const CLAUDE_LIMITS_CACHE_FRESH_TTL_MS = 10 * 60 * 1000;
39
40
  // A 429 from the usage endpoint carries a long `retry-after` (often 20+ minutes). Persist
40
41
  // the cooldown so every surface — this process, the menu bar app's embedded server, a later
41
42
  // restart — stops calling until it expires. Hammering during the cooldown just renews the
@@ -1484,11 +1485,18 @@ function hasClaudeWindow(limits) {
1484
1485
  return Boolean(limits?.five_hour || limits?.seven_day || limits?.seven_day_opus);
1485
1486
  }
1486
1487
 
1487
- function normalizeClaudeCachedLimits(raw, { nowMs = Date.now() } = {}) {
1488
+ function normalizeClaudeCachedLimits(
1489
+ raw,
1490
+ {
1491
+ nowMs = Date.now(),
1492
+ maxAgeMs = CLAUDE_LIMITS_CACHE_MAX_AGE_MS,
1493
+ stale = true,
1494
+ } = {},
1495
+ ) {
1488
1496
  const cachedAtMs = parseTimeMs(raw?.cached_at);
1489
1497
  if (!Number.isFinite(cachedAtMs)) return null;
1490
1498
  if (cachedAtMs > nowMs + 60_000) return null;
1491
- if (nowMs - cachedAtMs > CLAUDE_LIMITS_CACHE_MAX_AGE_MS) return null;
1499
+ if (nowMs - cachedAtMs > maxAgeMs) return null;
1492
1500
 
1493
1501
  const cached = {
1494
1502
  configured: true,
@@ -1497,22 +1505,36 @@ function normalizeClaudeCachedLimits(raw, { nowMs = Date.now() } = {}) {
1497
1505
  seven_day: isClaudeCacheWindowUsable(raw?.seven_day, { nowMs }) ? raw.seven_day : null,
1498
1506
  seven_day_opus: isClaudeCacheWindowUsable(raw?.seven_day_opus, { nowMs }) ? raw.seven_day_opus : null,
1499
1507
  extra_usage: raw?.extra_usage ?? null,
1500
- stale: true,
1508
+ stale,
1501
1509
  cached_at: raw.cached_at,
1502
1510
  };
1503
1511
  return hasClaudeWindow(cached) ? cached : null;
1504
1512
  }
1505
1513
 
1506
- function readClaudeLimitsCache({ home, nowMs = Date.now() } = {}) {
1514
+ function readClaudeLimitsCache({
1515
+ home,
1516
+ nowMs = Date.now(),
1517
+ maxAgeMs = CLAUDE_LIMITS_CACHE_MAX_AGE_MS,
1518
+ stale = true,
1519
+ } = {}) {
1507
1520
  const cachePath = resolveClaudeLimitsCachePath({ home });
1508
1521
  try {
1509
1522
  const parsed = JSON.parse(fs.readFileSync(cachePath, "utf8"));
1510
- return normalizeClaudeCachedLimits(parsed?.claude, { nowMs });
1523
+ return normalizeClaudeCachedLimits(parsed?.claude, { nowMs, maxAgeMs, stale });
1511
1524
  } catch (_error) {
1512
1525
  return null;
1513
1526
  }
1514
1527
  }
1515
1528
 
1529
+ function readFreshClaudeLimitsCache({ home, nowMs = Date.now() } = {}) {
1530
+ return readClaudeLimitsCache({
1531
+ home,
1532
+ nowMs,
1533
+ maxAgeMs: CLAUDE_LIMITS_CACHE_FRESH_TTL_MS,
1534
+ stale: false,
1535
+ });
1536
+ }
1537
+
1516
1538
  function writeClaudeLimitsCache(limits, { home, nowMs = Date.now() } = {}) {
1517
1539
  if (!limits?.configured || limits.error || !hasClaudeWindow(limits)) return;
1518
1540
  const cachePath = resolveClaudeLimitsCachePath({ home });
@@ -2016,10 +2038,15 @@ async function fetchUsageLimitsUncached({
2016
2038
  // Skip the upstream Claude call entirely while a 429 cooldown is active — calling again
2017
2039
  // just renews the penalty. The result handling below serves cache or a cooldown message.
2018
2040
  const claudeRetryAtMs = claudeToken ? readClaudeRateLimitRetryAtMs({ home, nowMs }) : null;
2041
+ // Also avoid cross-process hammering after a recent successful read. The macOS app can
2042
+ // restart its embedded Node server or force-refresh the limits page, both of which clear
2043
+ // the in-memory cache; the disk cache keeps those paths from immediately spending
2044
+ // another Claude OAuth usage request.
2045
+ const freshClaudeCache = claudeToken ? readFreshClaudeLimitsCache({ home, nowMs }) : null;
2019
2046
 
2020
2047
  const providerFetch = withFetchTimeout(fetchImpl, providerTimeoutMs);
2021
2048
  const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok] = await Promise.all([
2022
- claudeToken && !claudeRetryAtMs
2049
+ claudeToken && !freshClaudeCache && !claudeRetryAtMs
2023
2050
  ? withProviderTimeout(fetchClaudeUsageLimits(claudeToken, { fetchImpl: providerFetch, maxAttempts: 1 }), "Claude", providerTimeoutMs).then(
2024
2051
  (value) => ({ status: "fulfilled", value }),
2025
2052
  (reason) => ({ status: "rejected", reason }),
@@ -2052,6 +2079,8 @@ async function fetchUsageLimitsUncached({
2052
2079
  let claude;
2053
2080
  if (!claudeToken) {
2054
2081
  claude = { configured: false };
2082
+ } else if (freshClaudeCache) {
2083
+ claude = freshClaudeCache;
2055
2084
  } else if (claudeResult && claudeResult.status === "fulfilled") {
2056
2085
  claude = {
2057
2086
  configured: true,