Skip to content

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:

PackageHostRole
@cortexkit/opencode-antigravity-authOpenCode 1.x serverIntercepts fetch(), runs the account pool + quota manager, drives slash commands, and exposes a TUI sidebar through a loopback RPC.
@cortexkit/pi-antigravity-authPi coding agentRegisters a custom provider with OAuth login + Gemini streaming.
@cortexkit/antigravity-auth-coreAny harnessHarness-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

HostPackage versionHost requirementInstall path
OpenCode (server + TUI)2.0.0@opencode-ai/plugin >=1.17.13 <2 · @opentui/core, @opentui/keymap, @opentui/solid all ^0.4.5opencode plugin @cortexkit/opencode-antigravity-auth@latest (writes the server entry to opencode.json and the TUI entry to tui.json)
Pi coding agent2.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 CLIshipped inside the OpenCode package as the antigravity-auth binNode 20+npx @cortexkit/opencode-antigravity-auth
Node libraries (harness builders)2.0.0 coreNode 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).

FamilyHeader styleQuota groupRouting
Claude (Opus / Sonnet thinking)antigravityclaudeAntigravity only
Gemini 3.x / 2.5 (Pro / Flash / Image)antigravity then gemini-cligemini-pro, gemini-flashAntigravity first; cli_first: true reverses it
GPT-OSS 120Bantigravitygpt-ossAntigravity 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.7-flash, antigravity-gemini-3.6-flash, antigravity-gemini-3.5-flash, antigravity-gemini-3.1-pro, antigravity-gemini-3.1-flash-image
  • antigravity-claude-sonnet-4-6-thinking, 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):

StrategyBehavior
stickyUse 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-robinRotate 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.

CommandArgsEffect
/antigravity-quota[refresh]Status (or refresh) of cached quota across accounts. Returns within 2s default.
/antigravity-accountadd · refresh · remove · listManage 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-routingcli_first=true|false · quota_style_fallback=true|falseToggle the live routing flags. Omit a key to flip the current value. Persists immediately.
/antigravity-killswitchenabled=true|false · minimum_remaining_percent=0..100Configure the killswitch. Per-account thresholds are set via direct config edit.
/antigravity-dumpenable · disable · statusToggle wire-dump capture for the next request. (Backward-compatible alias: /gemini-dump.)
/antigravity-loggingerror · warn · info · debug · traceAdjust 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.

PathTypeDefaultEnv overrideEffect
quiet_modeboolfalseOPENCODE_ANTIGRAVITY_QUIET=1Suppress rate-limit and account-switching toasts; recovery toasts always shown.
toast_scoperoot_only | allroot_onlyOPENCODE_ANTIGRAVITY_TOAST_SCOPE=allroot_only silences subagents; all is verbose.
debugboolfalseOPENCODE_ANTIGRAVITY_DEBUG=1File logging to ~/.config/opencode/antigravity-logs/.
debug_tuiboolfalseOPENCODE_ANTIGRAVITY_DEBUG_TUI=1TUI log-panel verbosity (independent of debug).
log_dirstringXDG-derivedOPENCODE_ANTIGRAVITY_LOG_DIR=…Custom debug-log directory.
keep_thinkingboolfalseOPENCODE_ANTIGRAVITY_KEEP_THINKING=1Preserve Claude thinking blocks (signature caching); can destabilize models.
thinking_warmupboolfalseOPENCODE_ANTIGRAVITY_THINKING_WARMUP=1Costly warmup probe at session start.
cache_warmup_on_switchbooltrueOPENCODE_ANTIGRAVITY_CACHE_WARMUP_ON_SWITCH=1Cache-seeding probe after account rotation.
session_recoverybooltrueAuto-recover from tool_result_missing errors.
auto_resumebooltrueSend resume_text after recovery.
resume_textstring"continue"What to send on auto-resume.
signature_cache.enabledbooltrueDisk-persist thinking-block signatures.
signature_cache.memory_ttl_secondsnumber (60-86400)3600In-memory signature TTL.
signature_cache.disk_ttl_secondsnumber (3600-604800)172800On-disk signature TTL.
signature_cache.write_interval_secondsnumber (10-600)60Background flush interval.
empty_response_max_attemptsnumber (1-10)2Retries when Antigravity returns no candidates.
empty_response_retry_delay_msnumber (500-10000)2000Delay between empty-response retries.
tool_id_recoverybooltrueMatch orphan tool IDs by name.
claude_tool_hardeningbooltrueInject strict tool-usage rules for Claude.
claude_prompt_auto_cachingboolfalseAdd top-level cache_control when absent.
proactive_token_refreshbooltrueRefresh access tokens before expiry.
proactive_refresh_buffer_secondsnumber (60-7200)1800Pre-expiry refresh window.
proactive_refresh_check_interval_secondsnumber (30-1800)300Refresh-check cadence.
max_rate_limit_wait_secondsnumber (0-3600)300Cap on queueing wait; 0 waits indefinitely.
quota_fallbackboolfalseDeprecated, ignored at runtime; kept for back-compat.
cli_firstboolfalseCLI header first instead of Antigravity for Gemini.
account_selection_strategysticky | round-robin | hybridhybridOPENCODE_ANTIGRAVITY_ACCOUNT_SELECTION_STRATEGYAccount picker.
pid_offset_enabledboolfalseOPENCODE_ANTIGRAVITY_PID_OFFSET_ENABLED=1Per-PID starting account index.
switch_on_first_rate_limitbooltrueSwitch immediately on first 429.
max_account_switchesnumber (0-500)10OPENCODE_ANTIGRAVITY_MAX_ACCOUNT_SWITCHESAccount-switch budget per request.
quota_style_fallbackboolfalseOPENCODE_ANTIGRAVITY_QUOTA_STYLE_FALLBACKRe-send through the other header pool on exhaustion.
scheduling_modecache_first | balance | performance_firstcache_firstOPENCODE_ANTIGRAVITY_SCHEDULING_MODERate-limit scheduling strategy.
max_cache_first_wait_secondsnumber (5-300)60Wait cap when scheduling_mode=cache_first.
failure_ttl_secondsnumber (60-7200)3600Failure-count reset window.
default_retry_after_secondsnumber (1-300)60Fallback retry delay when 429 omits Retry-After.
max_backoff_secondsnumber (5-300)60Exponential backoff cap.
request_jitter_max_msnumber (0-5000)0Random pre-request delay to break cadence.
switch_account_delay_msnumber (0-10000)500Delay before switching accounts.
soft_quota_threshold_percentnumber (1-100)80Skip accounts above this usage %; 100 disables.
quota_refresh_interval_minutesnumber (0-120)30Background refresh cadence; 0 disables.
soft_quota_cache_ttl_minutesauto | number (1-120)"auto"TTL for cached quota. auto = max(2 × refresh, 10) minutes.
proactive_rotation_threshold_percentnumber (0-100)20OPENCODE_ANTIGRAVITY_PROACTIVE_ROTATION_THRESHOLDProactive account switcher; 0 disables.
health_score.initialnumber (0-100)70Health-score starting value.
health_score.success_rewardnumber (0-10)1Score gain per success.
health_score.rate_limit_penaltynumber (-50..0)-10Penalty per rate limit.
health_score.failure_penaltynumber (-100..0)-20Penalty per failure.
health_score.recovery_rate_per_hournumber (0-20)2Slow heal over time.
health_score.min_usablenumber (0-100)50Account is gated below this.
health_score.max_scorenumber (50-100)100Score ceiling.
token_bucket.max_tokensnumber (1-1000)50Max concurrent budget.
token_bucket.regeneration_rate_per_minutenumber (0.1-60)6Rate budget refills.
token_bucket.initial_tokensnumber (1-1000)50Starting budget.
auto_updatebooltrueBackground npm update check + auto-pin.
operator.routing.cli_firstboolfalseLive override of cli_first (from /antigravity-routing).
operator.routing.quota_style_fallbackboolfalseLive override of quota_style_fallback.
operator.killswitch.enabledboolfalseMaster killswitch.
operator.killswitch.minimum_remaining_percentnumber (0-100)5Global hard block threshold.
operator.killswitch.accountsrecord<string, number>unsetOptional per-account override (key = sha256(refreshToken).slice(0,12)). Absent by default; falls back to minimum_remaining_percent.
operator.log_levelerror | 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

