Skip to content

cortexkit/antigravity-auth

Repository files navigation

Antigravity Auth

Google Antigravity OAuth for coding agents. Authenticate with your Google account and access Antigravity quota for Gemini, Claude, and GPT-OSS models from OpenCode, the Pi coding agent, or the standalone CLI.

This monorepo ships three packages:

Package Host Role
@cortexkit/opencode-antigravity-auth OpenCode 1.x server Intercepts fetch(), runs the account pool + quota manager, drives slash commands, and exposes a TUI sidebar through a loopback RPC.
@cortexkit/pi-antigravity-auth Pi coding agent Registers a custom provider with OAuth login + Gemini streaming.
@cortexkit/antigravity-auth-core Any harness Harness-agnostic core: OAuth PKCE, raw HTTP/1.1 transport, device fingerprint, request transforms, account pool, quota manager, durable storage. Both host packages depend on it.

Risk and terms-of-service warning

CAUTION — read before installing.

Using this software (and any proxy for Antigravity) violates Google's Terms of Service. Google account holders have reported suspensions, bans, and shadow-bans (restricted access without notice). The tool is not endorsed by Google, and every account is operated by the account holder. You assume all legal, financial, and technical risk. Operators and contributors are not liable for outcomes.

The shipped headers, project IDs, and OAuth client identifiers match the publicly observable behavior of the upstream Antigravity CLI; they are not credentials and may be inspected in this repository. OAuth credentials (refresh tokens, access tokens, browser cookies), bearer tokens, and request/response dumps never appear in this README or the project tree — they live exclusively in antigravity-accounts.json on the operator's machine, the ~/.cache/opencode/antigravity-accounts.json fallback, or the developer-controlled debug log directory. Do not paste any of them into issues or chat.

Packages and supported hosts

Host Package version Host requirement Install path
OpenCode (server + TUI) 2.0.0 @opencode-ai/plugin >=1.17.13 <2 · @opentui/core, @opentui/keymap, @opentui/solid all ^0.4.5 opencode plugin @cortexkit/opencode-antigravity-auth@latest (writes the server entry to opencode.json and the TUI entry to tui.json)
Pi coding agent 2.0.0 @earendil-works/pi-ai, @earendil-works/pi-coding-agent, @earendil-works/pi-tui (peer) pi install npm:@cortexkit/pi-antigravity-auth then /login google-antigravity
Standalone CLI shipped inside the OpenCode package as the antigravity-auth bin Node 20+ npx @cortexkit/opencode-antigravity-auth
Node libraries (harness builders) 2.0.0 core Node 20+ npm install @cortexkit/antigravity-auth-core

The OpenCode package exposes two exports subpaths the host installer reads:

  • exports["."] — the bundled dist/index.js server root; opencode plugin writes this entry into opencode.json's plugin array.
  • exports["./tui"]src/tui/entry.mjs, the OpenTUI sidebar loader; opencode plugin writes this entry into tui.json so the TUI process picks it up on the next host start. The precompiled JSX tree ships under dist/src/tui-compiled/ and is regenerated by bun run build:tui; the host's loader also accepts the raw src/tui.tsx source path for development installs.

The host enforces the minimum OpenCode version through the package's engines.opencode field (>=1.17.13 <2); opencode plugin refuses to install a plugin that asks for an unsupported range. Both core and opencode packages are dual-target: the OpenCode package bundles a CommonJS-free ESM artifact via esbuild, while the core package ships tsc-emitted ESM. Runtime requirements match the engines.node field (>=20).

Installation matrix

OpenCode (npm — the only end-user install path)

The supported installer writes both registrations in one step:

opencode plugin @cortexkit/opencode-antigravity-auth@latest

What this does:

  • Reads the package's exports["."] (the server root) and writes it into ~/.config/opencode/opencode.json under the plugin array — that is the auth.loader and fetch interceptor the host calls on every model dispatch.
  • Reads the package's exports["./tui"] (the OpenTUI sidebar loader) and writes it into ~/.config/opencode/tui.json so the TUI process picks the sidebar up on the next host start.
  • Refuses to install when the host's OpenCode version falls outside the package's engines.opencode range (>=1.17.13 <2).

Manual config (hand-edited configs)

If you cannot use opencode plugin, write both files by hand. The server entry lives in opencode.json:

// ~/.config/opencode/opencode.json
{
  "plugin": ["@cortexkit/opencode-antigravity-auth@latest"]
}

