tokentracker-cli 0.20.0 → 0.21.0

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.
@@ -25,6 +25,9 @@ const {
25
25
  let cache = { data: null, fetchedAt: 0 };
26
26
  const CACHE_TTL_MS = 2 * 60 * 1000;
27
27
  const DEFAULT_PROVIDER_TIMEOUT_MS = 15_000;
28
+ const ANTIGRAVITY_LIMITS_CACHE_FILE = "usage-limits-cache.json";
29
+ const ANTIGRAVITY_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
30
+ const ANTIGRAVITY_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS = 12 * 60 * 60 * 1000;
28
31
 
29
32
  function clampPercent(value) {
30
33
  if (value === null || value === undefined || value === "") return null;
@@ -808,9 +811,15 @@ function runCommand(commandRunner, command, args, options = {}) {
808
811
  });
809
812
  }
810
813
 
811
- function isBinaryAvailable(binary, { commandRunner } = {}) {
814
+ function whichBinary(binary, { commandRunner } = {}) {
812
815
  const result = runCommand(commandRunner, "which", [binary], { timeout: 2000 });
813
- return !result?.error && result?.status === 0;
816
+ if (result?.error || result?.status !== 0) return null;
817
+ const stdout = typeof result?.stdout === "string" ? result.stdout.trim() : "";
818
+ return stdout ? stdout.split("\n")[0] : null;
819
+ }
820
+
821
+ function isBinaryAvailable(binary, { commandRunner } = {}) {
822
+ return whichBinary(binary, { commandRunner }) !== null;
814
823
  }
815
824
 
816
825
  function stripAnsi(text) {
@@ -1173,11 +1182,84 @@ function detectAntigravityProcess({ commandRunner } = {}) {
1173
1182
  return { configured: false };
1174
1183
  }
1175
1184
 
1176
- function resolveLsofBinary() {
1185
+ function resolveAntigravityLimitsCachePath({ home } = {}) {
1186
+ return path.join(home || os.homedir(), ".tokentracker", "tracker", ANTIGRAVITY_LIMITS_CACHE_FILE);
1187
+ }
1188
+
1189
+ function parseTimeMs(value) {
1190
+ if (typeof value !== "string" || !value) return null;
1191
+ const ts = Date.parse(value);
1192
+ return Number.isFinite(ts) && ts > 0 ? ts : null;
1193
+ }
1194
+
1195
+ function isCacheWindowUsable(window, { cachedAtMs, nowMs } = {}) {
1196
+ if (!window || typeof window !== "object") return false;
1197
+ const resetAtMs = parseTimeMs(window.reset_at);
1198
+ if (resetAtMs !== null) return resetAtMs > nowMs;
1199
+ return Number.isFinite(cachedAtMs)
1200
+ && nowMs - cachedAtMs <= ANTIGRAVITY_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS;
1201
+ }
1202
+
1203
+ function hasAntigravityWindow(limits) {
1204
+ return Boolean(limits?.primary_window || limits?.secondary_window || limits?.tertiary_window);
1205
+ }
1206
+
1207
+ function normalizeAntigravityCachedLimits(raw, { nowMs = Date.now() } = {}) {
1208
+ const cachedAtMs = parseTimeMs(raw?.cached_at);
1209
+ if (!Number.isFinite(cachedAtMs)) return null;
1210
+ if (cachedAtMs > nowMs + 60_000) return null;
1211
+ if (nowMs - cachedAtMs > ANTIGRAVITY_LIMITS_CACHE_MAX_AGE_MS) return null;
1212
+
1213
+ const cached = {
1214
+ configured: true,
1215
+ error: null,
1216
+ account_email: typeof raw?.account_email === "string" ? raw.account_email : null,
1217
+ account_plan: typeof raw?.account_plan === "string" ? raw.account_plan : null,
1218
+ primary_window: isCacheWindowUsable(raw?.primary_window, { cachedAtMs, nowMs }) ? raw.primary_window : null,
1219
+ secondary_window: isCacheWindowUsable(raw?.secondary_window, { cachedAtMs, nowMs }) ? raw.secondary_window : null,
1220
+ tertiary_window: isCacheWindowUsable(raw?.tertiary_window, { cachedAtMs, nowMs }) ? raw.tertiary_window : null,
1221
+ cached: true,
1222
+ cached_at: raw.cached_at,
1223
+ };
1224
+ return hasAntigravityWindow(cached) ? cached : null;
1225
+ }
1226
+
1227
+ function readAntigravityLimitsCache({ home, nowMs = Date.now() } = {}) {
1228
+ const cachePath = resolveAntigravityLimitsCachePath({ home });
1229
+ try {
1230
+ const parsed = JSON.parse(fs.readFileSync(cachePath, "utf8"));
1231
+ return normalizeAntigravityCachedLimits(parsed?.antigravity, { nowMs });
1232
+ } catch (_error) {
1233
+ return null;
1234
+ }
1235
+ }
1236
+
1237
+ function writeAntigravityLimitsCache(limits, { home, nowMs = Date.now() } = {}) {
1238
+ if (!limits?.configured || limits.error || !hasAntigravityWindow(limits)) return;
1239
+ const cachePath = resolveAntigravityLimitsCachePath({ home });
1240
+ const payload = {
1241
+ antigravity: {
1242
+ account_email: limits.account_email || null,
1243
+ account_plan: limits.account_plan || null,
1244
+ primary_window: limits.primary_window || null,
1245
+ secondary_window: limits.secondary_window || null,
1246
+ tertiary_window: limits.tertiary_window || null,
1247
+ cached_at: new Date(nowMs).toISOString(),
1248
+ },
1249
+ };
1250
+ try {
1251
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
1252
+ const tmpPath = `${cachePath}.${process.pid}.tmp`;
1253
+ fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 0o600 });
1254
+ fs.renameSync(tmpPath, cachePath);
1255
+ } catch (_error) {}
1256
+ }
1257
+
1258
+ function resolveLsofBinary({ commandRunner } = {}) {
1177
1259
  for (const candidate of ["/usr/sbin/lsof", "/usr/bin/lsof"]) {
1178
1260
  if (fs.existsSync(candidate)) return candidate;
1179
1261
  }
1180
- return null;
1262
+ return whichBinary("lsof", { commandRunner });
1181
1263
  }
1182
1264
 
1183
1265
  function parseListeningPorts(output) {
@@ -1193,7 +1275,7 @@ function parseListeningPorts(output) {
1193
1275
  }
1194
1276
 
1195
1277
  function listAntigravityPorts(pid, { commandRunner } = {}) {
1196
- const lsof = resolveLsofBinary();
1278
+ const lsof = resolveLsofBinary({ commandRunner });
1197
1279
  if (!lsof) {
1198
1280
  throw new Error("Antigravity port detection needs lsof. Install it, then retry.");
1199
1281
  }
@@ -1446,15 +1528,25 @@ async function probeAntigravityPort(port, csrfToken, { timeoutMs, requestFn } =
1446
1528
  }
1447
1529
  }
1448
1530
 
1449
- async function fetchAntigravityLimits({ commandRunner, requestFn, timeoutMs = 8000 } = {}) {
1531
+ async function fetchAntigravityLimits({ home, commandRunner, requestFn, timeoutMs = 8000, nowMs = Date.now() } = {}) {
1450
1532
  const processInfo = detectAntigravityProcess({ commandRunner });
1451
1533
  if (!processInfo.configured) {
1452
- return { configured: false };
1534
+ return readAntigravityLimitsCache({ home, nowMs }) || { configured: false };
1453
1535
  }
1454
1536
  if (processInfo.error) {
1455
1537
  return { configured: true, error: processInfo.error };
1456
1538
  }
1457
1539
 
1540
+ const finalize = (payload, normalizeOptions) => {
1541
+ const result = {
1542
+ configured: true,
1543
+ error: null,
1544
+ ...normalizeAntigravityResponse(payload, normalizeOptions),
1545
+ };
1546
+ writeAntigravityLimitsCache(result, { home, nowMs });
1547
+ return result;
1548
+ };
1549
+
1458
1550
  try {
1459
1551
  const ports = listAntigravityPorts(processInfo.pid, { commandRunner });
1460
1552
  let workingPort = null;
@@ -1478,11 +1570,7 @@ async function fetchAntigravityLimits({ commandRunner, requestFn, timeoutMs = 80
1478
1570
  timeoutMs,
1479
1571
  requestFn,
1480
1572
  });
1481
- return {
1482
- configured: true,
1483
- error: null,
1484
- ...normalizeAntigravityResponse(userStatus),
1485
- };
1573
+ return finalize(userStatus);
1486
1574
  } catch (primaryError) {
1487
1575
  const fallbackPort =
1488
1576
  Number.isFinite(processInfo.extensionPort) && processInfo.extensionPort > 0
@@ -1497,11 +1585,7 @@ async function fetchAntigravityLimits({ commandRunner, requestFn, timeoutMs = 80
1497
1585
  timeoutMs,
1498
1586
  requestFn,
1499
1587
  });
1500
- return {
1501
- configured: true,
1502
- error: null,
1503
- ...normalizeAntigravityResponse(modelConfigs, { fallbackToConfigs: true }),
1504
- };
1588
+ return finalize(modelConfigs, { fallbackToConfigs: true });
1505
1589
  }
1506
1590
  } catch (error) {
1507
1591
  const message = error?.message === "timeout"
@@ -1594,7 +1678,7 @@ async function getUsageLimits({
1594
1678
  withProviderTimeout(fetchGeminiLimits({ home, env, fetchImpl: providerFetch, commandRunner }), "Gemini", providerTimeoutMs)
1595
1679
  .catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
1596
1680
  Promise.resolve().then(() => fetchKiroLimits({ commandRunner, now })),
1597
- fetchAntigravityLimits({ commandRunner, requestFn }),
1681
+ fetchAntigravityLimits({ home, commandRunner, requestFn, nowMs }),
1598
1682
  withProviderTimeout(fetchCopilotLimits({ home, env, fetchImpl: providerFetch }), "GitHub Copilot", providerTimeoutMs)
1599
1683
  .catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
1600
1684
  ]);
@@ -1670,6 +1754,7 @@ module.exports = {
1670
1754
  normalizeAntigravityResponse,
1671
1755
  parseListeningPorts,
1672
1756
  detectAntigravityProcess,
1757
+ fetchAntigravityLimits,
1673
1758
  fetchCopilotLimits,
1674
1759
  readCopilotOauthToken,
1675
1760
  describeCopilotOtelStatus,