antigravity-usage 0.2.6 → 0.2.8

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/dist/index.js CHANGED
@@ -48,6 +48,7 @@ function success(message) {
48
48
  import { createServer } from "http";
49
49
  import { URL as URL2, URLSearchParams } from "url";
50
50
  import open from "open";
51
+ import inquirer from "inquirer";
51
52
 
52
53
  // src/accounts/types.ts
53
54
  var DEFAULT_CONFIG = {
@@ -722,6 +723,33 @@ async function resolveProjectId(accessToken) {
722
723
  return { projectId: void 0, tierId: void 0 };
723
724
  }
724
725
  }
726
+ async function completeLogin(code, redirectUri) {
727
+ const tokenResponse = await exchangeCodeForTokens(code, redirectUri);
728
+ const email = await getUserEmail(tokenResponse.access_token);
729
+ let projectId;
730
+ try {
731
+ const projectResult = await resolveProjectId(tokenResponse.access_token);
732
+ projectId = projectResult.projectId;
733
+ if (projectId) {
734
+ debug("oauth", `Project ID resolved: ${projectId}`);
735
+ } else {
736
+ debug("oauth", "No project ID obtained (will fetch on demand)");
737
+ }
738
+ } catch (err) {
739
+ debug("oauth", "Failed to resolve project ID during login (will fetch on demand)", err);
740
+ }
741
+ const tokens = {
742
+ accessToken: tokenResponse.access_token,
743
+ refreshToken: tokenResponse.refresh_token || "",
744
+ expiresAt: Date.now() + tokenResponse.expires_in * 1e3,
745
+ email,
746
+ projectId
747
+ };
748
+ if (email) {
749
+ getAccountManager().addAccount(tokens, email);
750
+ }
751
+ return { success: true, email };
752
+ }
725
753
  async function startOAuthFlow(options = {}) {
726
754
  const port = await getAvailablePort(options.port);
727
755
  const redirectUri = `http://127.0.0.1:${port}/callback`;
@@ -737,6 +765,43 @@ async function startOAuthFlow(options = {}) {
737
765
  state
738
766
  });
739
767
  const authUrl = `${OAUTH_CONFIG.authUrl}?${authParams.toString()}`;
768
+ if (options.manual) {
769
+ info("");
770
+ info("MANUAL LOGIN MODE");
771
+ info("1. Copy this URL and open it in your browser:");
772
+ info(authUrl);
773
+ info("");
774
+ info("2. Login with your Google account.");
775
+ info("3. You will be redirected to a localhost URL (which may fail to load).");
776
+ info("4. Copy that ENTIRE localhost URL and paste it below.");
777
+ info("");
778
+ const { pastedUrl } = await inquirer.prompt([
779
+ {
780
+ type: "input",
781
+ name: "pastedUrl",
782
+ message: "Paste the full redirect URL here:",
783
+ validate: (input) => input.trim().length > 0 ? true : "Please paste the URL"
784
+ }
785
+ ]);
786
+ try {
787
+ const url = new URL2(pastedUrl.trim());
788
+ const code = url.searchParams.get("code");
789
+ const returnedState = url.searchParams.get("state");
790
+ const errorParam = url.searchParams.get("error");
791
+ if (errorParam) {
792
+ return { success: false, error: errorParam };
793
+ }
794
+ if (!code || returnedState !== state) {
795
+ return { success: false, error: "Invalid URL: Missing code or state mismatch" };
796
+ }
797
+ return await completeLogin(code, redirectUri);
798
+ } catch (err) {
799
+ if (err instanceof Error) {
800
+ return { success: false, error: err.message };
801
+ }
802
+ return { success: false, error: "Invalid URL format" };
803
+ }
804
+ }
740
805
  return new Promise((resolve) => {
741
806
  let resolved = false;
742
807
  const server = createServer(async (req, res) => {
@@ -763,43 +828,20 @@ async function startOAuthFlow(options = {}) {
763
828
  return;
764
829
  }
765
830
  try {
766
- const tokenResponse = await exchangeCodeForTokens(code, redirectUri);
767
- const email = await getUserEmail(tokenResponse.access_token);
768
- let projectId;
769
- try {
770
- const projectResult = await resolveProjectId(tokenResponse.access_token);
771
- projectId = projectResult.projectId;
772
- if (projectId) {
773
- debug("oauth", `Project ID resolved: ${projectId}`);
774
- } else {
775
- debug("oauth", "No project ID obtained (will fetch on demand)");
776
- }
777
- } catch (err) {
778
- debug("oauth", "Failed to resolve project ID during login (will fetch on demand)", err);
779
- }
780
- const tokens = {
781
- accessToken: tokenResponse.access_token,
782
- refreshToken: tokenResponse.refresh_token || "",
783
- expiresAt: Date.now() + tokenResponse.expires_in * 1e3,
784
- email,
785
- projectId
786
- };
787
- if (email) {
788
- getAccountManager().addAccount(tokens, email);
789
- }
831
+ const result = await completeLogin(code, redirectUri);
790
832
  res.writeHead(200, { "Content-Type": "text/html" });
791
833
  res.end(`
792
834
  <html>
793
835
  <body style="font-family: system-ui; padding: 40px; text-align: center;">
794
836
  <h1>Login Successful!</h1>
795
- <p>You are now logged in${email ? ` as <strong>${email}</strong>` : ""}.</p>
837
+ <p>You are now logged in${result.email ? ` as <strong>${result.email}</strong>` : ""}.</p>
796
838
  <p>You can close this window and return to the terminal.</p>
797
839
  </body>
798
840
  </html>
799
841
  `);
800
842
  resolved = true;
801
843
  server.close();
802
- resolve({ success: true, email });
844
+ resolve(result);
803
845
  } catch (err) {
804
846
  res.writeHead(500, { "Content-Type": "text/html" });
805
847
  res.end("<html><body><h1>Login Failed</h1><p>Token exchange failed.</p></body></html>");
@@ -1203,7 +1245,8 @@ async function loginCommand(options) {
1203
1245
  }
1204
1246
  const result = await startOAuthFlow({
1205
1247
  noBrowser: options.noBrowser,
1206
- port: options.port
1248
+ port: options.port,
1249
+ manual: options.manual
1207
1250
  });
1208
1251
  if (result.success) {
1209
1252
  resetTokenManager();
@@ -1730,7 +1773,8 @@ function parseModelInfo(modelId, model) {
1730
1773
  remainingPercentage: quotaInfo?.remainingFraction,
1731
1774
  isExhausted: quotaInfo?.isExhausted ?? quotaInfo?.remainingFraction === 0,
1732
1775
  resetTime: quotaInfo?.resetTime,
1733
- timeUntilResetMs: parseResetTime(quotaInfo?.resetTime)
1776
+ timeUntilResetMs: parseResetTime(quotaInfo?.resetTime),
1777
+ isAutocompleteOnly: modelId.includes("gemini-2.5") || (model.displayName || "").includes("Gemini 2.5")
1734
1778
  };
1735
1779
  }
1736
1780
  function parsePromptCredits(response) {
@@ -1808,12 +1852,21 @@ async function detectOnUnix() {
1808
1852
  const { stdout } = await execAsync("ps aux");
1809
1853
  const lines = stdout.split("\n");
1810
1854
  for (const line of lines) {
1811
- if (line.toLowerCase().includes("antigravity") && (line.includes("language-server") || line.includes("lsp") || line.includes("server"))) {
1812
- debug("process-detector", `Found potential Antigravity process: ${line}`);
1813
- const processInfo = parseUnixProcessLine(line);
1814
- if (processInfo) {
1815
- return processInfo;
1816
- }
1855
+ const lower = line.toLowerCase();
1856
+ if (!lower.includes("antigravity")) {
1857
+ continue;
1858
+ }
1859
+ if (lower.includes("server installation script")) {
1860
+ continue;
1861
+ }
1862
+ const hasServerSignal = line.includes("language-server") || line.includes("lsp") || line.includes("--csrf_token") || line.includes("--extension_server_port") || line.includes("exa.language_server_pb");
1863
+ if (!hasServerSignal) {
1864
+ continue;
1865
+ }
1866
+ debug("process-detector", `Found potential Antigravity process: ${line}`);
1867
+ const processInfo = parseUnixProcessLine(line);
1868
+ if (processInfo) {
1869
+ return processInfo;
1817
1870
  }
1818
1871
  }
1819
1872
  debug("process-detector", "No Antigravity process found");
@@ -1850,24 +1903,27 @@ async function detectOnWindows() {
1850
1903
  // 10MB buffer for long command lines
1851
1904
  );
1852
1905
  const lines = stdout.split("\n").filter((line) => line.trim() && !line.includes("Node,CommandLine,ProcessId"));
1906
+ const candidates = [];
1853
1907
  for (const line of lines) {
1854
1908
  const parts = line.split(",");
1855
1909
  if (parts.length >= 3) {
1856
1910
  const commandLine = parts.slice(1, -1).join(",");
1857
1911
  const pid = parseInt(parts[parts.length - 1].trim(), 10);
1858
1912
  if (!isNaN(pid) && commandLine.toLowerCase().includes("antigravity")) {
1859
- debug("process-detector", `Found Antigravity process on Windows: PID ${pid}`);
1860
- const csrfToken = extractArgument(commandLine, "--csrf_token");
1861
- const extensionServerPort = extractArgument(commandLine, "--extension_server_port");
1862
- return {
1913
+ candidates.push({
1863
1914
  pid,
1864
- csrfToken: csrfToken || void 0,
1865
- extensionServerPort: extensionServerPort ? parseInt(extensionServerPort, 10) : void 0,
1915
+ csrfToken: extractArgument(commandLine, "--csrf_token") || void 0,
1916
+ extensionServerPort: parsePortValue(extractArgument(commandLine, "--extension_server_port")),
1866
1917
  commandLine
1867
- };
1918
+ });
1868
1919
  }
1869
1920
  }
1870
1921
  }
1922
+ const selected = selectBestWindowsCandidate(candidates);
1923
+ if (selected) {
1924
+ debug("process-detector", `Selected Antigravity process on Windows: PID ${selected.pid}`);
1925
+ return selected;
1926
+ }
1871
1927
  return await detectOnWindowsPowerShell();
1872
1928
  } catch (err) {
1873
1929
  debug("process-detector", "Error detecting process on Windows with WMIC, trying PowerShell", err);
@@ -1884,28 +1940,73 @@ async function detectOnWindowsPowerShell() {
1884
1940
  }
1885
1941
  const processes = JSON.parse(stdout);
1886
1942
  const processList = Array.isArray(processes) ? processes : [processes];
1943
+ const candidates = [];
1887
1944
  for (const proc of processList) {
1888
1945
  if (proc.Id) {
1889
1946
  const { stdout: cmdLine } = await execAsync(
1890
1947
  `powershell -Command "(Get-CimInstance Win32_Process -Filter 'ProcessId = ${proc.Id}').CommandLine"`
1891
1948
  );
1892
1949
  const commandLine = cmdLine.trim();
1893
- const csrfToken = extractArgument(commandLine, "--csrf_token");
1894
- const extensionServerPort = extractArgument(commandLine, "--extension_server_port");
1895
- return {
1950
+ if (!commandLine.toLowerCase().includes("antigravity")) {
1951
+ continue;
1952
+ }
1953
+ candidates.push({
1896
1954
  pid: proc.Id,
1897
- csrfToken: csrfToken || void 0,
1898
- extensionServerPort: extensionServerPort ? parseInt(extensionServerPort, 10) : void 0,
1955
+ csrfToken: extractArgument(commandLine, "--csrf_token") || void 0,
1956
+ extensionServerPort: parsePortValue(extractArgument(commandLine, "--extension_server_port")),
1899
1957
  commandLine
1900
- };
1958
+ });
1901
1959
  }
1902
1960
  }
1961
+ const selected = selectBestWindowsCandidate(candidates);
1962
+ if (selected) {
1963
+ debug("process-detector", `Selected Antigravity process on Windows (PowerShell): PID ${selected.pid}`);
1964
+ return selected;
1965
+ }
1903
1966
  return null;
1904
1967
  } catch (err) {
1905
1968
  debug("process-detector", "Error detecting process on Windows with PowerShell", err);
1906
1969
  return null;
1907
1970
  }
1908
1971
  }
1972
+ function parsePortValue(rawPort) {
1973
+ if (!rawPort) {
1974
+ return void 0;
1975
+ }
1976
+ const parsed = parseInt(rawPort, 10);
1977
+ return isNaN(parsed) ? void 0 : parsed;
1978
+ }
1979
+ function scoreWindowsCandidate(candidate) {
1980
+ const lower = candidate.commandLine.toLowerCase();
1981
+ let score = 0;
1982
+ if (lower.includes("antigravity")) score += 1;
1983
+ if (lower.includes("lsp")) score += 5;
1984
+ if (candidate.extensionServerPort) score += 10;
1985
+ if (candidate.csrfToken) score += 20;
1986
+ if (lower.includes("language_server") || lower.includes("language-server") || lower.includes("exa.language_server_pb")) {
1987
+ score += 50;
1988
+ }
1989
+ return score;
1990
+ }
1991
+ function selectBestWindowsCandidate(candidates) {
1992
+ if (candidates.length === 0) {
1993
+ return null;
1994
+ }
1995
+ debug("process-detector", `Found ${candidates.length} Antigravity candidate process(es) on Windows`);
1996
+ let best = null;
1997
+ let bestScore = -1;
1998
+ for (const candidate of candidates) {
1999
+ const score = scoreWindowsCandidate(candidate);
2000
+ if (score > bestScore) {
2001
+ best = candidate;
2002
+ bestScore = score;
2003
+ }
2004
+ }
2005
+ if (best) {
2006
+ debug("process-detector", `Selected PID ${best.pid} with score ${bestScore}`);
2007
+ }
2008
+ return best;
2009
+ }
1909
2010
  function extractArgument(commandLine, argName) {
1910
2011
  const eqRegex = new RegExp(`${argName}=([^\\s"']+|"[^"]*"|'[^']*')`, "i");
1911
2012
  const eqMatch = commandLine.match(eqRegex);
@@ -2032,6 +2133,8 @@ async function discoverPortsOnWindows(pid) {
2032
2133
  // src/local/port-prober.ts
2033
2134
  import https from "https";
2034
2135
  import http from "http";
2136
+ var CONNECT_RPC_PATH = "/exa.language_server_pb.LanguageServerService/GetUnleashData";
2137
+ var VALID_CONNECT_STATUSES = /* @__PURE__ */ new Set([200, 401]);
2035
2138
  async function probeForConnectAPI(ports, csrfToken, timeout = 500) {
2036
2139
  debug("port-prober", `Probing ${ports.length} ports: ${ports.join(", ")}`);
2037
2140
  const probePromises = ports.map((port) => probePort(port, csrfToken, timeout));
@@ -2051,7 +2154,7 @@ async function probePort(port, csrfToken, timeout = 500) {
2051
2154
  if (httpsResult) {
2052
2155
  return httpsResult;
2053
2156
  }
2054
- const httpResult = await probeHttp(port, timeout);
2157
+ const httpResult = await probeHttp(port, timeout, csrfToken);
2055
2158
  if (httpResult) {
2056
2159
  return httpResult;
2057
2160
  }
@@ -2062,7 +2165,7 @@ function probeHttps(port, timeout, csrfToken) {
2062
2165
  const options = {
2063
2166
  hostname: "127.0.0.1",
2064
2167
  port,
2065
- path: "/exa.language_server_pb.LanguageServerService/GetUnleashData",
2168
+ path: CONNECT_RPC_PATH,
2066
2169
  method: "POST",
2067
2170
  timeout,
2068
2171
  rejectUnauthorized: false,
@@ -2074,7 +2177,7 @@ function probeHttps(port, timeout, csrfToken) {
2074
2177
  }
2075
2178
  };
2076
2179
  const req = https.request(options, (res) => {
2077
- if (res.statusCode === 200) {
2180
+ if (res.statusCode && VALID_CONNECT_STATUSES.has(res.statusCode)) {
2078
2181
  debug("port-prober", `HTTPS Connect RPC probe on port ${port}: status ${res.statusCode} - valid connect port`);
2079
2182
  resolve({
2080
2183
  baseUrl: `https://127.0.0.1:${port}`,
@@ -2100,23 +2203,43 @@ function probeHttps(port, timeout, csrfToken) {
2100
2203
  req.end();
2101
2204
  });
2102
2205
  }
2103
- function probeHttp(port, timeout) {
2206
+ function probeHttp(port, timeout, csrfToken) {
2104
2207
  return new Promise((resolve) => {
2105
2208
  const options = {
2106
- hostname: "localhost",
2209
+ hostname: "127.0.0.1",
2107
2210
  port,
2108
- path: "/",
2109
- method: "GET",
2110
- timeout
2211
+ path: CONNECT_RPC_PATH,
2212
+ method: "POST",
2213
+ timeout,
2214
+ headers: {
2215
+ "Content-Type": "application/json",
2216
+ "Connect-Protocol-Version": "1",
2217
+ ...csrfToken ? { "X-Codeium-Csrf-Token": csrfToken } : {}
2218
+ }
2111
2219
  };
2112
2220
  const req = http.request(options, (res) => {
2113
- debug("port-prober", `HTTP probe on port ${port}: status ${res.statusCode}`);
2114
- resolve({
2115
- baseUrl: `http://localhost:${port}`,
2116
- protocol: "http",
2117
- port
2221
+ let data = "";
2222
+ res.on("data", (chunk) => {
2223
+ data += chunk.toString();
2224
+ });
2225
+ res.on("end", () => {
2226
+ if (data.toLowerCase().includes("client sent an http request to an https server")) {
2227
+ debug("port-prober", `HTTP probe on port ${port}: protocol mismatch response, rejecting`);
2228
+ resolve(null);
2229
+ return;
2230
+ }
2231
+ if (res.statusCode && VALID_CONNECT_STATUSES.has(res.statusCode)) {
2232
+ debug("port-prober", `HTTP Connect RPC probe on port ${port}: status ${res.statusCode} - valid connect port`);
2233
+ resolve({
2234
+ baseUrl: `http://127.0.0.1:${port}`,
2235
+ protocol: "http",
2236
+ port
2237
+ });
2238
+ return;
2239
+ }
2240
+ debug("port-prober", `HTTP probe on port ${port}: status ${res.statusCode} - not connect port`);
2241
+ resolve(null);
2118
2242
  });
2119
- res.resume();
2120
2243
  });
2121
2244
  req.on("error", (err) => {
2122
2245
  debug("port-prober", `HTTP probe on port ${port} failed: ${err.message}`);
@@ -2127,6 +2250,7 @@ function probeHttp(port, timeout) {
2127
2250
  req.destroy();
2128
2251
  resolve(null);
2129
2252
  });
2253
+ req.write(JSON.stringify({ wrapper_data: {} }));
2130
2254
  req.end();
2131
2255
  });
2132
2256
  }
@@ -2366,7 +2490,8 @@ function parseModelQuota(model) {
2366
2490
  remainingPercentage: quota?.remainingPercentage,
2367
2491
  isExhausted: model.isExhausted ?? quota?.remainingPercentage === 0,
2368
2492
  resetTime: quota?.resetTime,
2369
- timeUntilResetMs: quota?.timeUntilResetMs
2493
+ timeUntilResetMs: quota?.timeUntilResetMs,
2494
+ isAutocompleteOnly: model.modelId.includes("gemini-2.5") || (model.label || "").includes("Gemini 2.5") || (model.displayName || "").includes("Gemini 2.5")
2370
2495
  };
2371
2496
  }
2372
2497
 
@@ -2423,7 +2548,11 @@ async function fetchQuotaLocal() {
2423
2548
  throw new AntigravityNotRunningError();
2424
2549
  }
2425
2550
  debug("service", `Found Antigravity process: PID ${processInfo.pid}`);
2426
- const ports = await discoverPorts(processInfo.pid);
2551
+ let ports = await discoverPorts(processInfo.pid);
2552
+ if (ports.length === 0 && processInfo.extensionServerPort) {
2553
+ debug("service", `Falling back to extension_server_port: ${processInfo.extensionServerPort}`);
2554
+ ports = [processInfo.extensionServerPort];
2555
+ }
2427
2556
  if (ports.length === 0) {
2428
2557
  throw new PortDetectionError();
2429
2558
  }
@@ -2465,7 +2594,7 @@ function formatRemaining(model) {
2465
2594
  if (pct >= 25) return `\u{1F7E0} ${pct}%`;
2466
2595
  return `\u{1F534} ${pct}%`;
2467
2596
  }
2468
- function printQuotaTable(snapshot) {
2597
+ function printQuotaTable(snapshot, options = {}) {
2469
2598
  const timestamp = new Date(snapshot.timestamp).toLocaleString();
2470
2599
  console.log();
2471
2600
  console.log(`\u{1F4CA} Antigravity Quota Status (via ${snapshot.method.toUpperCase()})`);
@@ -2480,8 +2609,8 @@ function printQuotaTable(snapshot) {
2480
2609
  }
2481
2610
  console.log(` ${userParts.join(" | ")}`);
2482
2611
  }
2483
- console.log();
2484
- if (snapshot.models.length > 0) {
2612
+ const visibleModels = options.allModels ? snapshot.models : snapshot.models.filter((m) => !m.isAutocompleteOnly);
2613
+ if (visibleModels.length > 0) {
2485
2614
  const table = new Table2({
2486
2615
  head: ["Model", "Remaining", "Resets In"],
2487
2616
  style: {
@@ -2489,7 +2618,7 @@ function printQuotaTable(snapshot) {
2489
2618
  border: ["gray"]
2490
2619
  }
2491
2620
  });
2492
- for (const model of snapshot.models) {
2621
+ for (const model of visibleModels) {
2493
2622
  table.push([
2494
2623
  model.label,
2495
2624
  formatRemaining(model),
@@ -2499,6 +2628,9 @@ function printQuotaTable(snapshot) {
2499
2628
  console.log(table.toString());
2500
2629
  } else {
2501
2630
  console.log("No model quota information available.");
2631
+ if (!options.allModels && snapshot.models.some((m) => m.isAutocompleteOnly)) {
2632
+ console.log("Tip: Use --all-models to see autocomplete models.");
2633
+ }
2502
2634
  }
2503
2635
  console.log();
2504
2636
  }
@@ -2575,13 +2707,16 @@ function renderAccountsTable(accounts) {
2575
2707
  }
2576
2708
  function formatQuotaRemainingBar(remainingPercentage) {
2577
2709
  const width = 10;
2578
- const filled = Math.round(remainingPercentage / 100 * width);
2579
- const empty = width - filled;
2580
2710
  const filledChar = "\u2588";
2581
2711
  const emptyChar = "\u2591";
2712
+ if (remainingPercentage === void 0) {
2713
+ return `${emptyChar.repeat(width)} N/A`;
2714
+ }
2715
+ const filled = Math.round(remainingPercentage / 100 * width);
2716
+ const empty = width - filled;
2582
2717
  return `${filledChar.repeat(filled)}${emptyChar.repeat(empty)} ${Math.round(remainingPercentage)}%`;
2583
2718
  }
2584
- function renderAllQuotaTable(results) {
2719
+ function renderAllQuotaTable(results, options = {}) {
2585
2720
  if (results.length === 0) {
2586
2721
  console.log("\n\u{1F4ED} No accounts found.");
2587
2722
  console.log("\n\u{1F4A1} Run `antigravity-usage login` to add an account.\n");
@@ -2592,7 +2727,8 @@ function renderAllQuotaTable(results) {
2592
2727
  if (a.status !== "error" && b.status === "error") return -1;
2593
2728
  if (a.status === "error" && b.status === "error") return 0;
2594
2729
  const getRemaining = (result) => {
2595
- const firstModel = result.snapshot?.models?.[0];
2730
+ const models = options.allModels ? result.snapshot?.models : result.snapshot?.models?.filter((m) => !m.isAutocompleteOnly);
2731
+ const firstModel = models?.[0];
2596
2732
  if (!firstModel) return -1;
2597
2733
  if (firstModel.isExhausted) return 0;
2598
2734
  return firstModel.remainingPercentage ?? -1;
@@ -2643,15 +2779,17 @@ function renderAllQuotaTable(results) {
2643
2779
  credits = `${pc.available} / ${pc.monthly}`;
2644
2780
  }
2645
2781
  let quotaRemaining = "-";
2646
- if (snapshot?.models && snapshot.models.length > 0) {
2647
- const minRemaining = Math.min(
2648
- ...snapshot.models.filter((m) => m.remainingPercentage !== void 0).map((m) => m.remainingPercentage)
2649
- );
2650
- if (isFinite(minRemaining)) {
2651
- const remainingPct = minRemaining * 100;
2652
- quotaRemaining = formatQuotaRemainingBar(remainingPct);
2653
- } else if (snapshot.models.some((m) => m.isExhausted)) {
2782
+ const models = snapshot?.models || [];
2783
+ const relevantModels = options.allModels ? models : models.filter((m) => !m.isAutocompleteOnly);
2784
+ if (relevantModels.length > 0) {
2785
+ const percentages = relevantModels.filter((m) => m.remainingPercentage !== void 0).map((m) => m.remainingPercentage);
2786
+ if (percentages.length > 0) {
2787
+ const minRemaining = Math.min(...percentages);
2788
+ quotaRemaining = formatQuotaRemainingBar(minRemaining * 100);
2789
+ } else if (relevantModels.some((m) => m.isExhausted)) {
2654
2790
  quotaRemaining = "\u274C EXHAUSTED";
2791
+ } else {
2792
+ quotaRemaining = formatQuotaRemainingBar(void 0);
2655
2793
  }
2656
2794
  }
2657
2795
  table.push([
@@ -2713,7 +2851,7 @@ async function fetchSingleAccountQuota(options) {
2713
2851
  if (options.json) {
2714
2852
  printQuotaJson(snapshot);
2715
2853
  } else {
2716
- printQuotaTable(snapshot);
2854
+ printQuotaTable(snapshot, { allModels: options.allModels });
2717
2855
  }
2718
2856
  } finally {
2719
2857
  if (accountSwitched && originalActiveEmail) {
@@ -2787,7 +2925,7 @@ async function fetchAllAccountsQuota(options) {
2787
2925
  if (options.json) {
2788
2926
  console.log(JSON.stringify(results, null, 2));
2789
2927
  } else {
2790
- renderAllQuotaTable(results);
2928
+ renderAllQuotaTable(results, { allModels: options.allModels });
2791
2929
  }
2792
2930
  }
2793
2931
  async function fetchQuotaForAccount(email, method) {
@@ -3137,7 +3275,7 @@ async function accountsCommand(subcommand, args, options) {
3137
3275
  }
3138
3276
 
3139
3277
  // src/commands/wakeup.ts
3140
- import inquirer from "inquirer";
3278
+ import inquirer2 from "inquirer";
3141
3279
  import Table4 from "cli-table3";
3142
3280
 
3143
3281
  // src/wakeup/types.ts
@@ -3835,7 +3973,7 @@ async function configureWakeup() {
3835
3973
  console.log(" antigravity-usage login\n");
3836
3974
  return;
3837
3975
  }
3838
- const { enabled } = await inquirer.prompt([{
3976
+ const { enabled } = await inquirer2.prompt([{
3839
3977
  type: "confirm",
3840
3978
  name: "enabled",
3841
3979
  message: "Enable auto wake-up?",
@@ -3847,7 +3985,7 @@ async function configureWakeup() {
3847
3985
  console.log("\n\u2705 Auto wake-up disabled");
3848
3986
  return;
3849
3987
  }
3850
- const { triggerMode } = await inquirer.prompt([{
3988
+ const { triggerMode } = await inquirer2.prompt([{
3851
3989
  type: "list",
3852
3990
  name: "triggerMode",
3853
3991
  message: "Trigger mode:",
@@ -3859,7 +3997,7 @@ async function configureWakeup() {
3859
3997
  }]);
3860
3998
  config.wakeOnReset = triggerMode === "reset";
3861
3999
  if (!config.wakeOnReset) {
3862
- const { scheduleMode } = await inquirer.prompt([{
4000
+ const { scheduleMode } = await inquirer2.prompt([{
3863
4001
  type: "list",
3864
4002
  name: "scheduleMode",
3865
4003
  message: "Schedule type:",
@@ -3872,7 +4010,7 @@ async function configureWakeup() {
3872
4010
  }]);
3873
4011
  config.scheduleMode = scheduleMode;
3874
4012
  if (scheduleMode === "interval") {
3875
- const { intervalHours } = await inquirer.prompt([{
4013
+ const { intervalHours } = await inquirer2.prompt([{
3876
4014
  type: "number",
3877
4015
  name: "intervalHours",
3878
4016
  message: "Trigger every N hours:",
@@ -3881,7 +4019,7 @@ async function configureWakeup() {
3881
4019
  }]);
3882
4020
  config.intervalHours = intervalHours;
3883
4021
  } else if (scheduleMode === "daily") {
3884
- const { dailyTime } = await inquirer.prompt([{
4022
+ const { dailyTime } = await inquirer2.prompt([{
3885
4023
  type: "input",
3886
4024
  name: "dailyTime",
3887
4025
  message: "Time to trigger (HH:MM):",
@@ -3890,7 +4028,7 @@ async function configureWakeup() {
3890
4028
  }]);
3891
4029
  config.dailyTimes = [dailyTime];
3892
4030
  } else if (scheduleMode === "custom") {
3893
- const { cronExpression } = await inquirer.prompt([{
4031
+ const { cronExpression } = await inquirer2.prompt([{
3894
4032
  type: "input",
3895
4033
  name: "cronExpression",
3896
4034
  message: "Cron expression (min hour day month weekday):",
@@ -3899,7 +4037,7 @@ async function configureWakeup() {
3899
4037
  config.cronExpression = cronExpression;
3900
4038
  }
3901
4039
  } else {
3902
- const { resetCooldown } = await inquirer.prompt([{
4040
+ const { resetCooldown } = await inquirer2.prompt([{
3903
4041
  type: "number",
3904
4042
  name: "resetCooldown",
3905
4043
  message: "Cooldown between triggers (minutes):",
@@ -3912,7 +4050,7 @@ async function configureWakeup() {
3912
4050
  console.log("\n \u{1F4E6} Models: claude-sonnet-4-5, gemini-3-flash, gemini-3-pro-low");
3913
4051
  console.log(" (Triggers both Claude and Gemini families)");
3914
4052
  if (accounts.length > 1) {
3915
- const { selectedAccounts } = await inquirer.prompt([{
4053
+ const { selectedAccounts } = await inquirer2.prompt([{
3916
4054
  type: "checkbox",
3917
4055
  name: "selectedAccounts",
3918
4056
  message: "Select accounts to use:",
@@ -3926,14 +4064,14 @@ async function configureWakeup() {
3926
4064
  } else {
3927
4065
  config.selectedAccounts = void 0;
3928
4066
  }
3929
- const { customPrompt } = await inquirer.prompt([{
4067
+ const { customPrompt } = await inquirer2.prompt([{
3930
4068
  type: "input",
3931
4069
  name: "customPrompt",
3932
4070
  message: 'Custom wake-up prompt (leave empty for default "hi"):',
3933
4071
  default: config.customPrompt || ""
3934
4072
  }]);
3935
4073
  config.customPrompt = customPrompt || void 0;
3936
- const { maxTokens } = await inquirer.prompt([{
4074
+ const { maxTokens } = await inquirer2.prompt([{
3937
4075
  type: "number",
3938
4076
  name: "maxTokens",
3939
4077
  message: "Max output tokens (0 = no limit):",
@@ -3947,7 +4085,7 @@ async function configureWakeup() {
3947
4085
  console.log(` Models: ${config.selectedModels.join(", ")}`);
3948
4086
  console.log(` Accounts: ${config.selectedAccounts?.join(", ") || "Active account"}`);
3949
4087
  if (!config.wakeOnReset && isCronSupported()) {
3950
- const { installNow } = await inquirer.prompt([{
4088
+ const { installNow } = await inquirer2.prompt([{
3951
4089
  type: "confirm",
3952
4090
  name: "installNow",
3953
4091
  message: "Install to system cron now?",
@@ -4059,7 +4197,7 @@ async function runTestTrigger() {
4059
4197
  }
4060
4198
  let accountEmail = accounts[0];
4061
4199
  if (accounts.length > 1) {
4062
- const { selectedAccount } = await inquirer.prompt([{
4200
+ const { selectedAccount } = await inquirer2.prompt([{
4063
4201
  type: "list",
4064
4202
  name: "selectedAccount",
4065
4203
  message: "Select account:",
@@ -4068,13 +4206,13 @@ async function runTestTrigger() {
4068
4206
  accountEmail = selectedAccount;
4069
4207
  }
4070
4208
  const config = loadWakeupConfig();
4071
- const { modelId } = await inquirer.prompt([{
4209
+ const { modelId } = await inquirer2.prompt([{
4072
4210
  type: "input",
4073
4211
  name: "modelId",
4074
4212
  message: "Model ID to test:",
4075
4213
  default: config?.selectedModels[0] || "claude-sonnet-4-5"
4076
4214
  }]);
4077
- const { prompt } = await inquirer.prompt([{
4215
+ const { prompt } = await inquirer2.prompt([{
4078
4216
  type: "input",
4079
4217
  name: "prompt",
4080
4218
  message: "Test prompt:",
@@ -4195,10 +4333,10 @@ program.name("antigravity-usage").description("CLI tool to check Antigravity mod
4195
4333
  setDebugMode(true);
4196
4334
  }
4197
4335
  });
4198
- program.command("login").description("Authenticate with Google (adds a new account)").option("--no-browser", "Do not open browser, print URL instead").option("-p, --port <port>", "Port for OAuth callback server", parseInt).action(loginCommand);
4336
+ program.command("login").description("Authenticate with Google (adds a new account)").option("--no-browser", "Do not open browser, print URL instead").option("--manual", "Manual login flow (copy-paste URL)").option("-p, --port <port>", "Port for OAuth callback server", parseInt).action(loginCommand);
4199
4337
  program.command("logout [email]").description("Remove stored credentials").option("--all", "Logout from all accounts").action((email, options) => logoutCommand(options, email));
4200
4338
  program.command("status").description("Show current authentication status").option("--all", "Show status for all accounts").option("-a, --account <email>", "Show status for specific account").action(statusCommand);
4201
- program.command("quota", { isDefault: true }).description("Fetch and display quota information").option("--json", "Output as JSON").option("-m, --method <method>", "Method to use: auto (default), local, or google", "auto").option("--all", "Show quota for all accounts").option("-a, --account <email>", "Show quota for specific account").option("--refresh", "Force refresh (ignore cache)").action(quotaCommand);
4339
+ program.command("quota", { isDefault: true }).description("Fetch and display quota information").option("--json", "Output as JSON").option("-m, --method <method>", "Method to use: auto (default), local, or google", "auto").option("--all", "Show quota for all accounts").option("-a, --account <email>", "Show quota for specific account").option("--refresh", "Force refresh (ignore cache)").option("--all-models", "Include autocomplete models (Gemini 2.5) in quota display").action(quotaCommand);
4202
4340
  var accountsCmd = program.command("accounts").description("Manage multiple accounts");
4203
4341
  accountsCmd.command("list").description("List all accounts").option("--refresh", "Show refresh tip").action((options) => accountsCommand("list", [], options));
4204
4342
  accountsCmd.command("add").description("Add a new account (triggers OAuth login)").action(() => accountsCommand("add", [], {}));