The TUI entry lives in a separate tui.json file under the same config dir:

// ~/.config/opencode/tui.json
{
  "plugin": ["@cortexkit/opencode-antigravity-auth"]
}

The two files are independent — the host reads the server registration from opencode.json and the TUI registration from tui.json. Dropping one side disables that half of the plugin without touching the other.

OpenCode from this checkout (contributors)

Link the package into your local OpenCode config so the working-tree version is used:

bun install                                  # at repo root
bun run build                                # build core, opencode, pi in order
bun run --cwd packages/opencode smoke:tui    # smoke-test the bundled TUI

Then point OPENCODE_CONFIG_DIR at a project that has the development plugin entry, or copy dist/ and dist/src/tui-compiled/ into a directory OpenCode watches.

Pi

pi install npm:@cortexkit/pi-antigravity-auth
pi                                                # interactive
/login google-antigravity                         # OAuth

Pi's extension loader discovers the package's pi.extensions field and registers the provider automatically; no manual wiring required.

Standalone CLI

The antigravity-auth bin lives inside the OpenCode package; no separate install is needed:

npx -y @cortexkit/opencode-antigravity-auth login        # OAuth flow, no plugin
npx -y @cortexkit/opencode-antigravity-auth list         # print accounts
npx -y @cortexkit/opencode-antigravity-auth quota        # live quota snapshot
npx -y @cortexkit/opencode-antigravity-auth quota --refresh --json

The CLI writes to the same antigravity-accounts.json the OpenCode plugin reads, so a login from the standalone bin is visible to the plugin immediately on the next restart.

First login

  1. Install the package and start OpenCode (or the standalone CLI).
  2. For OpenCode: open the auth menu and choose Antigravity (Google OAuth), or run /antigravity-account add.
  3. For the CLI: run antigravity-auth login. --project <id> pins a Google Cloud project before the flow; --no-browser prints the URL instead of opening it (useful in headless / SSH sessions — see Troubleshooting).
  4. Approve the consent screen. The browser redirects to http://localhost:51121/oauth-callback; the local listener completes the PKCE exchange, persists refreshToken / accessToken / email / optional projectId into ~/.config/opencode/antigravity-accounts.json (mode 0600).
  5. Verify with antigravity-auth quota.

A fresh install may auto-fetch a project ID from the Cloud Code Assist API. If the lookup fails the plugin uses the hardcoded fallback project (rising-fact-p41fc); see 403 Permission Denied for manually setting a project ID.

Models and routing

packages/core/src/model-registry.ts is the canonical source of truth. The OpenCode package re-exports it as getAntigravityOpencodeModelIds() (consumed by applyAntigravityProviderCatalog).

Family Header style Quota group Routing
Claude (Opus / Sonnet thinking) antigravity claude Antigravity only
Gemini 3.x / 2.5 (Pro / Flash / Image) antigravity then gemini-cli gemini-pro, gemini-flash Antigravity first; cli_first: true reverses it
GPT-OSS 120B antigravity gpt-oss Antigravity only

Header style controls the User-Agent, X-Goog-Api-Client, and Client-Metadata triplets that the request transform attaches. The interceptor picks style per request:

  • Antigravity-first (default): Gemini requests first try the Antigravity header set; on 429 RESOURCE_EXHAUSTED they fall back to the Gemini CLI header set.
  • CLI-first (cli_first: true): Gemini requests first try the Gemini CLI header set; on 429 they fall back to Antigravity.
  • Style-fallback (quota_style_fallback: true): Re-sends the SAME request through the other header set when the active pool is rate-limited, consuming tokens from BOTH pools (default off to prevent double-spend across pools). The field is mutable from the /antigravity-routing dialog at runtime — toggling it does not require a restart.
  • Claude always uses Antigravity regardless of the toggles.

When all accounts for a given family cool down, the fetch interceptor waits up to max_rate_limit_wait_seconds (default 300) before failing fast.

Model IDs shipped in the registry

  • antigravity-gemini-3-pro, antigravity-gemini-3-flash, antigravity-gemini-3.5-flash, antigravity-gemini-3.1-pro, antigravity-gemini-3.1-flash-image
  • antigravity-claude-sonnet-4-6, antigravity-claude-opus-4-6-thinking
  • antigravity-gpt-oss-120b-medium
  • gemini-2.5-flash, gemini-2.5-pro, gemini-3-flash-preview, gemini-3-pro-preview, gemini-3.1-pro-preview, gemini-3.1-pro-preview-customtools (Gemini CLI namespace)

