Skip to content

feat: add Antigravity usage meter to the status bar#7952

Open
andrecristodev wants to merge 7 commits into
stablyai:mainfrom
andrecristodev:feat/antigravity-usage-meter
Open

feat: add Antigravity usage meter to the status bar#7952
andrecristodev wants to merge 7 commits into
stablyai:mainfrom
andrecristodev:feat/antigravity-usage-meter

Conversation

@andrecristodev

@andrecristodev andrecristodev commented Jul 9, 2026

Copy link
Copy Markdown

Summary

Adds an Antigravity (agy) subscription usage meter to the status bar, bringing it to parity with the existing Claude, Codex, Gemini, OpenCode Go, Kimi, and MiniMax meters. Antigravity is already a first-class launchable agent (src/shared/tui-agent-config.ts) with managed hooks, but its remaining quota and reset window were the only ones missing from the bar.

The PR now covers both phases: the read-only usage meter, and a multi-account switcher.

What it does (user-visible)

  • When the agy binary is detected on PATH, a detection-gated Antigravity usage segment appears in the status bar with a progress bar and reset window, plus a toggle in the status-bar context menu and settings search. Users without agy installed see no change.
  • The usage popover mirrors the Antigravity app's own grouping — two model families, "Gemini Models" and "Claude and GPT models", each on its most-constrained limit — instead of ~20 near-identical per-model rows.
  • Multi-account (Phase 2): the popover lists every Google account the user has signed into via agy, each with its own usage, and lets them switch the active account. Switching also rewrites the agy keyring entry on Windows so agy follows the switch.

Phase 2 — multi-account switcher

  • A self-contained account store persists each account under userData/antigravity-accounts/, with credentials encrypted at rest via Electron safeStorage (only id/email kept in clear). Accounts are auto-captured from the current agy token, keyed by the Google email (resolved via the OpenID userinfo endpoint).
  • The active account drives the meter; inactive accounts are fetched for their own usage and shown in the switcher. Switching updates Orca's active pointer and best-effort writes the gemini:antigravity keyring entry (Windows) so the CLI itself switches.
  • Weekly limit note: the Antigravity app also shows a weekly window, but that value is computed locally by agy's own quota manager and is not returned by the Code Assist API (verified against agy's request log — it only calls loadCodeAssist + fetchAvailableModels — and against CodexBar, which has the same limitation). Only the 5-hour limit the API exposes is surfaced; the per-family layout is ready to accept a second window if the API ever exposes one.

How it works

Antigravity authenticates the same Google account the Gemini CLI uses and serves its quota from Google's Code Assist backend (cloudcode-pa.googleapis.com) — the same backend Orca's Gemini meter already talks to — under the ANTIGRAVITY ideType, which returns Antigravity's own per-model quota. The implementation reuses the existing Gemini OAuth and bucket-formatting plumbing.

Credential sourcing (read-only). The Google OAuth token is read from, in order:

  1. ~/.gemini/oauth_creds.json (written by a Gemini CLI login), then
  2. the OS credential store, where the agy CLI keeps its token — it does not write an on-disk file. On Windows this is the Credential Manager Generic entry gemini:antigravity; macOS Keychain / Linux libsecret use the same identifiers.

Most Antigravity users only have (2), so the keyring fallback is what makes the meter light up for a real agy login. Neither source is ever written or refreshed on disk — the agy / Gemini CLIs own their credential lifecycles.

Usage. loadCodeAssist (ANTIGRAVITY ideType) resolves the Code Assist project, then fetchAvailableModels returns per-model quotaInfo { remainingFraction, resetTime } (falling back to retrieveUserQuota). Values are mapped through the shared Gemini bucket formatters (usedPercent = (1 - remainingFraction) * 100, resetsAt = Date.parse(resetTime)).

Files of note

  • src/main/rate-limits/antigravity-fetcher.ts — the fetcher (Code Assist calls + mapping).
  • src/main/rate-limits/antigravity-keyring.ts — read-only OS credential-store reader (PowerShell CredRead on Windows via the established execFileSync('powershell', …) pattern from browser-cookie-import.ts; security / secret-tool best-effort on macOS/Linux).
  • Wiring through shared types, the rate-limit service dispatch, the status bar, tooltip, provider visibility, detection gating, store slices + a one-shot default-add migration, and settings search. The icon reuses the existing agy agent-catalog icon (no new asset).
  • appearance-status-bar-search.ts was split into provider/system entry modules to stay under the max-lines cap.