PurposePathModeNotes
Account pool$OPENCODE_CONFIG_DIR/antigravity-accounts.json (or $XDG_CONFIG_HOME/opencode/antigravity-accounts.json, fallback to %APPDATA%\opencode\…)0600Per-account tokens, quota cache, fingerprint history. Storage schema V4.
Plugin configantigravity.json (project .opencode/, then user)0600The Zod-validated config above; OpenCodeConfigDir environment variable override.
Debug logsantigravity-logs/<file>.log (or log_dir)0600Created 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)0600Versioned 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 0700Loopback HTTP token bearer for the TUI.
Signature cachesignature-cache-disk-<hash>.json under keep_thinking path06002-day default TTL.
Wire dumpantigravity-logs/dumps/0600Sensitive — 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

SymptomLikely causeFix
OAuth callback hangsThe 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 callbackUse Chrome or Firefox; or temporarily disable HTTPS-Only mode (Safari → Settings → Privacy) during login.
Address already in use :51121Stale OAuth listener from a previous runlsof -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 IDEnable 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 poolLower 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" foreverThe state file is missing or unreadableConfirm $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 TUIThe TUI is polling but no server has bound a portConfirm 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 failsbun run build:tui was skipped, or the precompiled tree is missingRun bun run --cwd packages/opencode build (it bundles tsc + esbuild + build:tui); confirm dist/src/tui-compiled/tui.tsx exists.
quota_backoff recovered slowlyquota_refresh_interval_minutes is too highLower it; or run /antigravity-quota refresh to force a refresh.
Malformed JSON in antigravity.jsonInvalid escape / trailing commabun -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 errorskeep_thinking left on through a Claude version bumpSet 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 scriptRemoved in v2 — the script no longer shipsUse antigravity-auth quota [--refresh] [--json]; the npm-installed antigravity-auth binary replaces it.
Debug log location unknowndebug toggled off, or log_dir misconfigureddebug: 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:

ScopeToolingNetworkHow to run
Unit (deterministic)bun test --isolate (packages/{core,opencode,pi}/src, packages/e2e-tests/src)Nobun run test
Black-box e2e (deterministic)Mock Antigravity server, harness runner (packages/e2e-tests/)Nobun run test:e2e
Live model inventorybun run test:e2e:models (uses test-models.ts)Yes — gated by CI labelbun run test:e2e:models
Cross-model regressionbun run test:e2e:regression (script/test-regression.ts)Yesbun run test:e2e:regression
TUI smokebun run --cwd packages/opencode smoke:tui (build-tarball install + spawn)Nobun 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

22 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages