tokentracker-cli 0.60.3 → 0.62.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.
- package/dashboard/dist/assets/{ActivityHeatmap-cLPCmg1i.js → ActivityHeatmap-DiY-TQE6.js} +1 -1
- package/dashboard/dist/assets/{Card-_FGU06_g.js → Card-Dw2Ywh21.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-DxFUSPgP.js +19 -0
- package/dashboard/dist/assets/{DevicePage-SEcI1kHh.js → DevicePage-BZ6FyruO.js} +1 -1
- package/dashboard/dist/assets/{DialogTitle-BXqPt5Ob.js → DialogTitle-By_-gdyZ.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-By_JNfKb.js → FadeIn-DbTTDXlD.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-BOfxPmYV.js → HeaderGithubStar-Ddp9MldL.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DofM0M8X.js → IpCheckPage-DpRpe-yJ.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Cs-U-Y5I.js → LandingPage-1-X9Dyop.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-CLLQCHHj.js → LeaderboardAvatar-WLkGqwTq.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-Ctco-Kdt.js → LeaderboardPage-hWZKXtzP.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfileModal-DIu4iePI.js → LeaderboardProfileModal-DCKHaWyv.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-BQJbJ4Zk.js → LeaderboardProfilePage-BPYxDyKG.js} +3 -3
- package/dashboard/dist/assets/LimitsPage-BBDDxGWY.js +3 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-DF1lCLZT.js → LocalOnlyNotice-4EFHus0W.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-Br4lL88q.js → LoginPage-D1O2Yp2H.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-sfd3Ot7l.js → PopoverPopup-BJ1DYzia.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DPCimfFC.js → ResetPasswordPage-BYTCLfpG.js} +1 -1
- package/dashboard/dist/assets/{Select-BTE9IFaB.js → Select-BtUw6eA7.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-fTD-_D4r.js → SelectItemText-uBxS-TJY.js} +1 -1
- package/dashboard/dist/assets/SettingsPage-B8lgxPa3.js +1 -0
- package/dashboard/dist/assets/{SkillsPage-kLIOS3gm.js → SkillsPage-OalMj85c.js} +1 -1
- package/dashboard/dist/assets/WidgetsPage-1qfj_khV.js +1 -0
- package/dashboard/dist/assets/{WrappedPage-Bgjnpl0T.js → WrappedPage-DPIgHaEm.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-D1CgjfSi.js → agent-logos--zTo3ETT.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-BCwkxp9i.js → arrow-up-right-Cu9AGOq8.js} +1 -1
- package/dashboard/dist/assets/{download-DYn5lZdU.js → download-DfOwf6ht.js} +1 -1
- package/dashboard/dist/assets/{info-VYTQzNiq.js → info-B-6dkV8l.js} +1 -1
- package/dashboard/dist/assets/{main-ClN5xMKf.js → main-BxMBoQ4o.js} +39 -22
- package/dashboard/dist/assets/{main-x6DwHW58.css → main-CJDO0VC9.css} +1 -1
- package/dashboard/dist/assets/pencil-D_fU_ajz.js +1 -0
- package/dashboard/dist/assets/{use-limits-display-prefs-BZlxCSKp.js → use-limits-display-prefs-CeUpLkAH.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-Cbz2Ycln.js → use-native-settings-k36QjuqG.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-BYwcAHmE.js → use-usage-limits-DUqHPGiW.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-CgeoIGsJ.js → useCurrency-CGfsqycG.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-DqpLDNcl.js → useScrollLock-CPd8fHwH.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/lib/opencode-go-limits.js +115 -3
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +19 -0
- package/src/lib/usage-limits.js +212 -77
- package/dashboard/dist/assets/DashboardPage-sfNk7IcJ.js +0 -19
- package/dashboard/dist/assets/LimitsPage-B6cYrodR.js +0 -3
- package/dashboard/dist/assets/SettingsPage-DERfKxU0.js +0 -1
- package/dashboard/dist/assets/WidgetsPage-BIaj8VAc.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -8910,6 +8910,9 @@ async function parseAntigravityFile({
|
|
|
8910
8910
|
const resumed = canResume && (cachedTokens > 0 || cachedModel !== null);
|
|
8911
8911
|
const scanStart = resumed ? lastLine : 0;
|
|
8912
8912
|
let currentModel = resumed ? cachedModel : null;
|
|
8913
|
+
if (!currentModel) {
|
|
8914
|
+
currentModel = await readAntigravityDefaultModel(filePath);
|
|
8915
|
+
}
|
|
8913
8916
|
let contextTokens = resumed ? cachedTokens : 0;
|
|
8914
8917
|
// Snapshot of contextTokens at the last PLANNER_RESPONSE we billed for. Only
|
|
8915
8918
|
// tokens accumulated AFTER that point count as new input on the next planner
|
|
@@ -9021,6 +9024,22 @@ async function parseAntigravityFile({
|
|
|
9021
9024
|
};
|
|
9022
9025
|
}
|
|
9023
9026
|
|
|
9027
|
+
async function readAntigravityDefaultModel(filePath) {
|
|
9028
|
+
try {
|
|
9029
|
+
// filePath: …/antigravity-cli/brain/<uuid>/.system_generated/logs/transcript.jsonl
|
|
9030
|
+
// Go up 5 levels to reach the variant root (e.g. …/antigravity-cli/)
|
|
9031
|
+
let dir = filePath;
|
|
9032
|
+
for (let i = 0; i < 5; i++) dir = path.dirname(dir);
|
|
9033
|
+
const raw = await fs.readFile(path.join(dir, "settings.json"), "utf8");
|
|
9034
|
+
const settings = JSON.parse(raw);
|
|
9035
|
+
if (settings.model && typeof settings.model === "string") {
|
|
9036
|
+
return normalizeAntigravityTranscriptModel(settings.model);
|
|
9037
|
+
}
|
|
9038
|
+
} catch (_) {
|
|
9039
|
+
}
|
|
9040
|
+
return null;
|
|
9041
|
+
}
|
|
9042
|
+
|
|
9024
9043
|
function parseAntigravityModelSelection(content) {
|
|
9025
9044
|
if (typeof content !== "string" || !content) return null;
|
|
9026
9045
|
const match = content.match(
|
package/src/lib/usage-limits.js
CHANGED
|
@@ -1025,8 +1025,18 @@ function normalizeGeminiQuotaResponse({ buckets, email, tier }) {
|
|
|
1025
1025
|
|
|
1026
1026
|
async function fetchGeminiLimits({ home, env, fetchImpl = fetch, commandRunner } = {}) {
|
|
1027
1027
|
const settings = loadGeminiSettings({ home, env });
|
|
1028
|
+
const credentials = loadGeminiCredentials({ home, env });
|
|
1029
|
+
// Gemini is "configured" only if there are real OAuth credentials OR the
|
|
1030
|
+
// gemini CLI is installed. A bare ~/.gemini/settings.json is not enough:
|
|
1031
|
+
// sibling products (Antigravity) also live under ~/.gemini and would
|
|
1032
|
+
// otherwise surface a spurious "Not logged in to Gemini" card. Do NOT
|
|
1033
|
+
// require the binary when credentials exist — authenticated users on a
|
|
1034
|
+
// minimal launchd PATH have no `gemini` on PATH (issue #224).
|
|
1035
|
+
if (!credentials && !(await isBinaryAvailable("gemini", { commandRunner }))) {
|
|
1036
|
+
return { configured: false };
|
|
1037
|
+
}
|
|
1028
1038
|
const selectedType = settings?.security?.auth?.selectedType ?? null;
|
|
1029
|
-
if (!settings && !
|
|
1039
|
+
if (!settings && !credentials) {
|
|
1030
1040
|
return { configured: false };
|
|
1031
1041
|
}
|
|
1032
1042
|
if (selectedType === "api-key") {
|
|
@@ -1036,7 +1046,7 @@ async function fetchGeminiLimits({ home, env, fetchImpl = fetch, commandRunner }
|
|
|
1036
1046
|
return { configured: true, error: "Gemini Vertex AI auth not supported. Use Google account (OAuth) instead." };
|
|
1037
1047
|
}
|
|
1038
1048
|
|
|
1039
|
-
const creds =
|
|
1049
|
+
const creds = credentials;
|
|
1040
1050
|
if (!creds?.access_token) {
|
|
1041
1051
|
return { configured: true, error: "Not logged in to Gemini. Run 'gemini' in Terminal to authenticate." };
|
|
1042
1052
|
}
|
|
@@ -1612,15 +1622,38 @@ function parseProcessLine(line) {
|
|
|
1612
1622
|
};
|
|
1613
1623
|
}
|
|
1614
1624
|
|
|
1625
|
+
function firstCommandToken(command) {
|
|
1626
|
+
const trimmed = String(command || "").trimStart();
|
|
1627
|
+
const quote = trimmed[0];
|
|
1628
|
+
if (quote === '"' || quote === "'") {
|
|
1629
|
+
const end = trimmed.indexOf(quote, 1);
|
|
1630
|
+
return end >= 0 ? trimmed.slice(1, end) : trimmed.slice(1);
|
|
1631
|
+
}
|
|
1632
|
+
return trimmed.split(/\s+/, 1)[0] || "";
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1615
1635
|
function isAntigravityCommandLine(command) {
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1636
|
+
const raw = String(command || "");
|
|
1637
|
+
const lower = raw.toLowerCase();
|
|
1638
|
+
const executable = firstCommandToken(raw).split(/[\\/]/).pop() || "";
|
|
1639
|
+
|
|
1640
|
+
// The agy CLI binary itself runs as a server with Connect-RPC endpoints.
|
|
1641
|
+
// Match only the executable token's basename so absolute paths work without
|
|
1642
|
+
// treating arbitrary arguments like `vim /tmp/agy` as an Antigravity server.
|
|
1643
|
+
if (/^agy(?:\.exe)?$/i.test(executable)) return true;
|
|
1644
|
+
|
|
1645
|
+
// IDE language_server — only return true when accompanied by Antigravity-specific
|
|
1646
|
+
// markers so sibling Codeium products (Windsurf, etc.) are not misidentified.
|
|
1647
|
+
const hasLangServerBinary = /^language_server(?:_[a-z0-9]+)*(?:\.exe)?$/i.test(executable);
|
|
1648
|
+
|
|
1649
|
+
const hasAntigravityMarker =
|
|
1650
|
+
(lower.includes("--app_data_dir") && lower.includes("antigravity")) ||
|
|
1651
|
+
lower.includes("/antigravity/") ||
|
|
1652
|
+
lower.includes("/antigravity.app/") ||
|
|
1653
|
+
lower.includes("\\antigravity\\") ||
|
|
1654
|
+
/--override_ide_name(?:=|\s+)["']?antigravity\b/i.test(raw);
|
|
1655
|
+
|
|
1656
|
+
return hasLangServerBinary && hasAntigravityMarker;
|
|
1624
1657
|
}
|
|
1625
1658
|
|
|
1626
1659
|
function extractCommandFlag(command, flag) {
|
|
@@ -1641,8 +1674,7 @@ async function detectAntigravityProcess({ commandRunner } = {}) {
|
|
|
1641
1674
|
if (!parsed) continue;
|
|
1642
1675
|
if (!isAntigravityCommandLine(parsed.command)) continue;
|
|
1643
1676
|
sawProcess = true;
|
|
1644
|
-
const csrfToken = extractCommandFlag(parsed.command, "--csrf_token");
|
|
1645
|
-
if (!csrfToken) continue;
|
|
1677
|
+
const csrfToken = extractCommandFlag(parsed.command, "--csrf_token") || null;
|
|
1646
1678
|
const extensionPort = extractFirstNumber(extractCommandFlag(parsed.command, "--extension_server_port"));
|
|
1647
1679
|
return {
|
|
1648
1680
|
configured: true,
|
|
@@ -1677,7 +1709,7 @@ function isCacheWindowUsable(window, { cachedAtMs, nowMs } = {}) {
|
|
|
1677
1709
|
}
|
|
1678
1710
|
|
|
1679
1711
|
function hasAntigravityWindow(limits) {
|
|
1680
|
-
return Boolean(limits?.primary_window || limits?.secondary_window || limits?.tertiary_window);
|
|
1712
|
+
return Boolean(limits?.primary_window || limits?.secondary_window || limits?.tertiary_window || limits?.quaternary_window);
|
|
1681
1713
|
}
|
|
1682
1714
|
|
|
1683
1715
|
function normalizeAntigravityCachedLimits(raw, { nowMs = Date.now() } = {}) {
|
|
@@ -1694,6 +1726,7 @@ function normalizeAntigravityCachedLimits(raw, { nowMs = Date.now() } = {}) {
|
|
|
1694
1726
|
primary_window: isCacheWindowUsable(raw?.primary_window, { cachedAtMs, nowMs }) ? raw.primary_window : null,
|
|
1695
1727
|
secondary_window: isCacheWindowUsable(raw?.secondary_window, { cachedAtMs, nowMs }) ? raw.secondary_window : null,
|
|
1696
1728
|
tertiary_window: isCacheWindowUsable(raw?.tertiary_window, { cachedAtMs, nowMs }) ? raw.tertiary_window : null,
|
|
1729
|
+
quaternary_window: isCacheWindowUsable(raw?.quaternary_window, { cachedAtMs, nowMs }) ? raw.quaternary_window : null,
|
|
1697
1730
|
cached: true,
|
|
1698
1731
|
cached_at: raw.cached_at,
|
|
1699
1732
|
};
|
|
@@ -1720,6 +1753,7 @@ function writeAntigravityLimitsCache(limits, { home, nowMs = Date.now() } = {})
|
|
|
1720
1753
|
primary_window: limits.primary_window || null,
|
|
1721
1754
|
secondary_window: limits.secondary_window || null,
|
|
1722
1755
|
tertiary_window: limits.tertiary_window || null,
|
|
1756
|
+
quaternary_window: limits.quaternary_window || null,
|
|
1723
1757
|
cached_at: new Date(nowMs).toISOString(),
|
|
1724
1758
|
},
|
|
1725
1759
|
};
|
|
@@ -1935,6 +1969,14 @@ function requestLocalJson({
|
|
|
1935
1969
|
const client = scheme === "https" ? https : http;
|
|
1936
1970
|
return new Promise((resolve, reject) => {
|
|
1937
1971
|
const rawBody = JSON.stringify(body);
|
|
1972
|
+
const headers = {
|
|
1973
|
+
"Content-Type": "application/json",
|
|
1974
|
+
"Content-Length": Buffer.byteLength(rawBody),
|
|
1975
|
+
"Connect-Protocol-Version": "1",
|
|
1976
|
+
};
|
|
1977
|
+
if (csrfToken) {
|
|
1978
|
+
headers["X-Codeium-Csrf-Token"] = csrfToken;
|
|
1979
|
+
}
|
|
1938
1980
|
const req = client.request(
|
|
1939
1981
|
{
|
|
1940
1982
|
hostname: "127.0.0.1",
|
|
@@ -1943,12 +1985,7 @@ function requestLocalJson({
|
|
|
1943
1985
|
method: "POST",
|
|
1944
1986
|
rejectUnauthorized: false,
|
|
1945
1987
|
timeout: timeoutMs,
|
|
1946
|
-
headers
|
|
1947
|
-
"Content-Type": "application/json",
|
|
1948
|
-
"Content-Length": Buffer.byteLength(rawBody),
|
|
1949
|
-
"Connect-Protocol-Version": "1",
|
|
1950
|
-
"X-Codeium-Csrf-Token": csrfToken,
|
|
1951
|
-
},
|
|
1988
|
+
headers,
|
|
1952
1989
|
},
|
|
1953
1990
|
(res) => {
|
|
1954
1991
|
let data = "";
|
|
@@ -2039,36 +2076,6 @@ function antigravityPriority(model) {
|
|
|
2039
2076
|
return 0;
|
|
2040
2077
|
}
|
|
2041
2078
|
|
|
2042
|
-
function chooseAntigravityModel(models, family) {
|
|
2043
|
-
const candidates = models
|
|
2044
|
-
.filter((model) => antigravityFamily(model) === family)
|
|
2045
|
-
.map((model) => ({
|
|
2046
|
-
model,
|
|
2047
|
-
priority: antigravityPriority(model),
|
|
2048
|
-
remaining:
|
|
2049
|
-
typeof model.remaining_fraction === "number"
|
|
2050
|
-
? model.remaining_fraction
|
|
2051
|
-
: Number.POSITIVE_INFINITY,
|
|
2052
|
-
}))
|
|
2053
|
-
.filter((entry) => entry.priority !== null);
|
|
2054
|
-
|
|
2055
|
-
if (!candidates.length) return null;
|
|
2056
|
-
candidates.sort((a, b) => {
|
|
2057
|
-
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
2058
|
-
return a.remaining - b.remaining;
|
|
2059
|
-
});
|
|
2060
|
-
return candidates[0].model;
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
function makeAntigravityWindow(model) {
|
|
2064
|
-
if (!model) return null;
|
|
2065
|
-
const remaining = typeof model.remaining_fraction === "number" ? model.remaining_fraction * 100 : 0;
|
|
2066
|
-
return buildWindow({
|
|
2067
|
-
usedPercent: 100 - remaining,
|
|
2068
|
-
resetAt: model.reset_at,
|
|
2069
|
-
});
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
2079
|
function normalizeAntigravityResponse(body, { fallbackToConfigs = false } = {}) {
|
|
2073
2080
|
if (!antigravityCodeIsOk(body?.code)) {
|
|
2074
2081
|
throw new Error(`Antigravity API error: ${body?.code}`);
|
|
@@ -2078,21 +2085,40 @@ function normalizeAntigravityResponse(body, { fallbackToConfigs = false } = {})
|
|
|
2078
2085
|
const configs = fallbackToConfigs
|
|
2079
2086
|
? body?.clientModelConfigs
|
|
2080
2087
|
: userStatus?.cascadeModelConfigData?.clientModelConfigs;
|
|
2081
|
-
const
|
|
2082
|
-
if (!
|
|
2088
|
+
const allModels = parseAntigravityModelConfigs(configs);
|
|
2089
|
+
if (!allModels.length) {
|
|
2083
2090
|
throw new Error("Could not parse Antigravity quota: no quota models available.");
|
|
2084
2091
|
}
|
|
2085
2092
|
|
|
2086
|
-
|
|
2087
|
-
const
|
|
2088
|
-
const
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2093
|
+
// Keep only chat models (skip autocomplete/lite/tab models)
|
|
2094
|
+
const chatModels = allModels.filter((m) => antigravityPriority(m) !== null);
|
|
2095
|
+
const models = chatModels.length ? chatModels : allModels;
|
|
2096
|
+
|
|
2097
|
+
// Group chat models and pick the most-used (lowest remaining → weekly quota)
|
|
2098
|
+
// and least-used (highest remaining → 5h rolling quota) per family.
|
|
2099
|
+
const claudeModels = models.filter((m) => antigravityFamily(m) === "claude");
|
|
2100
|
+
const geminiModels = models.filter(
|
|
2101
|
+
(m) => antigravityFamily(m) === "gemini_pro" || antigravityFamily(m) === "gemini_flash",
|
|
2102
|
+
);
|
|
2103
|
+
|
|
2104
|
+
const pickMin = (list) => {
|
|
2105
|
+
const sorted = [...list].filter((m) => typeof m.remaining_fraction === "number");
|
|
2106
|
+
if (!sorted.length) return list[0] || null;
|
|
2107
|
+
sorted.sort((a, b) => a.remaining_fraction - b.remaining_fraction);
|
|
2108
|
+
return sorted[0];
|
|
2109
|
+
};
|
|
2110
|
+
const pickMax = (list) => {
|
|
2111
|
+
const sorted = [...list].filter((m) => typeof m.remaining_fraction === "number");
|
|
2112
|
+
if (!sorted.length) return list[list.length - 1] || null;
|
|
2113
|
+
sorted.sort((a, b) => b.remaining_fraction - a.remaining_fraction);
|
|
2114
|
+
return sorted[0];
|
|
2115
|
+
};
|
|
2116
|
+
|
|
2117
|
+
const makeWindow = (model) => {
|
|
2118
|
+
if (!model) return null;
|
|
2119
|
+
const remaining = typeof model.remaining_fraction === "number" ? model.remaining_fraction * 100 : 0;
|
|
2120
|
+
return buildWindow({ usedPercent: 100 - remaining, resetAt: model.reset_at });
|
|
2121
|
+
};
|
|
2096
2122
|
|
|
2097
2123
|
return {
|
|
2098
2124
|
account_email: typeof userStatus?.email === "string" ? userStatus.email : null,
|
|
@@ -2103,16 +2129,64 @@ function normalizeAntigravityResponse(body, { fallbackToConfigs = false } = {})
|
|
|
2103
2129
|
|| userStatus?.planStatus?.planInfo?.planName
|
|
2104
2130
|
|| userStatus?.planStatus?.planInfo?.planShortName
|
|
2105
2131
|
|| null,
|
|
2106
|
-
primary_window:
|
|
2107
|
-
secondary_window:
|
|
2108
|
-
tertiary_window:
|
|
2132
|
+
primary_window: makeWindow(claudeModels.length ? pickMin(claudeModels) : pickMin(models)),
|
|
2133
|
+
secondary_window: makeWindow(claudeModels.length ? pickMax(claudeModels) : null),
|
|
2134
|
+
tertiary_window: makeWindow(geminiModels.length ? pickMin(geminiModels) : pickMin(models)),
|
|
2135
|
+
quaternary_window: makeWindow(geminiModels.length ? pickMax(geminiModels) : null),
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
function normalizeAntigravityQuotaSummary(body) {
|
|
2140
|
+
if (!antigravityCodeIsOk(body?.code)) {
|
|
2141
|
+
throw new Error(`Antigravity API error: ${body?.code}`);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
const groups = body?.response?.groups;
|
|
2145
|
+
if (!Array.isArray(groups) || !groups.length) {
|
|
2146
|
+
throw new Error("Could not parse Antigravity quota summary: no groups.");
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// Map bucketId → window, regardless of which group they live in
|
|
2150
|
+
const makeWindow = (remainingFraction, resetTime) => {
|
|
2151
|
+
if (typeof remainingFraction !== "number") return null;
|
|
2152
|
+
return buildWindow({ usedPercent: 100 - remainingFraction * 100, resetAt: resetTime || null });
|
|
2153
|
+
};
|
|
2154
|
+
|
|
2155
|
+
// Collect all buckets into a flat bucketId-keyed map
|
|
2156
|
+
const buckets = {};
|
|
2157
|
+
for (const group of groups) {
|
|
2158
|
+
if (!Array.isArray(group.buckets)) continue;
|
|
2159
|
+
for (const b of group.buckets) {
|
|
2160
|
+
buckets[b.bucketId] = b;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
const windows = {
|
|
2165
|
+
primary_window: makeWindow(buckets["3p-weekly"]?.remainingFraction, buckets["3p-weekly"]?.resetTime),
|
|
2166
|
+
secondary_window: makeWindow(buckets["3p-5h"]?.remainingFraction, buckets["3p-5h"]?.resetTime),
|
|
2167
|
+
tertiary_window: makeWindow(buckets["gemini-weekly"]?.remainingFraction, buckets["gemini-weekly"]?.resetTime),
|
|
2168
|
+
quaternary_window: makeWindow(buckets["gemini-5h"]?.remainingFraction, buckets["gemini-5h"]?.resetTime),
|
|
2169
|
+
};
|
|
2170
|
+
|
|
2171
|
+
// If the groups parsed but none of the known bucketIds matched (upstream
|
|
2172
|
+
// renamed them), treat it as a parse failure so the caller falls back to
|
|
2173
|
+
// GetUserStatus rather than rendering an empty, error-free card.
|
|
2174
|
+
if (!windows.primary_window && !windows.secondary_window
|
|
2175
|
+
&& !windows.tertiary_window && !windows.quaternary_window) {
|
|
2176
|
+
throw new Error("Could not parse Antigravity quota summary: no known buckets matched.");
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
return {
|
|
2180
|
+
account_email: null, // quota summary doesn't include email
|
|
2181
|
+
account_plan: null, // quota summary doesn't include plan info
|
|
2182
|
+
...windows,
|
|
2109
2183
|
};
|
|
2110
2184
|
}
|
|
2111
2185
|
|
|
2112
|
-
async function probeAntigravityPort(port, csrfToken, { timeoutMs, requestFn } = {}) {
|
|
2186
|
+
async function probeAntigravityPort(port, csrfToken, { timeoutMs, requestFn, scheme = "https" } = {}) {
|
|
2113
2187
|
try {
|
|
2114
2188
|
await requestLocalJson({
|
|
2115
|
-
scheme
|
|
2189
|
+
scheme,
|
|
2116
2190
|
port,
|
|
2117
2191
|
path: "/exa.language_server_pb.LanguageServerService/GetUnleashData",
|
|
2118
2192
|
body: antigravityUnleashBody(),
|
|
@@ -2126,15 +2200,19 @@ async function probeAntigravityPort(port, csrfToken, { timeoutMs, requestFn } =
|
|
|
2126
2200
|
}
|
|
2127
2201
|
}
|
|
2128
2202
|
|
|
2129
|
-
|
|
2130
|
-
const
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2203
|
+
function hasAntigravityInstallEvidence({ home } = {}) {
|
|
2204
|
+
const geminiHome = path.join(home || os.homedir(), ".gemini");
|
|
2205
|
+
return ["antigravity", "antigravity-ide", "antigravity-cli"]
|
|
2206
|
+
.some((name) => {
|
|
2207
|
+
try {
|
|
2208
|
+
return fs.statSync(path.join(geminiHome, name)).isDirectory();
|
|
2209
|
+
} catch {
|
|
2210
|
+
return false;
|
|
2211
|
+
}
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2137
2214
|
|
|
2215
|
+
async function fetchAntigravityLimits({ home, commandRunner, requestFn, fetchImpl = fetch, timeoutMs = 8000, nowMs = Date.now() } = {}) {
|
|
2138
2216
|
const finalize = (payload, normalizeOptions) => {
|
|
2139
2217
|
const result = {
|
|
2140
2218
|
configured: true,
|
|
@@ -2145,22 +2223,70 @@ async function fetchAntigravityLimits({ home, commandRunner, requestFn, timeoutM
|
|
|
2145
2223
|
return result;
|
|
2146
2224
|
};
|
|
2147
2225
|
|
|
2226
|
+
const finalizeQuotaSummary = (payload) => {
|
|
2227
|
+
const result = {
|
|
2228
|
+
configured: true,
|
|
2229
|
+
error: null,
|
|
2230
|
+
...normalizeAntigravityQuotaSummary(payload),
|
|
2231
|
+
};
|
|
2232
|
+
writeAntigravityLimitsCache(result, { home, nowMs });
|
|
2233
|
+
return result;
|
|
2234
|
+
};
|
|
2235
|
+
|
|
2148
2236
|
try {
|
|
2237
|
+
const processInfo = await detectAntigravityProcess({ commandRunner });
|
|
2238
|
+
if (!processInfo.configured) {
|
|
2239
|
+
const cached = readAntigravityLimitsCache({ home, nowMs });
|
|
2240
|
+
if (cached) return cached;
|
|
2241
|
+
// No install evidence → user likely doesn't have Antigravity at all.
|
|
2242
|
+
// Return configured:false so the card stays neutral (like other providers).
|
|
2243
|
+
if (!hasAntigravityInstallEvidence({ home })) {
|
|
2244
|
+
return { configured: false };
|
|
2245
|
+
}
|
|
2246
|
+
return { configured: true, error: "Antigravity IDE is not running. Launch Antigravity to see usage limits." };
|
|
2247
|
+
}
|
|
2248
|
+
if (processInfo.error) {
|
|
2249
|
+
return { configured: true, error: processInfo.error };
|
|
2250
|
+
}
|
|
2149
2251
|
const ports = await listAntigravityPorts(processInfo.pid, { commandRunner });
|
|
2150
2252
|
let workingPort = null;
|
|
2253
|
+
let workingScheme = "https";
|
|
2151
2254
|
for (const port of ports) {
|
|
2152
2255
|
if (await probeAntigravityPort(port, processInfo.csrfToken, { timeoutMs, requestFn })) {
|
|
2153
2256
|
workingPort = port;
|
|
2154
2257
|
break;
|
|
2155
2258
|
}
|
|
2259
|
+
// agy CLI serves both HTTPS and HTTP; no CSRF needed
|
|
2260
|
+
if (!processInfo.csrfToken) {
|
|
2261
|
+
if (await probeAntigravityPort(port, null, { timeoutMs, requestFn, scheme: "http" })) {
|
|
2262
|
+
workingPort = port;
|
|
2263
|
+
workingScheme = "http";
|
|
2264
|
+
break;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2156
2267
|
}
|
|
2157
2268
|
if (!workingPort) {
|
|
2158
2269
|
throw new Error("Antigravity port detection failed: no working API port found");
|
|
2159
2270
|
}
|
|
2160
2271
|
|
|
2272
|
+
try {
|
|
2273
|
+
const quotaSummary = await requestLocalJson({
|
|
2274
|
+
scheme: workingScheme,
|
|
2275
|
+
port: workingPort,
|
|
2276
|
+
path: "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
|
|
2277
|
+
body: antigravityDefaultBody(),
|
|
2278
|
+
csrfToken: processInfo.csrfToken,
|
|
2279
|
+
timeoutMs,
|
|
2280
|
+
requestFn,
|
|
2281
|
+
});
|
|
2282
|
+
return finalizeQuotaSummary(quotaSummary);
|
|
2283
|
+
} catch (_quotaError) {
|
|
2284
|
+
// quota summary not available (IDE servers return 404) → fall back to GetUserStatus
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2161
2287
|
try {
|
|
2162
2288
|
const userStatus = await requestLocalJson({
|
|
2163
|
-
scheme:
|
|
2289
|
+
scheme: workingScheme,
|
|
2164
2290
|
port: workingPort,
|
|
2165
2291
|
path: "/exa.language_server_pb.LanguageServerService/GetUserStatus",
|
|
2166
2292
|
body: antigravityDefaultBody(),
|
|
@@ -2174,8 +2300,12 @@ async function fetchAntigravityLimits({ home, commandRunner, requestFn, timeoutM
|
|
|
2174
2300
|
Number.isFinite(processInfo.extensionPort) && processInfo.extensionPort > 0
|
|
2175
2301
|
? processInfo.extensionPort
|
|
2176
2302
|
: workingPort;
|
|
2303
|
+
// agy has no extension server port; try HTTP on the same port
|
|
2304
|
+
const fallbackScheme = !processInfo.csrfToken && fallbackPort === workingPort
|
|
2305
|
+
? (workingScheme === "https" ? "http" : "https")
|
|
2306
|
+
: (fallbackPort === workingPort ? "https" : "http");
|
|
2177
2307
|
const modelConfigs = await requestLocalJson({
|
|
2178
|
-
scheme:
|
|
2308
|
+
scheme: fallbackScheme,
|
|
2179
2309
|
port: fallbackPort,
|
|
2180
2310
|
path: "/exa.language_server_pb.LanguageServerService/GetCommandModelConfigs",
|
|
2181
2311
|
body: antigravityDefaultBody(),
|
|
@@ -2188,6 +2318,11 @@ async function fetchAntigravityLimits({ home, commandRunner, requestFn, timeoutM
|
|
|
2188
2318
|
} catch (error) {
|
|
2189
2319
|
const cached = readAntigravityLimitsCache({ home, nowMs });
|
|
2190
2320
|
if (cached) return cached;
|
|
2321
|
+
// If there's no install evidence, this error is likely from a system that
|
|
2322
|
+
// never had Antigravity — return neutral state like other providers.
|
|
2323
|
+
if (!hasAntigravityInstallEvidence({ home })) {
|
|
2324
|
+
return { configured: false };
|
|
2325
|
+
}
|
|
2191
2326
|
const message = error?.message === "timeout"
|
|
2192
2327
|
? "Antigravity quota request timed out."
|
|
2193
2328
|
: error?.message || "Unknown error";
|
|
@@ -2335,7 +2470,7 @@ async function fetchUsageLimitsUncached({
|
|
|
2335
2470
|
withProviderTimeout(fetchGeminiLimits({ home, env, fetchImpl: providerFetch, commandRunner }), "Gemini", providerTimeoutMs)
|
|
2336
2471
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
2337
2472
|
fetchKiroLimits({ commandRunner, now }),
|
|
2338
|
-
fetchAntigravityLimits({ home, commandRunner, requestFn, nowMs }),
|
|
2473
|
+
fetchAntigravityLimits({ home, commandRunner, requestFn, fetchImpl: providerFetch, nowMs }),
|
|
2339
2474
|
withProviderTimeout(fetchCopilotLimits({ home, env, fetchImpl: providerFetch, platform, securityRunner }), "GitHub Copilot", providerTimeoutMs)
|
|
2340
2475
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
2341
2476
|
withProviderTimeout(fetchGrokLimits({ home, env, fetchImpl: providerFetch }), "Grok Build", providerTimeoutMs)
|