/** * @file quota.js * @description How much of each native AI tool's plan quota is spent, for the * status bar's quota chips (`GET /api/pricing/quota`). * * - **Claude**: the logged-in account's `GET /api/oauth/usage` (`cc-usage.js`), * with the access token the CLI holds RIGHT NOW. It is never refreshed here — * a refresh rotates the CLI's credentials — so an expired token answers * `ELIVEEXPIRED` until Claude Code next refreshes it itself. Every OTHER * saved auth profile (`cc-profiles.js`) is asked with its own stored token * while that token is valid and never refreshed (a refresh rotates the * saved credentials); past expiry it shows its last figure, `asOf`. Cached * `CLAUDE_TTL_MS` per account, so a poll from every open tab is one * upstream call each. The live entry is `active: true`. Each token's owner * is asked too (`fetchOwner`): the live row is named after the account its * token belongs to (`.claude.json` can name another), and a saved profile * whose token belongs to a different account answers `EWRONGACCOUNT`, * never that account's usage. * - **Codex**: no network. Every `token_count` event Codex writes into its * rollout journal carries the account's `rate_limits`; the newest one wins. * - **Cursor**: the Cursor CLI's (`cursor-agent`) own login — * `$XDG_CONFIG_HOME/cursor/auth.json` — sent ONLY to cursor.com's * `GET /api/usage-summary` (undocumented; what cursor.com/dashboard reads), * as the `WorkosCursorSessionToken=::` cookie it expects, * while the token is valid and never refreshed. One `mo` window: the * billing cycle's `totalPercentUsed`. No file (macOS keeps it in the * keychain) means no row. * - **Antigravity**: `agy --print /usage --output-format json` — the CLI's * own `/usage` command (no model call: `num_turns` 0, no conversation), * run by the catalog-detected binary in a private empty directory, only * when the tool is enabled and `agy --version` is at least 1.1.11 (an * older agy may bill `/usage` as a model request). One window per pool * bucket, `group` naming the pool (Gemini / Claude and GPT). Its report * names no account and no plan. Cached like Claude; a failed run shows * the last figure with its `asOf`. Running agy may let it update itself; * `AGENTHUB_AGY_USAGE=0` never runs it. * - **Grok Build**: the CLI's `auth.x.ai` session in `$GROK_HOME/auth.json` * (default `~/.grok`), sent only to `cli-chat-proxy.grok.com`'s * `GET /v1/billing?format=credits`: `wk` from `creditUsagePercent`, `mo` * from `used / monthlyLimit`, plan `subscriptionTier`. * - **Kimi Code**: `$KIMI_CODE_HOME/credentials/kimi-code.json` (default * `~/.kimi-code`), sent only to `api.kimi.com`'s `GET /coding/v1/usages`: * `5h`, `wk`, `mo` from `limit_5h`/`limit_7d`/`limit_month_total`. * - **OpenCode Go**: opencode's own `opencode-go` API key — its `auth.json` * (OpenCode 1) else its `opencode.db` `credential` table (OpenCode 2), * under `$XDG_DATA_HOME/opencode` (default `~/.local/share`) — sent only to * `opencode.ai`'s `GET /zen/go/v1/usage`: `5h`, `wk`, `mo` meters. No Go * key (another provider's login) means no row. * Grok's and Kimi's tokens are used only while valid and never refreshed * (Kimi rotates its refresh token: a refresh here would sign the CLI out). * These three are Orca's readers (github.com/stablyai/orca, MIT), trimmed. * * Each tool also carries its signed-in `account` (an email), its `plan` (the * subscription's name as the tool reports it: Claude's `subscriptionType`, * Codex's `chatgpt_plan_type` claim, Cursor's `membershipType`) and * `expiresAt`, when its stored access token runs out — Claude's * `claudeAiOauth.expiresAt`, Codex's and Cursor's access-token `exp` (claims * read, never verified). * * Server mode with S3 (`quotas({home})`): a member's plan is read from THEIR * Linux account's home only — `.claude`, `.codex`, `.config/cursor` + * `.cursor`, `.grok`, `.kimi-code`, `.local/share/opencode`, never the * host's env overrides — through `run-as`'s owner-checked reads (F3). No * saved Claude profiles (the Profiles modal is the host's) and no agy: its * `/usage` needs the self-updating binary, so it stays host-only. * * Only percentages, window lengths, reset/expiry times, the plan's name and * the account's email leave this module — never a token. * @author Nguyễn Ngọc Trí Vĩ */ const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const { execFile } = require("node:child_process"); const { promisify } = require("node:util"); const aiTools = require("./ai-tools"); const { getCodexHome } = require("./codex-home"); const { readActiveAuth, readProfile, listProfiles } = require("./cc-profiles"); const { fetchUsage, fetchOwner } = require("./cc-usage"); const { allRolloutFiles } = require("./codex-ingest"); const { getCursorDir } = require("./cursor-usage"); const { Database } = require("../db"); const { readFileSafe, openSafe, ownerForRead } = require("./run-as"); const { s3Enabled } = require("./linux-accounts"); const CLAUDE_TTL_MS = 5 * 60_000; const FORCE_MIN_MS = 30_000; // a Refresh click still cannot hammer the endpoint const EXPIRY_MARGIN_MS = 60_000; const TAIL_BYTES = 512 * 1024; const CODEX_FILES = 5; const CURSOR_USAGE_URL = "https://cursor.com/api/usage-summary"; const TIMEOUT_MS = 10_000; const GROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits"; const KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages"; const OPENCODE_GO_URL = "https://opencode.ai/zen/go/v1/usage"; const AGY_TIMEOUT_MS = 60_000; const AGY_MIN_VERSION = [1, 1, 11]; const AGY_WINDOW = { "5h": "5h", weekly: "wk" }; const execFileAsync = promisify(execFile); const cache = new Map(); // "live" | "profile:" → { at, value } const lastGood = new Map(); // profile name → { windows, at } /** One upstream call per key per `CLAUDE_TTL_MS` (`FORCE_MIN_MS` on a click); * a call still in flight is shared, so two open tabs never run it twice. */ async function cached(key, force, fn) { const hit = cache.get(key); const age = hit ? Date.now() - hit.at : Infinity; if (age < (force ? FORCE_MIN_MS : CLAUDE_TTL_MS)) return hit.value; const value = fn(); cache.set(key, { at: Date.now(), value }); return value; } function windowLabel(minutes) { if (minutes === 300) return "5h"; if (minutes === 10080) return "wk"; if (minutes >= 40000 && minutes <= 45000) return "mo"; return minutes % 1440 === 0 ? `${minutes / 1440}d` : `${Math.round(minutes / 60)}h`; } const iso = (ms) => (Number(ms) ? new Date(Number(ms)).toISOString() : null); const fresh = (ms) => Number(ms) > Date.now() + EXPIRY_MARGIN_MS; function pct(w) { return { usedPercent: w.utilization, resetsAt: w.resetsAt }; } function claudeWindows(u) { const windows = []; if (u.fiveHour) windows.push({ label: "5h", ...pct(u.fiveHour) }); if (u.sevenDay) windows.push({ label: "wk", ...pct(u.sevenDay) }); return windows; } /** A token's windows — unless it belongs to another account than `uuid`. */ async function usageFor(token, uuid) { const [usage, owner] = await Promise.all([fetchUsage(token), fetchOwner(token)]); if (uuid && owner && owner.uuid !== uuid) { throw Object.assign(new Error("this sign-in belongs to another account"), { code: "EWRONGACCOUNT", }); } return claudeWindows(usage); } /** A member's Claude login: `/.claude/.credentials.json` + `.claude.json`. */ function memberClaudeAuth(home) { const creds = readJson(path.join(home, ".claude", ".credentials.json")); const cfg = [path.join(home, ".claude.json"), path.join(home, ".claude", ".claude.json")] .map(readJson) .find((c) => c?.oauthAccount); return { oauth: creds?.claudeAiOauth || null, account: cfg?.oauthAccount || null }; } /** The account the CLI is logged into, with the token it holds right now. */ async function liveClaude(readAuth = readActiveAuth) { let who = {}; try { const { oauth, account } = readAuth(); if (!oauth || !oauth.accessToken) return null; // API-key user or not logged in who = { account: account?.emailAddress || account?.displayName || null, plan: oauth.subscriptionType || null, expiresAt: iso(oauth.expiresAt), }; if (!fresh(oauth.expiresAt)) return { ...who, windows: [], error: "ELIVEEXPIRED" }; // The token's owner, not `.claude.json`, is who this usage is billed to. const [usage, owner] = await Promise.all([ fetchUsage(oauth.accessToken), fetchOwner(oauth.accessToken), ]); return { ...who, account: owner?.email || who.account, ownerUuid: owner?.uuid || account?.accountUuid || null, windows: claudeWindows(usage), }; } catch (e) { return { ...who, windows: [], error: e.code || "EUPSTREAM" }; } } /** * A saved profile that is not the live account, asked with its OWN stored * token only while that token is still valid. Never refreshed here: a refresh * rotates the profile's credentials on disk, so it happens only on the * Profiles modal's Check quota click (`cc-usage.profileUsage`). Past expiry * the last figure this process read is shown with its `asOf`. */ async function profileClaude(item) { const who = { account: item.email || item.displayName || null, plan: item.subscriptionType || null, expiresAt: iso(item.expiresAt), }; let creds = null; try { creds = readProfile(item.name)?.credentials || null; } catch { /* a corrupt profile file reads as signed out */ } if (creds?.accessToken && fresh(creds.expiresAt)) { try { const windows = await usageFor(creds.accessToken, item.accountUuid); lastGood.set(item.name, { windows, at: Date.now() }); return { ...who, windows }; } catch (e) { return { ...who, windows: [], error: e.code || "EUPSTREAM" }; } } const prev = lastGood.get(item.name); if (!prev) return { ...who, windows: [], error: "EPROFILEEXPIRED" }; return { ...who, windows: prev.windows.map(expireReset), asOf: iso(prev.at) }; } /** A window whose reset has passed is empty again. */ function expireReset(w) { return w.resetsAt && Date.parse(w.resetsAt) < Date.now() ? { ...w, usedPercent: 0 } : w; } /** The live account first, then every other saved profile (by label). */ async function claudeQuotas(force, home) { if (home) { const live = await cached(`${home}|live`, force, () => liveClaude(() => memberClaudeAuth(home)) ); if (!live) return []; const { ownerUuid: _uuid, ...shown } = live; return [{ id: "claude", active: true, label: null, ...shown }]; } let items = []; try { items = listProfiles().items; } catch { /* no profiles dir */ } const live = await cached("live", force, liveClaude); // The live row is the profile of whoever the token belongs to. const mine = live?.ownerUuid ? items.find((it) => it.accountUuid === live.ownerUuid) : items.find((it) => it.active); const others = await Promise.all( items .filter((it) => it !== mine) .map(async (it) => ({ id: "claude", active: false, label: it.label, ...(await cached(`profile:${it.name}`, force, () => profileClaude(it))), })) ); if (!live) return others; const { ownerUuid: _uuid, ...shown } = live; return [{ id: "claude", active: true, label: mine?.label || null, ...shown }, ...others]; } /** The newest `rate_limits` in the last `TAIL_BYTES` of a rollout, or null. */ function lastRateLimits(file) { let text; try { const fd = openSafe(file.path); try { const len = Math.min(file.size, TAIL_BYTES); const buf = Buffer.alloc(len); fs.readSync(fd, buf, 0, len, file.size - len); text = buf.toString("utf8"); } finally { fs.closeSync(fd); } } catch { return null; } const lines = text.split("\n"); for (let i = lines.length - 1; i >= 0; i--) { if (!lines[i].includes('"rate_limits"')) continue; try { const rl = JSON.parse(lines[i])?.payload?.rate_limits; if (rl && (rl.primary || rl.secondary)) return { rl, mtime: file.mtime }; } catch { /* a cut first line or a partial write */ } } return null; } function codexWindow(w, mtime) { if (!w || typeof w.used_percent !== "number") return null; const resetsMs = Number(w.resets_at) ? Number(w.resets_at) * 1000 : Number(w.resets_in_seconds) ? mtime + Number(w.resets_in_seconds) * 1000 : null; // A window that has reset since Codex last wrote is empty again. const used = resetsMs && resetsMs < Date.now() ? 0 : w.used_percent; return { label: windowLabel(Number(w.window_minutes)), usedPercent: used, resetsAt: resetsMs ? new Date(resetsMs).toISOString() : null, }; } /** A JWT's claims, unverified — only for showing who is signed in. */ function jwtClaims(token) { return JSON.parse(Buffer.from(String(token).split(".")[1], "base64url").toString("utf8")); } /** Codex's ChatGPT login from `auth.json`: email, plan + access-token expiry. */ function codexAccount(codexHome = getCodexHome()) { try { const { tokens } = JSON.parse(readFileSafe(path.join(codexHome, "auth.json"), "utf8", { max: 1 << 20 })); const { exp } = jwtClaims(tokens.access_token); const id = jwtClaims(tokens.id_token); return { account: id.email || null, plan: id["https://api.openai.com/auth"]?.chatgpt_plan_type || null, expiresAt: exp ? new Date(exp * 1000).toISOString() : null, }; } catch { return {}; // API-key login, no file, or a token that is not a JWT } } function codexQuota(codexHome = getCodexHome()) { const files = allRolloutFiles(codexHome) .sort((a, b) => b.mtime - a.mtime) .slice(0, CODEX_FILES); for (const f of files) { const found = lastRateLimits(f); if (!found) continue; const windows = [found.rl.primary, found.rl.secondary] .map((w) => codexWindow(w, found.mtime)) .filter(Boolean); return { id: "codex", active: true, ...codexAccount(codexHome), windows, asOf: new Date(found.mtime).toISOString(), }; } return null; } /** A JSON file, or null; a member's only if it is theirs (`readFileSafe`). */ function readJson(file) { try { return JSON.parse(readFileSafe(file, "utf8", { max: 1 << 20 })); } catch { return null; } } /** * Where each tool keeps its login: the host's (env overrides honoured), or a * member's home at the tools' defaults — a member's env is not the host's. */ function toolDirs(home) { const h = home || os.homedir(); const env = (k) => (home ? null : process.env[k]); return { codex: home ? path.join(home, ".codex") : getCodexHome(), cursorConfig: path.join(env("XDG_CONFIG_HOME") || path.join(h, ".config"), "cursor"), cursor: home ? path.join(home, ".cursor") : getCursorDir(), grok: env("GROK_HOME") || path.join(h, ".grok"), kimi: env("KIMI_CODE_HOME") || path.join(h, ".kimi-code"), opencode: path.join(env("XDG_DATA_HOME") || path.join(h, ".local", "share"), "opencode"), }; } /** The Cursor CLI's login and the account's plan + billing-cycle usage. */ async function cursorQuota(dirs = toolDirs()) { const token = readJson(path.join(dirs.cursorConfig, "auth.json"))?.accessToken; if (typeof token !== "string" || !token) return null; // not logged in let claims = {}; try { claims = jwtClaims(token); } catch { /* not a JWT: no expiry to check, cursor.com decides */ } const info = readJson(path.join(dirs.cursor, "cli-config.json"))?.authInfo || {}; const who = { id: "cursor", active: true, account: info.email || null, plan: null, expiresAt: claims.exp ? new Date(claims.exp * 1000).toISOString() : null, }; if (claims.exp && !fresh(claims.exp * 1000)) return { ...who, windows: [], error: "ETOKENEXPIRED" }; // The cookie names the user as the dashboard does: `user_…`, no `auth0|`. const user = String(info.authId || info.userId || claims.sub || "") .split("|") .pop(); try { const res = await fetch(CURSOR_USAGE_URL, { headers: { Accept: "application/json", Cookie: `WorkosCursorSessionToken=${user}::${token}` }, signal: AbortSignal.timeout(TIMEOUT_MS), }); if (!res.ok) { return { ...who, windows: [], error: res.status === 401 ? "ETOKENEXPIRED" : "EUPSTREAM" }; } const u = await res.json(); const p = u?.individualUsage?.plan; // Enterprise seats answer without `plan`: the plan name, no figure. const used = typeof p?.totalPercentUsed === "number" ? p.totalPercentUsed : null; return { ...who, plan: u?.membershipType || null, windows: used === null ? [] : [{ label: "mo", usedPercent: used, resetsAt: u.billingCycleEnd || null }], }; } catch { return { ...who, windows: [], error: "EUPSTREAM" }; } } /** `agy --version` at least `AGY_MIN_VERSION`. */ function agyVersionOk(text) { const m = /(\d+)\.(\d+)\.(\d+)/.exec(text); if (!m) return false; for (let i = 0; i < 3; i++) { const d = Number(m[i + 1]) - AGY_MIN_VERSION[i]; if (d) return d > 0; } return true; } /** `/usage`'s pools → windows: 5h before the week, in the pools' order. */ function agyWindows(report) { const windows = []; for (const g of report?.command?.data?.groups || []) { const rows = (g.buckets || []) .filter((b) => AGY_WINDOW[b.window] && typeof b.remaining_fraction === "number") .map((b) => { const resets = Date.parse(b.reset_time); return { group: String(g.name || ""), label: AGY_WINDOW[b.window], // A window past its reset is full again, as Codex's is. usedPercent: resets < Date.now() ? 0 : (1 - b.remaining_fraction) * 100, resetsAt: Number.isFinite(resets) ? new Date(resets).toISOString() : null, }; }); rows.sort((a, b) => (a.label === "5h" ? -1 : 0) - (b.label === "5h" ? -1 : 0)); windows.push(...rows); } return windows; } async function antigravityQuota() { if (process.env.AGENTHUB_AGY_USAGE === "0" || !aiTools.isEnabled("antigravity")) return null; const { installed, path: bin } = aiTools.detect("antigravity"); if (!installed || !bin) return null; const run = (args, cwd) => execFileAsync(bin, args, { cwd, timeout: AGY_TIMEOUT_MS, maxBuffer: 1024 * 1024 }); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-agy-usage-")); try { if (!agyVersionOk((await run(["--version"], dir)).stdout)) return null; const { stdout } = await run(["--print", "/usage", "--output-format", "json"], dir); const windows = agyWindows(JSON.parse(stdout)); if (!windows.length) throw new Error("no quota in /usage"); lastGood.set("antigravity", { windows, at: Date.now() }); return { id: "antigravity", active: true, windows }; } catch { const prev = lastGood.get("antigravity"); if (!prev) return { id: "antigravity", active: true, windows: [], error: "EUPSTREAM" }; return { id: "antigravity", active: true, windows: prev.windows.map(expireReset), asOf: iso(prev.at), }; } finally { fs.rmSync(dir, { recursive: true, force: true }); } } const isoOf = (v) => { const ms = Date.parse(v); return Number.isFinite(ms) ? new Date(ms).toISOString() : null; }; /** A Bearer GET to a tool's own host: its windows, or the row's error. */ async function bearerWindows(who, url, token, headers, toWindows) { try { const res = await fetch(url, { headers: { Accept: "application/json", Authorization: `Bearer ${token}`, ...headers }, signal: AbortSignal.timeout(TIMEOUT_MS), }); if (!res.ok) { const expired = res.status === 401 || res.status === 403; return { ...who, windows: [], error: expired ? "ETOKENEXPIRED" : "EUPSTREAM" }; } return { ...who, ...toWindows(await res.json()) }; } catch { return { ...who, windows: [], error: "EUPSTREAM" }; } } /** Grok Build's `auth.x.ai` session and its billing view. */ async function grokQuota(dirs = toolDirs()) { const sessions = readJson(path.join(dirs.grok, "auth.json")); if (!sessions || typeof sessions !== "object") return null; const xai = Object.entries(sessions) .filter(([issuer, s]) => /^https:\/\/auth\.x\.ai(::|$)/.test(issuer) && s?.key) .map(([, s]) => s); const live = (s) => !s.expires_at || fresh(Date.parse(s.expires_at)); const s = xai.find(live) || xai[0]; if (!s) return null; const who = { id: "grok", active: true, account: s.email || null, plan: null, expiresAt: s.expires_at ? isoOf(s.expires_at) : null, }; if (!live(s)) return { ...who, windows: [], error: "ETOKENEXPIRED" }; const headers = { "X-XAI-Token-Auth": "xai-grok-cli", ...(s.user_id && { "x-userid": String(s.user_id) }), }; return bearerWindows(who, GROK_BILLING_URL, s.key, headers, (body) => { const c = body?.config || body || {}; const windows = []; // ponytail: a reply with no percent shows no week; Orca infers 0 from // the period fields — add that if a real account ever answers so. if (typeof c.creditUsagePercent === "number") { windows.push({ label: "wk", usedPercent: c.creditUsagePercent, resetsAt: isoOf(c.currentPeriod?.end ?? c.billingPeriodEnd), }); } const limit = Number(c.monthlyLimit?.val); const used = Number(c.used?.val); if (limit > 0 && Number.isFinite(used)) { windows.push({ label: "mo", usedPercent: (used / limit) * 100, resetsAt: isoOf(c.billingPeriodEnd), }); } return { plan: c.subscriptionTier || null, windows }; }); } /** Kimi Code's OAuth token and its managed-plan usage. */ async function kimiQuota(dirs = toolDirs()) { const cred = readJson(path.join(dirs.kimi, "credentials", "kimi-code.json")); if (typeof cred?.access_token !== "string" || !cred.access_token) return null; const exp = Number(cred.expires_at) * 1000; const who = { id: "kimi", active: true, expiresAt: exp ? iso(exp) : null }; if (exp && !fresh(exp)) return { ...who, windows: [], error: "ETOKENEXPIRED" }; return bearerWindows(who, KIMI_USAGE_URL, cred.access_token, {}, (body) => ({ windows: [ ["5h", body?.usages?.limit_5h], ["wk", body?.usages?.limit_7d], ["mo", body?.usages?.limit_month_total], ] .filter(([, u]) => u && Number.isFinite(Number(u.used_ratio))) .map(([label, u]) => ({ label, usedPercent: Number(u.used_ratio) * 100, resetsAt: isoOf(u.reset_time), })), })); } /** * A member's `opencode.db`, copied (with its WAL) from owner-checked reads * into a private temp dir — SQLite opens by NAME, which the member could swap * between a check and the open. The host's own is opened in place. */ function openCodeDbPath(dir, tmp) { const db = path.join(dir, "opencode.db"); if (!s3Enabled() || !ownerForRead(db)) return db; const copy = path.join(tmp(), "opencode.db"); fs.writeFileSync(copy, readFileSafe(db, null, { max: 256 << 20 })); // ponytail: whole file in memory, capped try { fs.writeFileSync(`${copy}-wal`, readFileSafe(`${db}-wal`, null, { max: 256 << 20 })); } catch { /* no WAL: everything is in the main file */ } return copy; } /** opencode's `opencode-go` key: `auth.json` (v1), else `opencode.db` (v2). */ function openCodeGoKey(dirs = toolDirs()) { const dir = dirs.opencode; const entry = readJson(path.join(dir, "auth.json"))?.["opencode-go"]; if (entry?.type === "api" && typeof entry.key === "string" && entry.key) return entry.key; let conn; let tmpDir = null; const tmp = () => (tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-opencode-"))); try { const file = openCodeDbPath(dir, tmp); // The private copy is writable so SQLite can replay its WAL. conn = new Database(file, { readonly: !tmpDir, fileMustExist: true }); const rows = conn .prepare( "SELECT value FROM credential WHERE integration_id = 'opencode-go' ORDER BY active DESC, time_created DESC LIMIT 8" ) .all(); for (const r of rows) { const v = JSON.parse(r.value); if (typeof v?.key === "string" && v.key) return v.key; } } catch { /* no OpenCode 2 database, or no credential table (OpenCode 1) */ } finally { conn?.close(); if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true }); } return null; } async function openCodeGoQuota(dirs = toolDirs()) { const key = openCodeGoKey(dirs); if (!key) return null; const who = { id: "opencode", active: true, plan: "Go" }; return bearerWindows(who, OPENCODE_GO_URL, key, {}, (body) => ({ windows: [ ["5h", body?.usage?.rolling], ["wk", body?.usage?.weekly], ["mo", body?.usage?.monthly], ] .filter(([, m]) => typeof m?.percent === "number") .map(([label, m]) => ({ label, usedPercent: m.percent, resetsAt: isoOf(m.resetsAt) })), })); } /** * Every tool's quota row. `home`: a member's Linux home (S3) — that account's * logins only, and no agy; omitted, the host's. */ async function quotas({ force = false, home = null } = {}) { const dirs = toolDirs(home); const key = (k) => (home ? `${home}|${k}` : k); const [claude, cursor, antigravity, grok, kimi, opencode] = await Promise.all([ claudeQuotas(force, home), cached(key("cursor"), force, () => cursorQuota(dirs)), home ? null : cached("antigravity", force, antigravityQuota), cached(key("grok"), force, () => grokQuota(dirs)), cached(key("kimi"), force, () => kimiQuota(dirs)), cached(key("opencode"), force, () => openCodeGoQuota(dirs)), ]); const tools = [ ...claude, codexQuota(dirs.codex), antigravity, cursor, grok, kimi, opencode, ].filter(Boolean); return { tools, checkedAt: new Date().toISOString() }; } function __resetCache() { cache.clear(); lastGood.clear(); } module.exports = { quotas, codexWindow, lastRateLimits, codexAccount, windowLabel, agyVersionOk, __resetCache, };