Legacy antigravity-gemini-3-flash style aliases resolve through MODEL_ALIASES in the resolver; new model names should be added through the registry, not hand-coded in the transforms. The Transform layer renames old → new automatically.

Multi-account behavior

The account pool persists in antigravity-accounts.json (storage schema AccountStorageV4) with locking via acquireFencedFileLock so concurrent writers cannot corrupt the file. Each account carries:

  • refreshToken (stored in plaintext; the file sits under 0600 with its parent directory at 0700 — filesystem permissions are the only protection at rest)
  • email, enabled, coolingDownUntil, cachedQuota, healthScore
  • projectId (optional; used by Gemini CLI)
  • fingerprintHistory and verificationRequired / verificationUrl (used by the verification flow)

Account selection (account_selection_strategy):

Strategy Behavior
sticky Use one account until it is rate-limited; preserves Anthropic's prompt cache. Recommended for single-session use.
hybrid (default) Token bucket + health score + LRU freshness. Best for most workloads.
round-robin Rotate per request. Useful when running many short requests in parallel.

pid_offset_enabled shifts the per-session starting index so different PIDs begin at different accounts — recommended when running multiple parallel subagents and oh-my-opencode is installed. Per-request caps: max_account_switches (default 10) bounds how many accounts a single request may try before failing.

switch_on_first_rate_limit controls whether the interceptor switches accounts after the first 429 (default on) or retries the same account once first. default_retry_after_seconds (60) and max_backoff_seconds (60) shape the wait between attempts. request_jitter_max_ms adds timing jitter — set to 100–500 to avoid thundering-herd patterns when running many parallel agents.

Quota semantics and killswitch

Each account carries a per-quota-group cachedQuota ({ remainingFraction, resetTime } for claude, gemini-pro, gemini-flash, gpt-oss). The quota manager refreshes them opportunistically:

  • Periodic refresh: every quota_refresh_interval_minutes after a successful request (default 30; the schema migrated from 15 to 30 for v2 — set to 0 to disable).
  • TTL: soft_quota_cache_ttl_minutes = "auto" resolves to max(2 × refresh_interval, 10). While the cache is fresh the threshold check is authoritative.
  • Stale-TTL fail-open: when the cache is older than the TTL OR for an account the plugin has never refreshed, the account is allowed through the soft-quota check. A cold start can never deadlock on the first dial.
  • Soft-quota threshold: soft_quota_threshold_percent (default 80) — accounts with usage ≥ threshold are skipped as if rate-limited.
  • All-exhausted: when every account exceeds the threshold the interceptor waits for the earliest reset time, then waits up to max_rate_limit_wait_seconds before failing fast.
  • Proactive rotation: after a successful request, if the active account's remaining quota drops below proactive_rotation_threshold_percent (default 20) the next request is dispatched from a warm-cache account to avoid a forced 429 mid-conversation.

The operator killswitch (/antigravity-killswitch) is a hard rejection layer run before dispatch:

  • enabled (default false) — master switch.
  • minimum_remaining_percent — global threshold; accounts whose freshest cached quota is ≤ this are excluded.
  • accounts — per-account override keyed by sha256(refreshToken).slice(0,12). The key never appears outside the config file; no raw token is ever written, logged, or sent over RPC.
  • Fail-open on stale/missing cache: a candidate with no fresh cached quota (TTL 5min by default) is allowed through so the first dispatch after a cold start cannot deadlock.
  • All-killed error: when the entire pool is excluded, the interceptor throws AntigravityKillswitchError carrying a per-account summary so the host can surface a single, structured error rather than a synthetic body.

Both filters are evaluated before the token bucket / health score pick, so the operator has the last word even on a healthy pool.

OpenCode sidebar and slash commands

The sidebar lives under the OpenCode TUI; it is read-only and polls sidebar-state.json every two seconds. The state contract version is 1; the host loader silently falls back to DEFAULT_SIDEBAR_STATE on malformed JSON or unknown versions.

Slash commands

All six commands are registered in packages/opencode/src/plugin/commands.ts and the modal flow goes through packages/opencode/src/tui/command-dialogs.tsx. The plugin entry writes a sidebar freshness bump after every apply through createSidebarRefresher so the TUI's next poll sees a fresh checkedAt.