Testing

  • pnpm typecheck, pnpm test (new suites for the fetcher and keyring reader, plus extended visibility/type fixtures), and pnpm build all pass locally.
  • Verified end-to-end against a live Google AI Pro Antigravity account on Windows: the meter renders the real per-model quota and reset, sourced from the agy keyring entry with no ~/.gemini/oauth_creds.json present.

Review summary

  • Cross-platform: the PowerShell CredRead path is guarded to win32; the file path works on all platforms; macOS/Linux keyring identifiers are best-effort and documented as such. Windows was verified; macOS/Linux keyring reads should be validated by a maintainer with those setups.
  • SSH / remote / local: reads the local credential store on whichever host the main process runs, the same posture as the other CLI providers. No assumption of a local-only shell beyond the guarded keyring read.
  • Agent / integration compatibility: additive — a new provider slot. No change to the Gemini provider, the Antigravity hook service, launch/detection, or orchestration.
  • Performance: one additional provider in the existing 15-minute poll cycle. Note: the Windows keyring read currently uses a synchronous execFileSync (mirroring browser-cookie-import.ts); happy to convert it to async if preferred.
  • UI quality: reuses the existing status-bar segment, tooltip, formatWindowLabel, and agent icon — no new tokens, colors, or layout.
  • Security: read-only against the user's own local credential stores; no secret is logged, persisted, or transmitted anywhere except the Google Code Assist request that already authenticates the token. The PowerShell script is passed via -EncodedCommand with a constant target name (no interpolation of untrusted input). Tests use synthetic placeholder tokens only.

Screenshots

Antigravity meter in the status bar, populated from a live agy login sourced from the OS credential store (Windows Credential Manager entry gemini:antigravity, with no ~/.gemini/oauth_creds.json present):

  • Status bar segment: a progress bar + NN% 5h with the Antigravity icon.
  • Popover — usage grouped into Gemini Models and Claude and GPT models, each with a bar + reset.
  • Popover — Accounts section listing each signed-in Google account with its own usage; the active one is marked, the others are click-to-switch.

Security Audit

  • Usage reads are read-only. The fetcher reads the user's own local Google OAuth token from ~/.gemini/oauth_creds.json or the OS credential store (gemini:antigravity); background polling never writes, rewrites, or refreshes either store.
  • Account credentials are encrypted at rest. The multi-account store keeps each account's OAuth credentials encrypted with Electron safeStorage (OS-backed); only the account id and email are stored in clear. The store file is written 0600.
  • The only credential write is an explicit user action. Switching the active account rewrites the gemini:antigravity keyring entry (Windows only, guarded to win32) so agy follows the switch; it is never called from background polling. Both the CredRead and CredWrite PowerShell scripts are passed via -EncodedCommand with constant target names, and the token blob is sent on stdin (never on the command line) — no interpolation of external data.
  • No secret is logged or persisted in the clear. Tokens are held in memory only for a request and transmitted solely to Google's Code Assist / userinfo endpoints that already authenticate them.
  • Tests use synthetic placeholder tokens/emails only (e.g. ya29.test-access, a@gmail.com); the full diff was scanned for real access/refresh tokens, project ids, and emails prior to commit — none are present.

Notes

  • Detection-gated and added to the default status-bar items once via the _antigravityStatusBarDefaultAdded migration.
  • Follow-up (Phase 2): multi-account display and switching for Antigravity, which — because agy is single-account and keyring-backed — would require Orca to manage its own account snapshots rather than reuse the CLI-login-capture flow used for Claude/Codex.

X handle: (add yours here for a shout-out)

Add a detection-gated Antigravity (agy) subscription usage meter,
reusing the existing Gemini / Google Code Assist OAuth plumbing.

Source the Google OAuth token from ~/.gemini/oauth_creds.json, falling
back to the OS credential store where the agy CLI keeps it (Windows
Credential Manager `gemini:antigravity`; macOS/Linux keyring). Read-only:
never writes or refreshes either store. Phase 1 is single-account.

- antigravity-fetcher.ts: loadCodeAssist (ANTIGRAVITY ideType) ->
  fetchAvailableModels, falling back to retrieveUserQuota; maps
  remainingFraction/resetTime via the shared Gemini bucket formatters.
- antigravity-keyring.ts: read-only OS credential-store reader
  (PowerShell CredRead on Windows; security/secret-tool best-effort on
  macOS/Linux), normalized to the oauth_creds shape.
- Wire through types, service dispatch, status bar, tooltip, provider
  visibility, detection gating, store slices + default-add migration,
  and settings search. Icon reuses the agy agent-catalog icon.
- Split appearance-status-bar-search into provider/system entry modules
  to stay under the max-lines cap.
- Tests for the fetcher and keyring reader; extend visibility/type fixtures.

Cross-platform: PowerShell path guarded to win32; file path works
everywhere; macOS/Linux keyring identifiers are best-effort. No secrets
logged or persisted; tests use synthetic tokens only.
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d0817a15-d661-4db4-a262-2e234fc2cc50

📥 Commits

Reviewing files that changed from the base of the PR and between e5a3e51 and ec8460d.

📒 Files selected for processing (4)
  • src/main/ipc/rate-limits.ts
  • src/preload/index.ts
  • src/renderer/src/components/status-bar/StatusBar.tsx
  • src/renderer/src/web/web-preload-api.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/renderer/src/web/web-preload-api.ts
  • src/preload/index.ts
  • src/renderer/src/components/status-bar/StatusBar.tsx
  • src/main/ipc/rate-limits.ts

📝 Walkthrough

Walkthrough

This change adds Antigravity across main-process rate-limit fetching, account storage, IPC, shared types, renderer state, status-bar UI, settings search entries, and localized copy. It introduces Antigravity credential reading and writing plus quota retrieval with fallback and retry behavior, adds Antigravity account management, and updates status-bar visibility and rendering to show the new provider. Tests were added and updated for keyring parsing, account storage, fetch behavior, and visibility logic.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding an Antigravity usage meter to the status bar.
Description check ✅ Passed The description covers the required sections and provides a solid summary, testing, security, and notes, with only minor formatting gaps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/renderer/src/components/settings/appearance-status-bar-search-entries.ts (1)

200-233: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Antigravity i18n keys deviate from the SHA1 auto-key convention used by every sibling entry.

All other entries in this catalog use SHA1-hash keys (e.g. 9dc15020d7, 51b0ccd6a2) generated by the localization sync tooling, but the new antigravity entries use hand-written keys (antigravityTitle, antigravityDescription, antigravityKeyword, antigravityGoogleKeyword). This could confuse pnpm run sync:localization-catalog (e.g. failing to detect that "google" already has key 51b0ccd6a2, resulting in a duplicate string with a different key).

Consider running the sync tool to auto-key these entries like the rest of the catalog, and reuse the existing 51b0ccd6a2 ("google") key instead of introducing antigravityGoogleKeyword.

Source: Learnings


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ea1b2fed-cb1d-47a1-b56b-068b6fc200a8

📥 Commits

Reviewing files that changed from the base of the PR and between 8adfef4 and f1b7ec1.

📒 Files selected for processing (27)
  • src/main/rate-limits/antigravity-fetcher.test.ts
  • src/main/rate-limits/antigravity-fetcher.ts
  • src/main/rate-limits/antigravity-keyring.test.ts
  • src/main/rate-limits/antigravity-keyring.ts
  • src/main/rate-limits/service.ts
  • src/main/runtime/rpc/methods/client-ui.ts
  • src/renderer/src/components/settings/appearance-status-bar-search-entries.ts
  • src/renderer/src/components/settings/appearance-status-bar-search.ts
  • src/renderer/src/components/settings/appearance-status-bar-system-entries.ts
  • src/renderer/src/components/status-bar/StatusBar.tsx
  • src/renderer/src/components/status-bar/status-bar-agent-gating.ts
  • src/renderer/src/components/status-bar/status-bar-provider-visibility.test.ts
  • src/renderer/src/components/status-bar/status-bar-provider-visibility.ts
  • src/renderer/src/components/status-bar/tooltip.tsx
  • src/renderer/src/components/status-bar/usage-error-copy.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/store/slices/rate-limits.ts
  • src/renderer/src/store/slices/ui.ts
  • src/renderer/src/web/web-preload-api.ts
  • src/shared/rate-limit-types.test.ts
  • src/shared/rate-limit-types.ts
  • src/shared/status-bar-defaults.ts
  • src/shared/types.ts

- i18n: replace the hand-written localization keys with SHA1 auto-keys
  matching the catalog convention, and reuse the existing shared "google"
  keyword key (51b0ccd6a2) instead of a duplicate.
- docs: add JSDoc docstrings to the antigravity fetcher and keyring
  helpers to meet the docstring-coverage threshold.
Phase 2 of the Antigravity meter.

- Multi-account: a self-contained account store persists each Google
  account signed into via agy, with credentials encrypted at rest via
  Electron safeStorage (only id/email kept in clear). Accounts are
  auto-captured from the agy keyring/file token, keyed by email.
- Switcher: the Antigravity popover lists stored accounts with per-account
  usage and switches the active account, also rewriting the
  gemini:antigravity keyring entry on Windows so agy follows the switch.
- Usage view: mirror the Antigravity app's grouping — collapse the ~20
  per-model quotas into the two families it shows ("Gemini Models" and
  "Claude and GPT models"), each on its most-constrained limit. The
  status-bar segment now renders a progress bar + "NN% 5h" like the other
  providers instead of a bucket name.
- Wire through shared types, the rate-limit service + state push, a
  keyring writer, IPC + preload, the store slice, and the status-bar UI.
  Tests for the account store and family grouping.

The weekly window agy shows is computed locally by its own quota manager
and is not returned by the Code Assist API (confirmed via agy's request
log and against CodexBar), so only the 5-hour limit is surfaced.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/main/rate-limits/service.ts (1)

1310-1349: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Gate Antigravity account seeding behind CLI detection

refreshAntigravityAccounts() runs on every poll and still reads ~/.gemini/oauth_creds.json, calls Google userinfo, and rewrites antigravity-accounts even when Antigravity is hidden because agy isn’t on PATH. Plumb the same detection state into the service and skip this refresh unless Antigravity is actually available.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f1751ee2-d229-4b18-a117-b3e537998b02

📥 Commits

Reviewing files that changed from the base of the PR and between 2f4a497 and 4db655a.

📒 Files selected for processing (19)
  • src/main/ipc/rate-limits.ts
  • src/main/rate-limits/antigravity-account-store.test.ts
  • src/main/rate-limits/antigravity-account-store.ts
  • src/main/rate-limits/antigravity-fetcher.test.ts
  • src/main/rate-limits/antigravity-fetcher.ts
  • src/main/rate-limits/antigravity-keyring.ts
  • src/main/rate-limits/service.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/renderer/src/components/status-bar/StatusBar.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/store/slices/rate-limits.ts
  • src/renderer/src/web/web-preload-api.ts
  • src/shared/rate-limit-types.test.ts
  • src/shared/rate-limit-types.ts
✅ Files skipped from review due to trivial changes (1)
  • src/renderer/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/renderer/src/store/slices/rate-limits.ts
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json

Comment thread src/main/rate-limits/antigravity-keyring.ts Outdated
Comment thread src/renderer/src/components/status-bar/StatusBar.tsx
- keyring: build the CredWrite blob inside the guarded try so a bad
  (NaN) expiry_date resolves to `false` instead of throwing out of
  writeAntigravityKeyringCredentials (and rejecting the switch IPC).
- StatusBar: the account-switcher `run` helper now catches and logs
  failed actions so a rejected switch/refresh can't become an unhandled
  rejection. Adds keyring write tests for the NaN-expiry and non-Windows
  paths.
Signing into a new Google account in agy only surfaced in Orca after the
15-minute poll. Now opening the Antigravity popover triggers an immediate
capture of the account agy is currently signed into (agy keeps a single
account in the OS keyring, so Orca captures whichever is active), so a new
login appears as soon as the user opens the menu instead of on the next poll.

Adds rateLimits.refreshAntigravityAccounts IPC + preload surface and a
mount effect in the account switcher.
Adds a "Manage accounts" toggle to the Antigravity account switcher so
users can remove/disconnect a stored account, like the Claude/Codex
account flows. In manage mode each row becomes a remove action.

Removal rebuilds the switcher WITHOUT the auto-follow capture, so a
removed account is not immediately re-added; the account agy is currently
signed into reappears on the next capture until the user also signs out
of it in agy (surfaced in an inline hint).
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Raise docstring coverage: document ProviderSegment, the account-switcher
memo/callback/effect helpers, the added preload rateLimits methods, the
IPC registrar, and the web preload stub. Extract the switcher's inline
onSelect logic into documented handleAccountAction / toggleManageMode
callbacks instead of anonymous inline arrows. No behavior change.
@andrecristodev

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants