tokentracker-cli 0.94.0 → 0.95.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/README.de.md +9 -7
- package/README.ja.md +8 -6
- package/README.ko.md +8 -6
- package/README.md +23 -10
- package/README.zh-CN.md +8 -6
- package/dashboard/dist/assets/{AchievementBadge-DPqwk2zI.js → AchievementBadge-Pi3Gvbm8.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-CELHoMQt.js → AchievementsPage-C4WLT8Rl.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DdAC0VCb.js → AchievementsSection-ilvR2ssr.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-Dd4I1SpM.js → BadgeDetailModal-CjEeToxa.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-DL2-4mnm.js → BotAnimated-DDf9pBuK.js} +1 -1
- package/dashboard/dist/assets/{Card-B_MpSRse.js → Card-DNous84-.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-BLZZ4rG6.js → ClawdAnimated-BLMIRntb.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-B-vfdIbH.js → CommandPalette-vVekhoFO.js} +1 -1
- package/dashboard/dist/assets/CommunityStatsModal-C0Kp_J8B.js +1 -0
- package/dashboard/dist/assets/{ConfirmModal-DLnRwUXC.js → ConfirmModal-DFdpcx12.js} +1 -1
- package/dashboard/dist/assets/{Controls-FZ8wEN5w.js → Controls-RNhqybmE.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-BQjQh1IQ.js +1 -0
- package/dashboard/dist/assets/{DevicePage-D2IzPc5o.js → DevicePage-DIhh-YAk.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BaqQaUUk.js → DialogClose-ZqxYVIzl.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-DDM4aanX.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DrbKjtuS.js → DialogTitle-BiRcejlu.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-GddKDE8i.js → FadeIn-BDBQpeGe.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DUu-Z5mE.js → HeaderGithubStar-CztIwtKT.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-BmDkY35A.js → HoverTooltip-Cfso7LTC.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-CGJyqWk2.js → IpCheckPage-CNE7dLs3.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Bd91ALUj.js → LandingPage-B2a3cweM.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-BGGUwXFd.js → LeaderboardAvatar-CwmZnIji.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BMEqHwif.js → LeaderboardPage-DkTHj3fB.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-BSweJqm2.js → LeaderboardProfileModal-CefTO3Zh.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-CRrI3RFz.js → LeaderboardProfilePage-CnOXlQ3L.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-XIRDv46K.js → LimitsPage-BIZ1-6BB.js} +2 -2
- package/dashboard/dist/assets/{LocalOnlyNotice-CCD-lGWG.js → LocalOnlyNotice-CdHQiPhy.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-lDuqSv4f.js → LoginCard-yXLA9geu.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-DStt5VXN.js → LoginModal-BF2Pfkqa.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-dmVPSL72.js → LoginPage-DuOypZap.js} +1 -1
- package/dashboard/dist/assets/{PetPage-ybcZRctF.js → PetPage-BH3xDVV9.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-Dar_UjxF.js → PopoverPopup-CewF_cGz.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-Ct9tILpz.js → ProviderIcon-Dg0K0RPn.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-B4yLh5zx.js → ResetPasswordPage-CYwt85O3.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-BOE1CEzq.js → SegmentedControl-BJkXXomQ.js} +1 -1
- package/dashboard/dist/assets/{Select-D-bSh7Zz.js → Select-B3_WH0Qe.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-DMYjXXq4.js → SelectItemText-BUby_DUu.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-CaleS-6_.js → ServiceStatusPage-BDlVZk4i.js} +1 -1
- package/dashboard/dist/assets/SessionsPage-TaC9nahK.js +2 -0
- package/dashboard/dist/assets/{SettingsPage-iJH0Yyah.js → SettingsPage-CudcQh6m.js} +2 -2
- package/dashboard/dist/assets/SkillsPage-iDLQ8lpZ.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-CuXKoMPg.js → TokenGalaxy-DtYzUsXU.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-Dx5GB7F5.js → ToolbarRootContext-PC8Tg1Jz.js} +1 -1
- package/dashboard/dist/assets/TrendMonitor-r8n8YO_9.js +19 -0
- package/dashboard/dist/assets/{WidgetsPage-DUaIyGtu.js → WidgetsPage-vzfGccWb.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BzqlcAUC.js → WrappedPage-DaKuIkYE.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-BHz2N7Ih.css → __vite-browser-external-CCMdjCX2.css} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-Dnxl50k1.js +45 -0
- package/dashboard/dist/assets/{agent-logos-BdWWxza6.js → agent-logos-B7nzn3n2.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-CANSzmm-.js → arrow-up-right-CsxyvCER.js} +1 -1
- package/dashboard/dist/assets/{check-BnJpzXRZ.js → check-hYcC7MdH.js} +1 -1
- package/dashboard/dist/assets/{composite-Ccj6KQmh.js → composite-C79LNbex.js} +1 -1
- package/dashboard/dist/assets/{copy-C2Yc8Evu.js → copy-Cz4qzE62.js} +1 -1
- package/dashboard/dist/assets/{download-B52JTk6Y.js → download-CUaSNE2E.js} +1 -1
- package/dashboard/dist/assets/{external-link-DayTwZx-.js → external-link-vq7kIyKK.js} +1 -1
- package/dashboard/dist/assets/{flame-B8GEX2yT.js → flame-CB6K4eR6.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-mhkbohkb.js → format-tokens-KI-e_JQ-.js} +1 -1
- package/dashboard/dist/assets/{icons-o8bPXqRP.js → icons-3maItkrp.js} +1 -1
- package/dashboard/dist/assets/{index-MBVWUKpA.js → index-BRXoZ7Jj.js} +1 -1
- package/dashboard/dist/assets/{index-CK_CduLm.js → index-C5WrfmLB.js} +1 -1
- package/dashboard/dist/assets/{index-Bp0ZHDtz.js → index-CUbC50Zp.js} +1 -1
- package/dashboard/dist/assets/{info-D94QM_vX.js → info-r0yGihUe.js} +1 -1
- package/dashboard/dist/assets/{limits-providers-BLzMyCiy.js → limits-providers-BN_94oe9.js} +1 -1
- package/dashboard/dist/assets/{link-2-CqBmFaDI.js → link-2-J9t0VXWM.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-SaxUpDR6.js → loader-circle-DCvOsPze.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-BoXqARks.js → maximize-2-CZ9JhGjf.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-Conh_-BD.js → modifiers.esm-DJ0_WKg0.js} +1 -1
- package/dashboard/dist/assets/module-xpSFyNBK.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-D6qLR9qd.js → monitor-smartphone-nqbWjJx_.js} +1 -1
- package/dashboard/dist/assets/{plus-D51mjIc2.js → plus-Cg7wd2jK.js} +1 -1
- package/dashboard/dist/assets/{provider-display-DpHPjd_s.js → provider-display-B9L1nRY9.js} +2 -2
- package/dashboard/dist/assets/{react-D_AcI466.js → react-D2AD6Pk2.js} +1 -1
- package/dashboard/dist/assets/{search-CwLcNdvZ.js → search-uNiWba1h.js} +1 -1
- package/dashboard/dist/assets/{skills-api-Dqi7Mjgo.js → skills-api-BBYuAhiL.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-BPDlUxPb.js → sortable.esm-CzPxc949.js} +1 -1
- package/dashboard/dist/assets/{store-ikeRIRGd.js → store-D4A7EUwE.js} +1 -1
- package/dashboard/dist/assets/{terminal-BfeX3L5T.js → terminal-Crmm_yKF.js} +1 -1
- package/dashboard/dist/assets/{trash-2-5_Qlp1k7.js → trash-2-ia9VjRnU.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-D90Fy2_j.js → use-community-stats-KaGCAxaF.js} +1 -1
- package/dashboard/dist/assets/use-latest-request-guard-Dy8gSDkk.js +1 -0
- package/dashboard/dist/assets/{use-limits-display-prefs-D4BYFaa-.js → use-limits-display-prefs-JSTYlbTD.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-DJxLNdXY.js → use-ordered-list-C4JnHdE0.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-AQE0ummK.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-CAX_GqXc.js → use-session-efficiency-pref-CgyUz5Gc.js} +1 -1
- package/dashboard/dist/assets/{use-transform-CeW6Z5R0.js → use-transform-C7-jSJ9k.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-B3YYqn-v.js +1 -0
- package/dashboard/dist/assets/{useCurrency-BarGeFyK.js → useCurrency-CK_gcb3J.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-DUJ9iT9H.js → useTokenFormat-C_a-UkCA.js} +1 -1
- package/dashboard/dist/assets/{zap--m0IWoI3.js → zap-BIJNn-Ic.js} +1 -1
- package/dashboard/dist/brand-logos/lmstudio.svg +12 -0
- package/dashboard/dist/brand-logos/unsloth.svg +1 -0
- package/dashboard/dist/index.html +16 -12
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/llms.txt +4 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +2 -2
- package/src/commands/device-login.js +5 -10
- package/src/commands/init.js +28 -1
- package/src/commands/serve.js +6 -0
- package/src/commands/status.js +24 -0
- package/src/commands/sync.js +114 -18
- package/src/lib/cloud-account.js +102 -29
- package/src/lib/fs.js +30 -0
- package/src/lib/grok-context-breakdown.js +24 -33
- package/src/lib/grok-usage.js +95 -0
- package/src/lib/local-api.js +101 -13
- package/src/lib/pricing/index.js +15 -0
- package/src/lib/pricing/matcher.js +15 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1094 -72
- package/src/lib/session-analytics.js +100 -40
- package/src/lib/usage-limits.js +507 -55
- package/src/lib/zcode-limits.js +65 -8
- package/dashboard/dist/assets/CommunityStatsModal-aAico565.js +0 -1
- package/dashboard/dist/assets/DashboardPage-BByRBsbt.js +0 -1
- package/dashboard/dist/assets/DialogDescription-BUrh0Jod.js +0 -1
- package/dashboard/dist/assets/SessionsPage-DOaY6_E9.js +0 -2
- package/dashboard/dist/assets/SkillsPage-BkzJOBLk.js +0 -1
- package/dashboard/dist/assets/TrendMonitor-BuKmsznP.js +0 -19
- package/dashboard/dist/assets/__vite-browser-external-CVIGknXl.js +0 -45
- package/dashboard/dist/assets/chevron-right-DJm0mbG0.js +0 -1
- package/dashboard/dist/assets/module-D2D5r9gC.js +0 -3
- package/dashboard/dist/assets/use-reduced-motion-yE3T3cat.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-5HP8k9a3.js +0 -1
package/src/lib/usage-limits.js
CHANGED
|
@@ -55,6 +55,38 @@ const DEFAULT_PROVIDER_TIMEOUT_MS = 15_000;
|
|
|
55
55
|
const ANTIGRAVITY_LIMITS_CACHE_FILE = "usage-limits-cache.json";
|
|
56
56
|
const ANTIGRAVITY_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
57
57
|
const ANTIGRAVITY_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS = 12 * 60 * 60 * 1000;
|
|
58
|
+
// Public installed-app OAuth client used by Antigravity / agy (same id PokeTokenBar
|
|
59
|
+
// and the agy binary embed). Not confidential — installed-app clients cannot keep a
|
|
60
|
+
// secret. Used to refresh the on-disk Google token so quota can be read like
|
|
61
|
+
// Claude/Codex without the IDE process running.
|
|
62
|
+
const ANTIGRAVITY_OAUTH_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
63
|
+
const ANTIGRAVITY_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
64
|
+
const ANTIGRAVITY_LOAD_CODE_ASSIST_URL = "https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist";
|
|
65
|
+
// The daily- host is what the Antigravity app uses. The unprefixed host often
|
|
66
|
+
// returns remainingFraction=1 for Gemini buckets (looks like 100% free).
|
|
67
|
+
const ANTIGRAVITY_QUOTA_SUMMARY_URLS = Object.freeze([
|
|
68
|
+
"https://daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary",
|
|
69
|
+
"https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary",
|
|
70
|
+
]);
|
|
71
|
+
const ANTIGRAVITY_USER_AGENT = "antigravity/2.9.1";
|
|
72
|
+
const ANTIGRAVITY_TOKEN_REFRESH_SKEW_MS = 60 * 1000;
|
|
73
|
+
// Per-step ceilings for the Antigravity serial chain. `providerTimeoutMs` bounds the
|
|
74
|
+
// whole chain (see fetchAntigravityLimits); these only cap a single step, so one slow
|
|
75
|
+
// call cannot spend a budget that later steps — and the cache fallback — still need.
|
|
76
|
+
// Every step's effective timeout is min(ceiling, remaining budget − guard).
|
|
77
|
+
const ANTIGRAVITY_REMOTE_TIMEOUT_MS = 8000;
|
|
78
|
+
const ANTIGRAVITY_PROCESS_SCAN_TIMEOUT_MS = 4000;
|
|
79
|
+
const ANTIGRAVITY_LOCAL_REQUEST_TIMEOUT_MS = 8000;
|
|
80
|
+
// Reserved tail of the provider budget. Without it the chain can consume the budget
|
|
81
|
+
// right up to the outer race, leaving no time for readAntigravityLimitsCache — the
|
|
82
|
+
// user would get a red error instead of the stale-but-usable bars the cache exists for.
|
|
83
|
+
// Proportional with an absolute cap: a flat reserve larger than the whole budget would
|
|
84
|
+
// skip every step and guarantee the error it exists to prevent.
|
|
85
|
+
const ANTIGRAVITY_BUDGET_GUARD_MS = 1500;
|
|
86
|
+
const ANTIGRAVITY_BUDGET_GUARD_FRACTION = 0.15;
|
|
87
|
+
const ANTIGRAVITY_BUDGET_EXHAUSTED_MESSAGE = "Antigravity quota lookup timed out.";
|
|
88
|
+
const ANTIGRAVITY_AUTH_EXPIRED_MESSAGE = "Not logged in to Antigravity. Launch Antigravity once to authenticate.";
|
|
89
|
+
const ANTIGRAVITY_NOT_RUNNING_MESSAGE = "Antigravity IDE is not running. Launch Antigravity to see usage limits.";
|
|
58
90
|
// Claude shares its OAuth usage endpoint budget with Claude Code itself, so a transient
|
|
59
91
|
// 429 is common. Persist the last successful read so the panel can keep showing it instead
|
|
60
92
|
// of flashing a red error. Separate file from Antigravity's (whose writer rewrites the whole
|
|
@@ -2113,7 +2145,12 @@ function parseWindowsProcesses(output) {
|
|
|
2113
2145
|
.filter((entry) => Number.isFinite(entry.pid) && entry.command);
|
|
2114
2146
|
}
|
|
2115
2147
|
|
|
2116
|
-
async function detectAntigravityProcess({
|
|
2148
|
+
async function detectAntigravityProcess({
|
|
2149
|
+
commandRunner,
|
|
2150
|
+
platform = process.platform,
|
|
2151
|
+
timeoutMs = ANTIGRAVITY_PROCESS_SCAN_TIMEOUT_MS,
|
|
2152
|
+
signal,
|
|
2153
|
+
} = {}) {
|
|
2117
2154
|
let processes;
|
|
2118
2155
|
if (platform === "win32") {
|
|
2119
2156
|
const script = [
|
|
@@ -2125,12 +2162,13 @@ async function detectAntigravityProcess({ commandRunner, platform = process.plat
|
|
|
2125
2162
|
commandRunner,
|
|
2126
2163
|
"powershell.exe",
|
|
2127
2164
|
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
2128
|
-
{ timeout:
|
|
2165
|
+
{ timeout: timeoutMs, signal },
|
|
2129
2166
|
);
|
|
2130
2167
|
processes = parseWindowsProcesses(result?.stdout);
|
|
2131
2168
|
} else {
|
|
2132
2169
|
const result = await runCommand(commandRunner, "/bin/ps", ["-ax", "-o", "pid=,command="], {
|
|
2133
|
-
timeout:
|
|
2170
|
+
timeout: timeoutMs,
|
|
2171
|
+
signal,
|
|
2134
2172
|
});
|
|
2135
2173
|
processes = String(result?.stdout || "")
|
|
2136
2174
|
.split("\n")
|
|
@@ -2594,13 +2632,18 @@ function parseWindowsListeningPorts(output, pid) {
|
|
|
2594
2632
|
return Array.from(ports).sort((a, b) => a - b);
|
|
2595
2633
|
}
|
|
2596
2634
|
|
|
2597
|
-
async function listAntigravityPorts(pid, {
|
|
2635
|
+
async function listAntigravityPorts(pid, {
|
|
2636
|
+
commandRunner,
|
|
2637
|
+
platform = process.platform,
|
|
2638
|
+
timeoutMs = ANTIGRAVITY_PROCESS_SCAN_TIMEOUT_MS,
|
|
2639
|
+
signal,
|
|
2640
|
+
} = {}) {
|
|
2598
2641
|
if (platform === "win32") {
|
|
2599
2642
|
const result = await runCommand(
|
|
2600
2643
|
commandRunner,
|
|
2601
2644
|
"netstat.exe",
|
|
2602
2645
|
["-ano", "-p", "tcp"],
|
|
2603
|
-
{ timeout:
|
|
2646
|
+
{ timeout: timeoutMs, signal },
|
|
2604
2647
|
);
|
|
2605
2648
|
const ports = parseWindowsListeningPorts(result?.stdout, pid);
|
|
2606
2649
|
if (!ports.length) {
|
|
@@ -2616,7 +2659,7 @@ async function listAntigravityPorts(pid, { commandRunner, platform = process.pla
|
|
|
2616
2659
|
commandRunner,
|
|
2617
2660
|
lsof,
|
|
2618
2661
|
["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", String(pid)],
|
|
2619
|
-
{ timeout:
|
|
2662
|
+
{ timeout: timeoutMs, signal },
|
|
2620
2663
|
);
|
|
2621
2664
|
const ports = parseListeningPorts(result?.stdout);
|
|
2622
2665
|
if (!ports.length) {
|
|
@@ -2660,11 +2703,12 @@ function requestLocalJson({
|
|
|
2660
2703
|
path,
|
|
2661
2704
|
body,
|
|
2662
2705
|
csrfToken,
|
|
2663
|
-
timeoutMs =
|
|
2706
|
+
timeoutMs = ANTIGRAVITY_LOCAL_REQUEST_TIMEOUT_MS,
|
|
2664
2707
|
requestFn,
|
|
2708
|
+
signal,
|
|
2665
2709
|
}) {
|
|
2666
2710
|
if (typeof requestFn === "function") {
|
|
2667
|
-
return requestFn({ scheme, port, path, body, csrfToken, timeoutMs });
|
|
2711
|
+
return requestFn({ scheme, port, path, body, csrfToken, timeoutMs, signal });
|
|
2668
2712
|
}
|
|
2669
2713
|
|
|
2670
2714
|
const client = scheme === "https" ? https : http;
|
|
@@ -2687,6 +2731,9 @@ function requestLocalJson({
|
|
|
2687
2731
|
rejectUnauthorized: false,
|
|
2688
2732
|
timeout: timeoutMs,
|
|
2689
2733
|
headers,
|
|
2734
|
+
// Without this the provider race only stops *waiting* — the socket stays
|
|
2735
|
+
// open past the deadline. http.request aborts and emits 'error' instead.
|
|
2736
|
+
signal,
|
|
2690
2737
|
},
|
|
2691
2738
|
(res) => {
|
|
2692
2739
|
let data = "";
|
|
@@ -2838,11 +2885,17 @@ function normalizeAntigravityResponse(body, { fallbackToConfigs = false } = {})
|
|
|
2838
2885
|
}
|
|
2839
2886
|
|
|
2840
2887
|
function normalizeAntigravityQuotaSummary(body) {
|
|
2841
|
-
if (!antigravityCodeIsOk(body
|
|
2888
|
+
if (body?.code !== undefined && body?.code !== null && !antigravityCodeIsOk(body.code)) {
|
|
2842
2889
|
throw new Error(`Antigravity API error: ${body?.code}`);
|
|
2843
2890
|
}
|
|
2844
2891
|
|
|
2845
|
-
|
|
2892
|
+
// Local Connect-RPC wraps the payload in `response`; the Cloud Code HTTP
|
|
2893
|
+
// endpoint returns `groups` at the top level.
|
|
2894
|
+
const groups = Array.isArray(body?.response?.groups)
|
|
2895
|
+
? body.response.groups
|
|
2896
|
+
: Array.isArray(body?.groups)
|
|
2897
|
+
? body.groups
|
|
2898
|
+
: null;
|
|
2846
2899
|
if (!Array.isArray(groups) || !groups.length) {
|
|
2847
2900
|
throw new Error("Could not parse Antigravity quota summary: no groups.");
|
|
2848
2901
|
}
|
|
@@ -2850,7 +2903,10 @@ function normalizeAntigravityQuotaSummary(body) {
|
|
|
2850
2903
|
// Map bucketId → window, regardless of which group they live in
|
|
2851
2904
|
const makeWindow = (remainingFraction, resetTime) => {
|
|
2852
2905
|
if (typeof remainingFraction !== "number") return null;
|
|
2853
|
-
return buildWindow({
|
|
2906
|
+
return buildWindow({
|
|
2907
|
+
usedPercent: Math.round((1 - remainingFraction) * 100),
|
|
2908
|
+
resetAt: parseAntigravityDate(resetTime) || (typeof resetTime === "string" ? resetTime : null),
|
|
2909
|
+
});
|
|
2854
2910
|
};
|
|
2855
2911
|
|
|
2856
2912
|
// Collect all buckets into a flat bucketId-keyed map
|
|
@@ -2884,7 +2940,7 @@ function normalizeAntigravityQuotaSummary(body) {
|
|
|
2884
2940
|
};
|
|
2885
2941
|
}
|
|
2886
2942
|
|
|
2887
|
-
async function probeAntigravityPort(port, csrfToken, { timeoutMs, requestFn, scheme = "https" } = {}) {
|
|
2943
|
+
async function probeAntigravityPort(port, csrfToken, { timeoutMs, requestFn, scheme = "https", signal } = {}) {
|
|
2888
2944
|
try {
|
|
2889
2945
|
await requestLocalJson({
|
|
2890
2946
|
scheme,
|
|
@@ -2894,6 +2950,7 @@ async function probeAntigravityPort(port, csrfToken, { timeoutMs, requestFn, sch
|
|
|
2894
2950
|
csrfToken,
|
|
2895
2951
|
timeoutMs,
|
|
2896
2952
|
requestFn,
|
|
2953
|
+
signal,
|
|
2897
2954
|
});
|
|
2898
2955
|
return true;
|
|
2899
2956
|
} catch (_error) {
|
|
@@ -2913,7 +2970,343 @@ function hasAntigravityInstallEvidence({ home } = {}) {
|
|
|
2913
2970
|
});
|
|
2914
2971
|
}
|
|
2915
2972
|
|
|
2916
|
-
|
|
2973
|
+
function listAntigravityCredentialPaths(home) {
|
|
2974
|
+
const geminiHome = path.join(home || os.homedir(), ".gemini");
|
|
2975
|
+
return [
|
|
2976
|
+
path.join(geminiHome, "jetski-standalone-oauth-token"),
|
|
2977
|
+
path.join(geminiHome, "antigravity", "jetski-standalone-oauth-token"),
|
|
2978
|
+
path.join(geminiHome, "antigravity", "antigravity-oauth-token"),
|
|
2979
|
+
path.join(geminiHome, "antigravity-ide", "antigravity-oauth-token"),
|
|
2980
|
+
path.join(geminiHome, "antigravity-cli", "antigravity-oauth-token"),
|
|
2981
|
+
];
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
function parseAntigravityExpiryMs(value) {
|
|
2985
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
2986
|
+
return value < 1e12 ? value * 1000 : value;
|
|
2987
|
+
}
|
|
2988
|
+
if (typeof value === "string" && value) {
|
|
2989
|
+
const ms = Date.parse(value);
|
|
2990
|
+
if (Number.isFinite(ms) && ms > 0) return ms;
|
|
2991
|
+
}
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
function parseAntigravityCredentialPayload(raw) {
|
|
2996
|
+
if (typeof raw !== "string" || !raw.trim()) return null;
|
|
2997
|
+
let text = raw.trim();
|
|
2998
|
+
if (text.startsWith("go-keyring-base64:")) {
|
|
2999
|
+
try {
|
|
3000
|
+
text = Buffer.from(text.slice("go-keyring-base64:".length), "base64").toString("utf8").trim();
|
|
3001
|
+
} catch {
|
|
3002
|
+
return null;
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
let parsed;
|
|
3006
|
+
try {
|
|
3007
|
+
parsed = JSON.parse(text);
|
|
3008
|
+
} catch {
|
|
3009
|
+
return null;
|
|
3010
|
+
}
|
|
3011
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3012
|
+
|
|
3013
|
+
const tokenObj = parsed.token && typeof parsed.token === "object" && !Array.isArray(parsed.token)
|
|
3014
|
+
? parsed.token
|
|
3015
|
+
: parsed;
|
|
3016
|
+
const accessToken = typeof tokenObj.access_token === "string" && tokenObj.access_token
|
|
3017
|
+
? tokenObj.access_token
|
|
3018
|
+
: (typeof parsed.token === "string" && parsed.token ? parsed.token : null);
|
|
3019
|
+
if (!accessToken) return null;
|
|
3020
|
+
const refreshToken = typeof tokenObj.refresh_token === "string" && tokenObj.refresh_token
|
|
3021
|
+
? tokenObj.refresh_token
|
|
3022
|
+
: (typeof parsed.refresh_token === "string" && parsed.refresh_token ? parsed.refresh_token : null);
|
|
3023
|
+
return {
|
|
3024
|
+
accessToken,
|
|
3025
|
+
refreshToken,
|
|
3026
|
+
expiryMs: parseAntigravityExpiryMs(
|
|
3027
|
+
tokenObj.expiry ?? tokenObj.expiry_date ?? parsed.expiry ?? parsed.expiry_date,
|
|
3028
|
+
),
|
|
3029
|
+
raw: parsed,
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
function loadAntigravityCredentialsFromFiles({ home } = {}) {
|
|
3034
|
+
for (const credPath of listAntigravityCredentialPaths(home)) {
|
|
3035
|
+
try {
|
|
3036
|
+
const parsed = parseAntigravityCredentialPayload(fs.readFileSync(credPath, "utf8"));
|
|
3037
|
+
if (parsed) {
|
|
3038
|
+
return { ...parsed, source: "file", path: credPath };
|
|
3039
|
+
}
|
|
3040
|
+
} catch {
|
|
3041
|
+
// missing or unreadable
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
return null;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
function readAntigravityKeychainRaw({ securityRunner, timeoutMs = 2000 } = {}) {
|
|
3048
|
+
const runner = typeof securityRunner === "function" ? securityRunner : cp.spawnSync;
|
|
3049
|
+
if (runner === cp.spawnSync) {
|
|
3050
|
+
if (process.platform !== "darwin" || !fs.existsSync(MACOS_SECURITY_BIN)) return null;
|
|
3051
|
+
}
|
|
3052
|
+
try {
|
|
3053
|
+
const result = runner(
|
|
3054
|
+
MACOS_SECURITY_BIN,
|
|
3055
|
+
["find-generic-password", "-s", "gemini", "-a", "antigravity", "-w"],
|
|
3056
|
+
{
|
|
3057
|
+
encoding: "utf8",
|
|
3058
|
+
timeout: timeoutMs,
|
|
3059
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3060
|
+
},
|
|
3061
|
+
);
|
|
3062
|
+
if (!result || result.error || result.status !== 0) return null;
|
|
3063
|
+
const stdout = typeof result.stdout === "string"
|
|
3064
|
+
? result.stdout
|
|
3065
|
+
: Buffer.isBuffer(result.stdout)
|
|
3066
|
+
? result.stdout.toString("utf8")
|
|
3067
|
+
: "";
|
|
3068
|
+
const trimmed = stdout.trim();
|
|
3069
|
+
return trimmed || null;
|
|
3070
|
+
} catch {
|
|
3071
|
+
return null;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function loadAntigravityCredentials({ home, platform = process.platform, securityRunner } = {}) {
|
|
3076
|
+
const fromFile = loadAntigravityCredentialsFromFiles({ home });
|
|
3077
|
+
if (fromFile) return fromFile;
|
|
3078
|
+
if (platform !== "darwin" && typeof securityRunner !== "function") return null;
|
|
3079
|
+
const raw = readAntigravityKeychainRaw({ securityRunner });
|
|
3080
|
+
const parsed = parseAntigravityCredentialPayload(raw);
|
|
3081
|
+
if (!parsed) return null;
|
|
3082
|
+
return { ...parsed, source: "keychain", path: null };
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
function persistAntigravityCredentials(creds, next, { nowMs = Date.now() } = {}) {
|
|
3086
|
+
if (creds?.source !== "file" || !creds.path) return;
|
|
3087
|
+
const expiresIn = Number.isFinite(next?.expiresIn) && next.expiresIn > 0 ? next.expiresIn : 3600;
|
|
3088
|
+
const expiry = new Date(nowMs + expiresIn * 1000).toISOString();
|
|
3089
|
+
try {
|
|
3090
|
+
const payload = creds.raw && typeof creds.raw === "object" ? { ...creds.raw } : {};
|
|
3091
|
+
if (payload.token && typeof payload.token === "object") {
|
|
3092
|
+
payload.token = {
|
|
3093
|
+
...payload.token,
|
|
3094
|
+
access_token: next.accessToken,
|
|
3095
|
+
expiry,
|
|
3096
|
+
};
|
|
3097
|
+
if (next.refreshToken) payload.token.refresh_token = next.refreshToken;
|
|
3098
|
+
} else {
|
|
3099
|
+
payload.access_token = next.accessToken;
|
|
3100
|
+
payload.expiry = expiry;
|
|
3101
|
+
if (next.refreshToken) payload.refresh_token = next.refreshToken;
|
|
3102
|
+
}
|
|
3103
|
+
const tmpPath = `${creds.path}.${process.pid}.tmp`;
|
|
3104
|
+
fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
3105
|
+
fs.renameSync(tmpPath, creds.path);
|
|
3106
|
+
} catch (_error) {}
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
async function refreshAntigravityAccessToken(refreshToken, { fetchImpl = fetch, signal } = {}) {
|
|
3110
|
+
if (typeof refreshToken !== "string" || !refreshToken) {
|
|
3111
|
+
const err = new Error(ANTIGRAVITY_AUTH_EXPIRED_MESSAGE);
|
|
3112
|
+
err.code = "AUTH_EXPIRED";
|
|
3113
|
+
throw err;
|
|
3114
|
+
}
|
|
3115
|
+
const res = await fetchImpl(ANTIGRAVITY_OAUTH_TOKEN_URL, {
|
|
3116
|
+
method: "POST",
|
|
3117
|
+
headers: {
|
|
3118
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
3119
|
+
Accept: "application/json",
|
|
3120
|
+
},
|
|
3121
|
+
body: new URLSearchParams({
|
|
3122
|
+
client_id: ANTIGRAVITY_OAUTH_CLIENT_ID,
|
|
3123
|
+
grant_type: "refresh_token",
|
|
3124
|
+
refresh_token: refreshToken,
|
|
3125
|
+
}),
|
|
3126
|
+
signal,
|
|
3127
|
+
});
|
|
3128
|
+
if (!res.ok) {
|
|
3129
|
+
const err = new Error(ANTIGRAVITY_AUTH_EXPIRED_MESSAGE);
|
|
3130
|
+
err.code = "AUTH_EXPIRED";
|
|
3131
|
+
throw err;
|
|
3132
|
+
}
|
|
3133
|
+
const json = await res.json();
|
|
3134
|
+
if (typeof json?.access_token !== "string" || !json.access_token) {
|
|
3135
|
+
throw new Error("Could not parse Antigravity token refresh response");
|
|
3136
|
+
}
|
|
3137
|
+
return {
|
|
3138
|
+
accessToken: json.access_token,
|
|
3139
|
+
refreshToken: typeof json.refresh_token === "string" && json.refresh_token
|
|
3140
|
+
? json.refresh_token
|
|
3141
|
+
: refreshToken,
|
|
3142
|
+
expiresIn: Number(json.expires_in),
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
async function resolveAntigravityAccessToken(creds, { fetchImpl = fetch, nowMs = Date.now(), forceRefresh = false, signal } = {}) {
|
|
3147
|
+
const expired = creds.expiryMs != null && creds.expiryMs <= nowMs + ANTIGRAVITY_TOKEN_REFRESH_SKEW_MS;
|
|
3148
|
+
if ((forceRefresh || expired || !creds.accessToken) && creds.refreshToken) {
|
|
3149
|
+
const next = await refreshAntigravityAccessToken(creds.refreshToken, { fetchImpl, signal });
|
|
3150
|
+
persistAntigravityCredentials(creds, next, { nowMs });
|
|
3151
|
+
return next.accessToken;
|
|
3152
|
+
}
|
|
3153
|
+
if (!creds.accessToken) {
|
|
3154
|
+
const err = new Error(ANTIGRAVITY_AUTH_EXPIRED_MESSAGE);
|
|
3155
|
+
err.code = "AUTH_EXPIRED";
|
|
3156
|
+
throw err;
|
|
3157
|
+
}
|
|
3158
|
+
return creds.accessToken;
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
function postAntigravityCloudCode(fetchImpl, url, accessToken, body, signal) {
|
|
3162
|
+
return fetchImpl(url, {
|
|
3163
|
+
method: "POST",
|
|
3164
|
+
headers: {
|
|
3165
|
+
Authorization: `Bearer ${accessToken}`,
|
|
3166
|
+
"Content-Type": "application/json",
|
|
3167
|
+
Accept: "application/json",
|
|
3168
|
+
"User-Agent": ANTIGRAVITY_USER_AGENT,
|
|
3169
|
+
},
|
|
3170
|
+
body: JSON.stringify(body ?? {}),
|
|
3171
|
+
signal,
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
async function fetchAntigravityQuotaSummaryJson(fetchImpl, accessToken, signal) {
|
|
3176
|
+
let lastError = null;
|
|
3177
|
+
for (const url of ANTIGRAVITY_QUOTA_SUMMARY_URLS) {
|
|
3178
|
+
try {
|
|
3179
|
+
const res = await postAntigravityCloudCode(fetchImpl, url, accessToken, {}, signal);
|
|
3180
|
+
if (res.status === 401 || res.status === 403) {
|
|
3181
|
+
const err = new Error(ANTIGRAVITY_AUTH_EXPIRED_MESSAGE);
|
|
3182
|
+
err.code = "AUTH_EXPIRED";
|
|
3183
|
+
err.status = res.status;
|
|
3184
|
+
throw err;
|
|
3185
|
+
}
|
|
3186
|
+
if (!res.ok) {
|
|
3187
|
+
lastError = new Error(`Antigravity API error: HTTP ${res.status}`);
|
|
3188
|
+
continue;
|
|
3189
|
+
}
|
|
3190
|
+
return await res.json();
|
|
3191
|
+
} catch (error) {
|
|
3192
|
+
if (error?.code === "AUTH_EXPIRED") throw error;
|
|
3193
|
+
lastError = error;
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
throw lastError || new Error("Antigravity quota request failed.");
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
async function fetchAntigravityPlanLabel(fetchImpl, accessToken, signal) {
|
|
3200
|
+
try {
|
|
3201
|
+
const res = await postAntigravityCloudCode(fetchImpl, ANTIGRAVITY_LOAD_CODE_ASSIST_URL, accessToken, {
|
|
3202
|
+
metadata: { ideType: "ANTIGRAVITY" },
|
|
3203
|
+
}, signal);
|
|
3204
|
+
if (!res.ok) return null;
|
|
3205
|
+
const json = await res.json();
|
|
3206
|
+
const paid = json?.paidTier;
|
|
3207
|
+
if (typeof paid?.name === "string" && paid.name.trim()) return paid.name.trim();
|
|
3208
|
+
if (typeof paid?.id === "string" && paid.id.trim()) return paid.id.trim();
|
|
3209
|
+
return null;
|
|
3210
|
+
} catch {
|
|
3211
|
+
return null;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
async function fetchAntigravityRemoteLimits({
|
|
3216
|
+
home,
|
|
3217
|
+
platform = process.platform,
|
|
3218
|
+
securityRunner,
|
|
3219
|
+
fetchImpl = fetch,
|
|
3220
|
+
nowMs = Date.now(),
|
|
3221
|
+
signal,
|
|
3222
|
+
} = {}) {
|
|
3223
|
+
const creds = loadAntigravityCredentials({ home, platform, securityRunner });
|
|
3224
|
+
if (!creds) return null;
|
|
3225
|
+
|
|
3226
|
+
const loadWithToken = async (accessToken) => {
|
|
3227
|
+
const payload = await fetchAntigravityQuotaSummaryJson(fetchImpl, accessToken, signal);
|
|
3228
|
+
const windows = normalizeAntigravityQuotaSummary(payload);
|
|
3229
|
+
const accountPlan = windows.account_plan || await fetchAntigravityPlanLabel(fetchImpl, accessToken, signal);
|
|
3230
|
+
return {
|
|
3231
|
+
configured: true,
|
|
3232
|
+
error: null,
|
|
3233
|
+
account_email: windows.account_email || null,
|
|
3234
|
+
account_plan: accountPlan || null,
|
|
3235
|
+
primary_window: windows.primary_window,
|
|
3236
|
+
secondary_window: windows.secondary_window,
|
|
3237
|
+
tertiary_window: windows.tertiary_window,
|
|
3238
|
+
quaternary_window: windows.quaternary_window,
|
|
3239
|
+
};
|
|
3240
|
+
};
|
|
3241
|
+
|
|
3242
|
+
let accessToken = await resolveAntigravityAccessToken(creds, { fetchImpl, nowMs, signal });
|
|
3243
|
+
try {
|
|
3244
|
+
return await loadWithToken(accessToken);
|
|
3245
|
+
} catch (error) {
|
|
3246
|
+
if (error?.code !== "AUTH_EXPIRED" || !creds.refreshToken) throw error;
|
|
3247
|
+
accessToken = await resolveAntigravityAccessToken(creds, { fetchImpl, nowMs, forceRefresh: true, signal });
|
|
3248
|
+
return await loadWithToken(accessToken);
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
function antigravityUnavailableResult({ home, nowMs, platform, securityRunner, remoteError } = {}) {
|
|
3253
|
+
const cached = readAntigravityLimitsCache({ home, nowMs });
|
|
3254
|
+
if (cached) return cached;
|
|
3255
|
+
const creds = loadAntigravityCredentials({ home, platform, securityRunner });
|
|
3256
|
+
if (!hasAntigravityInstallEvidence({ home }) && !creds) {
|
|
3257
|
+
return { configured: false };
|
|
3258
|
+
}
|
|
3259
|
+
if (remoteError) {
|
|
3260
|
+
const raw = remoteError.message || "Unknown error";
|
|
3261
|
+
const message = raw === "timeout" || /timed out/i.test(raw)
|
|
3262
|
+
? "Antigravity quota request timed out."
|
|
3263
|
+
: raw;
|
|
3264
|
+
return { configured: true, error: message };
|
|
3265
|
+
}
|
|
3266
|
+
if (creds) {
|
|
3267
|
+
return { configured: true, error: ANTIGRAVITY_AUTH_EXPIRED_MESSAGE };
|
|
3268
|
+
}
|
|
3269
|
+
return { configured: true, error: ANTIGRAVITY_NOT_RUNNING_MESSAGE };
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
/**
|
|
3273
|
+
* `providerTimeoutMs` bounds the WHOLE serial chain — remote quota attempt, process
|
|
3274
|
+
* scan, port scan, port probes and local RPCs — not any single call. Mirrors
|
|
3275
|
+
* fetchArkCodingPlanLimits / the codex remaining-budget pattern: each step's timeout is
|
|
3276
|
+
* clamped to what is left of the budget, and a step whose share has run out is skipped
|
|
3277
|
+
* rather than issued, so the chain can never outrun the outer provider race and the
|
|
3278
|
+
* disk-cache fallback still resolves inside it.
|
|
3279
|
+
*
|
|
3280
|
+
* Before this was budgeted, `timeoutMs` was used as a PER-CALL timeout while the call
|
|
3281
|
+
* site passed it as a TOTAL budget: one 15s budget bought 15s remote + 4s ps + 4s lsof
|
|
3282
|
+
* + 15s per probed port + 15s per local RPC (~83s for a single port).
|
|
3283
|
+
*/
|
|
3284
|
+
async function fetchAntigravityLimits({
|
|
3285
|
+
home,
|
|
3286
|
+
commandRunner,
|
|
3287
|
+
requestFn,
|
|
3288
|
+
fetchImpl = fetch,
|
|
3289
|
+
providerTimeoutMs = DEFAULT_PROVIDER_TIMEOUT_MS,
|
|
3290
|
+
nowMs = Date.now(),
|
|
3291
|
+
platform = process.platform,
|
|
3292
|
+
securityRunner,
|
|
3293
|
+
signal,
|
|
3294
|
+
} = {}) {
|
|
3295
|
+
const startedAtMs = performance.now();
|
|
3296
|
+
// min(this step's ceiling, budget left after reserving the fallback guard).
|
|
3297
|
+
// 0 means "no time left" — the caller must skip the call, not issue it.
|
|
3298
|
+
const budgetedTimeoutMs = (stepCeilingMs) => {
|
|
3299
|
+
if (!Number.isFinite(providerTimeoutMs) || providerTimeoutMs <= 0) return stepCeilingMs;
|
|
3300
|
+
const guardMs = Math.min(
|
|
3301
|
+
ANTIGRAVITY_BUDGET_GUARD_MS,
|
|
3302
|
+
providerTimeoutMs * ANTIGRAVITY_BUDGET_GUARD_FRACTION,
|
|
3303
|
+
);
|
|
3304
|
+
const remainingMs = providerTimeoutMs - (performance.now() - startedAtMs);
|
|
3305
|
+
const guardedMs = Math.floor(remainingMs - guardMs);
|
|
3306
|
+
if (guardedMs <= 0) return 0;
|
|
3307
|
+
return Math.min(stepCeilingMs, guardedMs);
|
|
3308
|
+
};
|
|
3309
|
+
|
|
2917
3310
|
const finalize = (payload, normalizeOptions) => {
|
|
2918
3311
|
const result = {
|
|
2919
3312
|
configured: true,
|
|
@@ -2934,32 +3327,67 @@ async function fetchAntigravityLimits({ home, commandRunner, requestFn, fetchImp
|
|
|
2934
3327
|
return result;
|
|
2935
3328
|
};
|
|
2936
3329
|
|
|
3330
|
+
let remoteError = null;
|
|
3331
|
+
const remoteTimeoutMs = budgetedTimeoutMs(ANTIGRAVITY_REMOTE_TIMEOUT_MS);
|
|
3332
|
+
if (remoteTimeoutMs > 0) {
|
|
3333
|
+
try {
|
|
3334
|
+
const remote = await withProviderTimeout(
|
|
3335
|
+
fetchAntigravityRemoteLimits({ home, platform, securityRunner, fetchImpl, nowMs, signal }),
|
|
3336
|
+
"Antigravity",
|
|
3337
|
+
remoteTimeoutMs,
|
|
3338
|
+
);
|
|
3339
|
+
if (remote?.configured && !remote.error && hasAntigravityWindow(remote)) {
|
|
3340
|
+
writeAntigravityLimitsCache(remote, { home, nowMs });
|
|
3341
|
+
return remote;
|
|
3342
|
+
}
|
|
3343
|
+
} catch (error) {
|
|
3344
|
+
remoteError = error;
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
|
|
2937
3348
|
try {
|
|
2938
|
-
const
|
|
3349
|
+
const processScanTimeoutMs = budgetedTimeoutMs(ANTIGRAVITY_PROCESS_SCAN_TIMEOUT_MS);
|
|
3350
|
+
if (processScanTimeoutMs <= 0) {
|
|
3351
|
+
throw new Error(ANTIGRAVITY_BUDGET_EXHAUSTED_MESSAGE);
|
|
3352
|
+
}
|
|
3353
|
+
const processInfo = await detectAntigravityProcess({
|
|
3354
|
+
commandRunner,
|
|
3355
|
+
platform,
|
|
3356
|
+
timeoutMs: processScanTimeoutMs,
|
|
3357
|
+
signal,
|
|
3358
|
+
});
|
|
2939
3359
|
if (!processInfo.configured) {
|
|
2940
|
-
|
|
2941
|
-
if (cached) return cached;
|
|
2942
|
-
// No install evidence → user likely doesn't have Antigravity at all.
|
|
2943
|
-
// Return configured:false so the card stays neutral (like other providers).
|
|
2944
|
-
if (!hasAntigravityInstallEvidence({ home })) {
|
|
2945
|
-
return { configured: false };
|
|
2946
|
-
}
|
|
2947
|
-
return { configured: true, error: "Antigravity IDE is not running. Launch Antigravity to see usage limits." };
|
|
3360
|
+
return antigravityUnavailableResult({ home, nowMs, platform, securityRunner, remoteError });
|
|
2948
3361
|
}
|
|
2949
3362
|
if (processInfo.error) {
|
|
2950
3363
|
return { configured: true, error: processInfo.error };
|
|
2951
3364
|
}
|
|
2952
|
-
const
|
|
3365
|
+
const portScanTimeoutMs = budgetedTimeoutMs(ANTIGRAVITY_PROCESS_SCAN_TIMEOUT_MS);
|
|
3366
|
+
if (portScanTimeoutMs <= 0) {
|
|
3367
|
+
throw new Error(ANTIGRAVITY_BUDGET_EXHAUSTED_MESSAGE);
|
|
3368
|
+
}
|
|
3369
|
+
const ports = await listAntigravityPorts(processInfo.pid, {
|
|
3370
|
+
commandRunner,
|
|
3371
|
+
platform,
|
|
3372
|
+
timeoutMs: portScanTimeoutMs,
|
|
3373
|
+
signal,
|
|
3374
|
+
});
|
|
2953
3375
|
let workingPort = null;
|
|
2954
3376
|
let workingScheme = "https";
|
|
3377
|
+
// Each probe draws from the same budget, so a long port list cannot multiply it:
|
|
3378
|
+
// once the guard is reached the loop stops and the cache fallback runs instead.
|
|
2955
3379
|
for (const port of ports) {
|
|
2956
|
-
|
|
3380
|
+
const probeTimeoutMs = budgetedTimeoutMs(ANTIGRAVITY_LOCAL_REQUEST_TIMEOUT_MS);
|
|
3381
|
+
if (probeTimeoutMs <= 0) break;
|
|
3382
|
+
if (await probeAntigravityPort(port, processInfo.csrfToken, { timeoutMs: probeTimeoutMs, requestFn, signal })) {
|
|
2957
3383
|
workingPort = port;
|
|
2958
3384
|
break;
|
|
2959
3385
|
}
|
|
2960
3386
|
// agy CLI serves both HTTPS and HTTP; no CSRF needed
|
|
2961
3387
|
if (!processInfo.csrfToken) {
|
|
2962
|
-
|
|
3388
|
+
const httpProbeTimeoutMs = budgetedTimeoutMs(ANTIGRAVITY_LOCAL_REQUEST_TIMEOUT_MS);
|
|
3389
|
+
if (httpProbeTimeoutMs <= 0) break;
|
|
3390
|
+
if (await probeAntigravityPort(port, null, { timeoutMs: httpProbeTimeoutMs, requestFn, scheme: "http", signal })) {
|
|
2963
3391
|
workingPort = port;
|
|
2964
3392
|
workingScheme = "http";
|
|
2965
3393
|
break;
|
|
@@ -2970,21 +3398,29 @@ async function fetchAntigravityLimits({ home, commandRunner, requestFn, fetchImp
|
|
|
2970
3398
|
throw new Error("Antigravity port detection failed: no working API port found");
|
|
2971
3399
|
}
|
|
2972
3400
|
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3401
|
+
const quotaTimeoutMs = budgetedTimeoutMs(ANTIGRAVITY_LOCAL_REQUEST_TIMEOUT_MS);
|
|
3402
|
+
if (quotaTimeoutMs > 0) {
|
|
3403
|
+
try {
|
|
3404
|
+
const quotaSummary = await requestLocalJson({
|
|
3405
|
+
scheme: workingScheme,
|
|
3406
|
+
port: workingPort,
|
|
3407
|
+
path: "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
|
|
3408
|
+
body: antigravityDefaultBody(),
|
|
3409
|
+
csrfToken: processInfo.csrfToken,
|
|
3410
|
+
timeoutMs: quotaTimeoutMs,
|
|
3411
|
+
requestFn,
|
|
3412
|
+
signal,
|
|
3413
|
+
});
|
|
3414
|
+
return finalizeQuotaSummary(quotaSummary);
|
|
3415
|
+
} catch (_quotaError) {
|
|
3416
|
+
// quota summary not available (IDE servers return 404) → fall back to GetUserStatus
|
|
3417
|
+
}
|
|
2986
3418
|
}
|
|
2987
3419
|
|
|
3420
|
+
const userStatusTimeoutMs = budgetedTimeoutMs(ANTIGRAVITY_LOCAL_REQUEST_TIMEOUT_MS);
|
|
3421
|
+
if (userStatusTimeoutMs <= 0) {
|
|
3422
|
+
throw new Error(ANTIGRAVITY_BUDGET_EXHAUSTED_MESSAGE);
|
|
3423
|
+
}
|
|
2988
3424
|
try {
|
|
2989
3425
|
const userStatus = await requestLocalJson({
|
|
2990
3426
|
scheme: workingScheme,
|
|
@@ -2992,11 +3428,14 @@ async function fetchAntigravityLimits({ home, commandRunner, requestFn, fetchImp
|
|
|
2992
3428
|
path: "/exa.language_server_pb.LanguageServerService/GetUserStatus",
|
|
2993
3429
|
body: antigravityDefaultBody(),
|
|
2994
3430
|
csrfToken: processInfo.csrfToken,
|
|
2995
|
-
timeoutMs,
|
|
3431
|
+
timeoutMs: userStatusTimeoutMs,
|
|
2996
3432
|
requestFn,
|
|
3433
|
+
signal,
|
|
2997
3434
|
});
|
|
2998
3435
|
return finalize(userStatus);
|
|
2999
3436
|
} catch (primaryError) {
|
|
3437
|
+
const configsTimeoutMs = budgetedTimeoutMs(ANTIGRAVITY_LOCAL_REQUEST_TIMEOUT_MS);
|
|
3438
|
+
if (configsTimeoutMs <= 0) throw primaryError;
|
|
3000
3439
|
const fallbackPort =
|
|
3001
3440
|
Number.isFinite(processInfo.extensionPort) && processInfo.extensionPort > 0
|
|
3002
3441
|
? processInfo.extensionPort
|
|
@@ -3011,26 +3450,20 @@ async function fetchAntigravityLimits({ home, commandRunner, requestFn, fetchImp
|
|
|
3011
3450
|
path: "/exa.language_server_pb.LanguageServerService/GetCommandModelConfigs",
|
|
3012
3451
|
body: antigravityDefaultBody(),
|
|
3013
3452
|
csrfToken: processInfo.csrfToken,
|
|
3014
|
-
timeoutMs,
|
|
3453
|
+
timeoutMs: configsTimeoutMs,
|
|
3015
3454
|
requestFn,
|
|
3455
|
+
signal,
|
|
3016
3456
|
});
|
|
3017
3457
|
return finalize(modelConfigs, { fallbackToConfigs: true });
|
|
3018
3458
|
}
|
|
3019
3459
|
} catch (error) {
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
}
|
|
3027
|
-
const message = error?.message === "timeout"
|
|
3028
|
-
? "Antigravity quota request timed out."
|
|
3029
|
-
: error?.message || "Unknown error";
|
|
3030
|
-
return {
|
|
3031
|
-
configured: true,
|
|
3032
|
-
error: message,
|
|
3033
|
-
};
|
|
3460
|
+
return antigravityUnavailableResult({
|
|
3461
|
+
home,
|
|
3462
|
+
nowMs,
|
|
3463
|
+
platform,
|
|
3464
|
+
securityRunner,
|
|
3465
|
+
remoteError: remoteError || error,
|
|
3466
|
+
});
|
|
3034
3467
|
}
|
|
3035
3468
|
}
|
|
3036
3469
|
|
|
@@ -3210,7 +3643,24 @@ async function fetchUsageLimitsUncached({
|
|
|
3210
3643
|
withProviderTimeout(fetchGeminiLimits({ home, env, fetchImpl: providerFetch, commandRunner }), "Gemini", providerTimeoutMs)
|
|
3211
3644
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
3212
3645
|
fetchKiroLimits({ commandRunner, now, platform, home }),
|
|
3213
|
-
|
|
3646
|
+
// Antigravity's own budget keeps the serial chain inside providerTimeoutMs; this
|
|
3647
|
+
// outer race is the enforcing backstop every other provider already has, and the
|
|
3648
|
+
// signal makes a fired race actually kill the spawned scans and open sockets.
|
|
3649
|
+
withAbortableProviderTimeout(
|
|
3650
|
+
(signal) => fetchAntigravityLimits({
|
|
3651
|
+
home,
|
|
3652
|
+
commandRunner,
|
|
3653
|
+
requestFn,
|
|
3654
|
+
fetchImpl: providerFetch,
|
|
3655
|
+
nowMs,
|
|
3656
|
+
platform,
|
|
3657
|
+
securityRunner,
|
|
3658
|
+
providerTimeoutMs,
|
|
3659
|
+
signal,
|
|
3660
|
+
}),
|
|
3661
|
+
"Antigravity",
|
|
3662
|
+
providerTimeoutMs,
|
|
3663
|
+
).catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
3214
3664
|
withProviderTimeout(fetchCopilotLimits({ home, env, fetchImpl: providerFetch, platform, securityRunner }), "GitHub Copilot", providerTimeoutMs)
|
|
3215
3665
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
3216
3666
|
withProviderTimeout(fetchGrokLimits({ home, env, fetchImpl: providerFetch }), "Grok Build", providerTimeoutMs)
|
|
@@ -3501,6 +3951,8 @@ module.exports = {
|
|
|
3501
3951
|
readKiroCreditsSummary,
|
|
3502
3952
|
fetchKiroLimits,
|
|
3503
3953
|
normalizeAntigravityResponse,
|
|
3954
|
+
normalizeAntigravityQuotaSummary,
|
|
3955
|
+
loadAntigravityCredentials,
|
|
3504
3956
|
parseListeningPorts,
|
|
3505
3957
|
parseWindowsListeningPorts,
|
|
3506
3958
|
listAntigravityPorts,
|