Command Args Effect
/antigravity-quota [refresh] Status (or refresh) of cached quota across accounts. Returns within 2s default.
/antigravity-account add · refresh · remove · list Manage the account pool. add and refresh opt into a 120_000 ms RPC timeout (OAuth can take two minutes on a fresh login). list and remove use the 2_000 default.
/antigravity-routing cli_first=true|false · quota_style_fallback=true|false Toggle the live routing flags. Omit a key to flip the current value. Persists immediately.
/antigravity-killswitch enabled=true|false · minimum_remaining_percent=0..100 Configure the killswitch. Per-account thresholds are set via direct config edit.
/antigravity-dump enable · disable · status Toggle wire-dump capture for the next request. (Backward-compatible alias: /gemini-dump.)
/antigravity-logging error · warn · info · debug · trace Adjust runtime logging; takes effect immediately and persists across restarts.

Every apply RPC defaults to 2_000 ms. The account add / refresh paths opt into 120_000 ms because the OAuth flow can take up to two minutes on a fresh login. The two timeouts are visible to operators as the timeoutMs knob on the apply result.

Sidebar contract

getSidebarStateFile() resolves to $XDG_STATE_HOME/cortexkit/antigravity-auth/sidebar-state.json (or ~/.local/state/...). Override with ANTIGRAVITY_AUTH_SIDEBAR_STATE_FILE. The file carries:

  • version: 1, checkedAt (epoch ms), accounts[], activeRouting (per-session map, pruned to 24h / 100 entries), routingAuthoritative, optional quotaBackoffUntil and lastError.

Every writer follows the same recipe (serialize through sidebarWriteChain → acquire fenced file lock with 2s retry → re-read and merge → atomic rename mode 0600). If a cross-process lock holder does not release within SIDEBAR_LOCK_TIMEOUT_MS (2_000 ms) the writer surfaces SidebarStateLockContentionError; the caller decides whether to surface a toast, drop the write, or retry next tick.

Standalone CLI

antigravity-auth (the bin inside the OpenCode package) accepts three commands. The parser rejects anything else with exit 2.

antigravity-auth login [--project <id>] [--no-browser]
antigravity-auth list [--json]
antigravity-auth quota [--json] [--refresh]
  • login performs the same PKCE + local callback flow as the plugin. --project <id> pins the project ID before the request; --no-browser prints the URL instead of opening it. The CLI auto-detects a headless session (SSH_*, OPENCODE_HEADLESS=1) and combines that with --no-browser for you.
  • list prints a fixed-width table (INDEX, EMAIL, STATUS) where STATUS is active, disabled, ineligible, or verification-required. --json emits the same data as JSON.
  • quota prints ACCOUNT, STATUS, GROUP, REMAINING, RESET. With --refresh it forces a live refresh; with --json it emits the same array as JSON. Exit code is 0 on success, 1 on a thrown error, and 2 on a parse failure.

All commands write to the same antigravity-accounts.json the plugin reads; a login from the CLI shows up in the next sidebar poll after the plugin reload.

Pi usage

pi install npm:@cortexkit/pi-antigravity-auth
pi
/login google-antigravity                    # OAuth flow

The Pi extension registers the google-antigravity provider automatically through the package's pi.extensions field; no further wiring is needed. Both packages delegate to @cortexkit/antigravity-auth-core, so the request transport and model transforms are shared. The account-pool layer is not: Pi holds a single credential locally (no antigravity-accounts.json, no rotation, no killswitch, no operator settings). Multi-account rotation, quota routing, and the killswitch are OpenCode-only.

Configuration reference

packages/opencode/src/plugin/config/schema.ts defines AntigravityConfigSchema (Zod); the generated JSON Schema ships at packages/opencode/assets/antigravity.schema.json. Every nested config property, default, and environment override is documented below. Project settings (.opencode/antigravity.json) override user settings (~/.config/opencode/antigravity.json), which in turn override env vars.

Path Type Default Env override Effect
quiet_mode bool false OPENCODE_ANTIGRAVITY_QUIET=1 Suppress rate-limit and account-switching toasts; recovery toasts always shown.
toast_scope root_only | all root_only OPENCODE_ANTIGRAVITY_TOAST_SCOPE=all root_only silences subagents; all is verbose.
debug bool false OPENCODE_ANTIGRAVITY_DEBUG=1 File logging to ~/.config/opencode/antigravity-logs/.
debug_tui bool false OPENCODE_ANTIGRAVITY_DEBUG_TUI=1 TUI log-panel verbosity (independent of debug).
log_dir string XDG-derived OPENCODE_ANTIGRAVITY_LOG_DIR=… Custom debug-log directory.
keep_thinking bool false OPENCODE_ANTIGRAVITY_KEEP_THINKING=1 Preserve Claude thinking blocks (signature caching); can destabilize models.
thinking_warmup bool false OPENCODE_ANTIGRAVITY_THINKING_WARMUP=1 Costly warmup probe at session start.
cache_warmup_on_switch bool true OPENCODE_ANTIGRAVITY_CACHE_WARMUP_ON_SWITCH=1 Cache-seeding probe after account rotation.
session_recovery bool true Auto-recover from tool_result_missing errors.
auto_resume bool true Send resume_text after recovery.
resume_text string "continue" What to send on auto-resume.
signature_cache.enabled bool true Disk-persist thinking-block signatures.
signature_cache.memory_ttl_seconds number (60-86400) 3600 In-memory signature TTL.
signature_cache.disk_ttl_seconds number (3600-604800) 172800 On-disk signature TTL.
signature_cache.write_interval_seconds number (10-600) 60 Background flush interval.
empty_response_max_attempts number (1-10) 2 Retries when Antigravity returns no candidates.
empty_response_retry_delay_ms number (500-10000) 2000 Delay between empty-response retries.
tool_id_recovery bool true Match orphan tool IDs by name.
claude_tool_hardening bool true Inject strict tool-usage rules for Claude.
claude_prompt_auto_caching bool false Add top-level cache_control when absent.
proactive_token_refresh bool true Refresh access tokens before expiry.
proactive_refresh_buffer_seconds number (60-7200) 1800 Pre-expiry refresh window.
proactive_refresh_check_interval_seconds number (30-1800) 300 Refresh-check cadence.
max_rate_limit_wait_seconds number (0-3600) 300 Cap on queueing wait; 0 waits indefinitely.
quota_fallback bool false Deprecated, ignored at runtime; kept for back-compat.
cli_first bool false CLI header first instead of Antigravity for Gemini.
account_selection_strategy sticky | round-robin | hybrid hybrid OPENCODE_ANTIGRAVITY_ACCOUNT_SELECTION_STRATEGY Account picker.
pid_offset_enabled bool false OPENCODE_ANTIGRAVITY_PID_OFFSET_ENABLED=1 Per-PID starting account index.
switch_on_first_rate_limit bool true Switch immediately on first 429.
max_account_switches number (0-500) 10 OPENCODE_ANTIGRAVITY_MAX_ACCOUNT_SWITCHES Account-switch budget per request.
quota_style_fallback bool false OPENCODE_ANTIGRAVITY_QUOTA_STYLE_FALLBACK Re-send through the other header pool on exhaustion.
scheduling_mode cache_first | balance | performance_first cache_first OPENCODE_ANTIGRAVITY_SCHEDULING_MODE Rate-limit scheduling strategy.
max_cache_first_wait_seconds number (5-300) 60 Wait cap when scheduling_mode=cache_first.
failure_ttl_seconds number (60-7200) 3600 Failure-count reset window.
default_retry_after_seconds number (1-300) 60 Fallback retry delay when 429 omits Retry-After.
max_backoff_seconds number (5-300) 60 Exponential backoff cap.
request_jitter_max_ms number (0-5000) 0 Random pre-request delay to break cadence.
switch_account_delay_ms number (0-10000) 500 Delay before switching accounts.
soft_quota_threshold_percent number (1-100) 80 Skip accounts above this usage %; 100 disables.
quota_refresh_interval_minutes number (0-120) 30 Background refresh cadence; 0 disables.
soft_quota_cache_ttl_minutes auto | number (1-120) "auto" TTL for cached quota. auto = max(2 × refresh, 10) minutes.
proactive_rotation_threshold_percent number (0-100) 20 OPENCODE_ANTIGRAVITY_PROACTIVE_ROTATION_THRESHOLD Proactive account switcher; 0 disables.
health_score.initial number (0-100) 70 Health-score starting value.
health_score.success_reward number (0-10) 1 Score gain per success.
health_score.rate_limit_penalty number (-50..0) -10 Penalty per rate limit.
health_score.failure_penalty number (-100..0) -20 Penalty per failure.
health_score.recovery_rate_per_hour number (0-20) 2 Slow heal over time.
health_score.min_usable number (0-100) 50 Account is gated below this.
health_score.max_score number (50-100) 100 Score ceiling.
token_bucket.max_tokens number (1-1000) 50 Max concurrent budget.
token_bucket.regeneration_rate_per_minute number (0.1-60) 6 Rate budget refills.
token_bucket.initial_tokens number (1-1000) 50 Starting budget.
auto_update bool true Background npm update check + auto-pin.
operator.routing.cli_first bool false Live override of cli_first (from /antigravity-routing).
operator.routing.quota_style_fallback bool false Live override of quota_style_fallback.
operator.killswitch.enabled bool false Master killswitch.
operator.killswitch.minimum_remaining_percent number (0-100) 5 Global hard block threshold.
operator.killswitch.accounts record<string, number> unset Optional per-account override (key = sha256(refreshToken).slice(0,12)). Absent by default; falls back to minimum_remaining_percent.
operator.log_level error | warn | info | debug | trace "info" Runtime log-level filter (mutable from /antigravity-logging).

globalThis.fetch calls inside the package go through fetchWithActiveTimeout (see State, cache, log, RPC, and dump files).

State, cache, log, RPC, and dump files

Purpose Path Mode Notes
Account pool $OPENCODE_CONFIG_DIR/antigravity-accounts.json (or $XDG_CONFIG_HOME/opencode/antigravity-accounts.json, fallback to %APPDATA%\opencode\…) 0600 Per-account tokens, quota cache, fingerprint history. Storage schema V4.
Plugin config antigravity.json (project .opencode/, then user) 0600 The Zod-validated config above; OpenCodeConfigDir environment variable override.
Debug logs antigravity-logs/<file>.log (or log_dir) 0600 Created by the debug subsystem when debug is on.
Sidebar state $XDG_STATE_HOME/cortexkit/antigravity-auth/sidebar-state.json (override ANTIGRAVITY_AUTH_SIDEBAR_STATE_FILE) 0600 Versioned contract 1; pruned to 24h/100 entries.
RPC port file $XDG_STATE_HOME/cortexkit/antigravity-auth/rpc/<project-hash>/port-<pid>.json (override ANTIGRAVITY_AUTH_RPC_DIR) 0600 dir 0700 Loopback HTTP token bearer for the TUI.
Signature cache signature-cache-disk-<hash>.json under keep_thinking path 0600 2-day default TTL.
Wire dump antigravity-logs/dumps/ 0600 Sensitive — see Security and redaction.

The fetch interceptor uses fetchWithActiveTimeout (packages/core/src/fetch-timeout.ts). The contract is intentional: the 15_000 ms header-timeout aborts only the request until the underlying fetch resolves; once headers arrive the timeout listener is removed and the body may stream past the deadline. Streaming responses are bounded by the agy-transport.ts idle-timeout watchdog (180_000 ms / 3 min), so a stalled SSE chunk counts as a transport failure even though the header budget has been exceeded. Operators should not raise the 15s header window to chase a streaming response — the stream watchdog is the right knob.

The RPC between the TUI (packages/opencode/src/tui-compiled/) and the server plugin (packages/opencode/src/rpc/rpc-server.ts) uses a per-PID port file ($XDG_STATE_HOME/cortexkit/antigravity-auth/rpc/<project-hash>/port-<pid>.json). The TUI process reads the highest-mtime live entry, drops stale entries with process.kill(pid, 0) checks, and posts to /rpc/apply or /rpc/pending-notifications with a Bearer <token> header. Apply default timeout is 2_000 ms; 120_000 ms for antigravity-account add/refresh. Notifications are pushed over loopback HTTP and drained via the same /rpc/pending-notifications long-poll.

Disposal: the plugin's dispose() (called on host shutdown) drains sidebar writes via drainSidebarWrites() BEFORE tearing down the RPC server and file logger so any in-flight write enqueued at shutdown lands before the host closes the terminal. The lifecycle disposes (in order): the active AccountManager + token refresh queue → the disk signature cache → the session registry → the fetch state → sidebar drain → registered disposables (RPC server, file logger).

Security and redaction

  • Refresh tokens live in antigravity-accounts.json with file mode 0600. The file is rewritten through acquireFencedFileLock so concurrent writers never corrupt state. Existing overly-permissive files are tightened on load.
  • The sidebar state file carries only id/label/enabled/health/current/cooldownUntil/quota — never a refresh token, access token, project ID, fingerprint, or other credential-shaped field. redactAccountForSidebar is the single conversion point.
  • The killswitch accounts field is keyed by sha256(refreshToken).slice(0,12) so the raw token never lands in the config, sidebar, RPC payload, or apply response. The hash is deterministic per token but irreversible.
  • debug file logs MAY include request headers and response bodies for the model under test. Do not enable debug on a session that handles personally identifiable data. The wire dump produces a raw request/response pair; keep it out of issue reports. Operators can route logs to an encrypted directory via log_dir.
  • OAuth client ID and secret (packages/core/src/constants.ts) are part of the public Antigravity CLI client and are not sensitive on their own; they are not a credential pair.
  • This README and project tree never contain OAuth tokens, bearer tokens, account file contents, or request/response dumps.

Troubleshooting

Symptom Likely cause Fix
OAuth callback hangs The browser can't reach localhost:51121 (SSH, remote dev, Docker, WSL2) antigravity-auth login --no-browser, copy the URL into a browser that can reach the host; or ssh -L 51121:localhost:51121 user@remote to forward the callback port.
Safari shows "Safari can't open the page" HTTPS-Only mode blocks http://localhost callback Use Chrome or Firefox; or temporarily disable HTTPS-Only mode (Safari → Settings → Privacy) during login.
Address already in use :51121 Stale OAuth listener from a previous run lsof -i :51121 (or netstat -ano | findstr :51121 on Windows), kill the listed PID, retry.
Permission 'cloudaicompanion.companions.generateChat' denied (rising-fact-p41fc fallback project) Antigravity returned no project ID Enable the Gemini for Google Cloud API (cloudaicompanion.googleapis.com) on a Google Cloud project and add projectId to the account in antigravity-accounts.json. Repeat per account in multi-account setups.
All accounts show cooling-down (quota available) The threshold filter is excluding the entire pool Lower soft_quota_threshold_percent, or wait for soft_quota_cache_ttl_minutes to expire and let the stale-TTL fail-open kick in. Refresh quota via /antigravity-quota refresh.
Sidebar shows "Awaiting Antigravity state" forever The state file is missing or unreadable Confirm $XDG_STATE_HOME/cortexkit/antigravity-auth/sidebar-state.json exists and is 0600; verify the running plugin's user can write to the directory.
Antigravity RPC server is not available from the TUI The TUI is polling but no server has bound a port Confirm the host process actually loaded the plugin (config.command.antigravity-quota should appear); the RPC dir (ANTIGRAVITY_AUTH_RPC_DIR or its default) must be writable.
TUI shows "Awaiting antigravity bundle" / spawn fails bun run build:tui was skipped, or the precompiled tree is missing Run bun run --cwd packages/opencode build (it bundles tsc + esbuild + build:tui); confirm dist/src/tui-compiled/tui.tsx exists.
quota_backoff recovered slowly quota_refresh_interval_minutes is too high Lower it; or run /antigravity-quota refresh to force a refresh.
Malformed JSON in antigravity.json Invalid escape / trailing comma bun -e 'JSON.parse(require("fs").readFileSync(require("os").homedir()+"/.config/opencode/antigravity.json","utf8"))'; the schema reference in the file ($schema: assets/antigravity.schema.json) drives editor IntelliSense.
Invalid signature in thinking block errors keep_thinking left on through a Claude version bump Set keep_thinking: false; or clear signature_cache.disk_ttl_seconds and restart. The SKIP_THOUGHT_SIGNATURE sentinel is injected automatically for known-bad signatures.
Pre-v2 inline quota helper script Removed in v2 — the script no longer ships Use antigravity-auth quota [--refresh] [--json]; the npm-installed antigravity-auth binary replaces it.
Debug log location unknown debug toggled off, or log_dir misconfigured debug: true + log_dir: "/secure/path"; logs sit under antigravity-logs/ by default and contain request/response bodies — handle with care.

When none of the above resolves the issue: stop the host, delete antigravity-accounts.json (and optionally ~/.cache/opencode/node_modules/@cortexkit/opencode-antigravity-auth if a stale npm cache is suspected), and run opencode auth login again. Re-add accounts one at a time so a single problem account can be isolated.

Development workflow

This monorepo uses Bun for contribution commands; consumers install through npm. Keep both in mind when you write a script — npm users run npx @cortexkit/opencode-antigravity-auth, contributors run bun.

# at repo root
bun install                                         # sync workspace
bun run build                                       # core -> opencode -> pi
bun run typecheck                                   # tsc across the workspace + scripts tsconfig
bun run test                                        # bun test --isolate across packages
bun run test:e2e                                    # plugin-flow + cli-flow + rpc-tui-flow
bun run test:e2e:models                             # live Antigravity model inventory (gated by CI)
bun run test:e2e:regression                         # cross-model + Gemini CLI regression
bun run --cwd packages/opencode smoke:tui           # bundle + spawn the TUI precompiled tree against the npm package shape
bun run format:check && bun run lint                # Biome format + lint (no auto-fix in CI)

The TUI side ships a precompiled JSX tree (packages/opencode/src/tui-compiled/) generated by bun run build:tui (alias of bunx tsx packages/opencode/scripts/build-tui.ts). The output is gitignored; rebuild it as part of every bun run build. A smoke-test (bun run --cwd packages/opencode smoke:tui) installs the freshly-built artifact as a tarball into a scratch dir and confirms the host can resolve both the server and tui subpaths. CI runs the smoke-test before TUI-loading tests and before publishing.

bun run dev symlinks all packages into a dist-dev/ tree and watches for source changes. bun run dev:clean removes the dev symlinks; use it after switching branches.

Pre-commit hooks (.lefthook.yml) run Biome on changed files; CI re-runs the full bun run format:check && bun run lint gate.

Test strategy

The repo splits tests by what they exercise and what they need:

Scope Tooling Network How to run
Unit (deterministic) bun test --isolate (packages/{core,opencode,pi}/src, packages/e2e-tests/src) No bun run test
Black-box e2e (deterministic) Mock Antigravity server, harness runner (packages/e2e-tests/) No bun run test:e2e
Live model inventory bun run test:e2e:models (uses test-models.ts) Yes — gated by CI label bun run test:e2e:models
Cross-model regression bun run test:e2e:regression (script/test-regression.ts) Yes bun run test:e2e:regression
TUI smoke bun run --cwd packages/opencode smoke:tui (build-tarball install + spawn) No bun run --cwd packages/opencode smoke:tui

Deterministic tests run everywhere (including in CI). The live test:e2e:models and test:e2e:regression flows are gated by label because they hit real Google infrastructure. They live in packages/opencode/script/ (TypeScript + shell launchers) and use anthropic-agy style fixtures kept under test-fixtures/.

Release process

./scripts/release.sh 1.7.0            # tag-driven release: typecheck -> test -> build -> sync version -> commit + tag + push
./scripts/release.sh 1.7.0 --dry      # dry run: previews version sync without committing/pushing

The script enforces semver, refuses to run on a dirty tree, and refuses to run on a non-main/master branch without confirmation. It runs bun run typecheckbun run testbun run build before any mutation. scripts/version-sync.mjs is a Node script (no Bun runtime dep) that walks every workspace and writes the new version into each package.json. The script tags the release and pushes it; the GitHub workflow is what runs the actual publish.

The Release workflow (.github/workflows/release.yml) runs on tag push (or workflow_dispatch) and:

  1. Test job — checkout the tag ref → install Bun 1.3 + Node 24 → bun install --frozen-lockfilebun run typecheckbun run buildbun run --cwd packages/opencode smoke:tuibun testbun run test:e2ebun run format:checkbun run lint.
  2. Publish job — matrixed across the three packages (@cortexkit/antigravity-auth-core, @cortexkit/opencode-antigravity-auth, @cortexkit/pi-antigravity-auth) with max-parallel: 1 so each package picks up its own OIDC token; npm Trusted Publishing (npm publish --workspace … --access public --provenance) handles the upload. The job skips a package if its npm version is already published.
  3. GitHub Release job — softprops/action-gh-release@v2 creates the GitHub release with generate_release_notes: true.

CI (.github/workflows/ci.yml) reuses the same typecheck → build → smoke → test → e2e → format → lint chain on every push to main and on every pull request. The issue-triage.yml workflow handles GitHub-issue routing.

Architecture and structure links

License

MIT. See LICENSE.

About

Google Antigravity OAuth for OpenCode and Pi — access Gemini 3 and Claude 4.6 with Google credentials

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages