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. |
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.
| 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 bundleddist/index.jsserver root;opencode pluginwrites this entry intoopencode.json'spluginarray.exports["./tui"]—src/tui/entry.mjs, the OpenTUI sidebar loader;opencode pluginwrites this entry intotui.jsonso the TUI process picks it up on the next host start. The precompiled JSX tree ships underdist/src/tui-compiled/and is regenerated bybun run build:tui; the host's loader also accepts the rawsrc/tui.tsxsource 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).
The supported installer writes both registrations in one step:
opencode plugin @cortexkit/opencode-antigravity-auth@latestWhat this does:
- Reads the package's
exports["."](the server root) and writes it into~/.config/opencode/opencode.jsonunder thepluginarray — that is theauth.loaderandfetchinterceptor the host calls on every model dispatch. - Reads the package's
exports["./tui"](the OpenTUI sidebar loader) and writes it into~/.config/opencode/tui.jsonso 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.opencoderange (>=1.17.13 <2).
If you cannot use opencode plugin, write both files by hand. The server entry lives in opencode.json:
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.
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 TUIThen 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 install npm:@cortexkit/pi-antigravity-auth
pi # interactive
/login google-antigravity # OAuthPi's extension loader discovers the package's pi.extensions field and registers the provider automatically; no manual wiring required.
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 --jsonThe 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.
- Install the package and start OpenCode (or the standalone CLI).
- For OpenCode: open the auth menu and choose Antigravity (Google OAuth), or run
/antigravity-account add. - For the CLI: run
antigravity-auth login.--project <id>pins a Google Cloud project before the flow;--no-browserprints the URL instead of opening it (useful in headless / SSH sessions — see Troubleshooting). - Approve the consent screen. The browser redirects to
http://localhost:51121/oauth-callback; the local listener completes the PKCE exchange, persistsrefreshToken/accessToken/email/ optionalprojectIdinto~/.config/opencode/antigravity-accounts.json(mode0600). - 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.
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_EXHAUSTEDthey fall back to the Gemini CLI header set. - CLI-first (
cli_first: true): Gemini requests first try the Gemini CLI header set; on429they 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-routingdialog 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.
antigravity-gemini-3-pro,antigravity-gemini-3-flash,antigravity-gemini-3.5-flash,antigravity-gemini-3.1-pro,antigravity-gemini-3.1-flash-imageantigravity-claude-sonnet-4-6,antigravity-claude-opus-4-6-thinkingantigravity-gpt-oss-120b-mediumgemini-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.
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 under0600with its parent directory at0700— filesystem permissions are the only protection at rest)email,enabled,coolingDownUntil,cachedQuota,healthScoreprojectId(optional; used by Gemini CLI)fingerprintHistoryandverificationRequired/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.
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_minutesafter a successful request (default30; the schema migrated from 15 to 30 for v2 — set to0to disable). - TTL:
soft_quota_cache_ttl_minutes = "auto"resolves tomax(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(default80) — 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_secondsbefore failing fast. - Proactive rotation: after a successful request, if the active account's remaining quota drops below
proactive_rotation_threshold_percent(default20) 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(defaultfalse) — master switch.minimum_remaining_percent— global threshold; accounts whose freshest cached quota is ≤ this are excluded.accounts— per-account override keyed bysha256(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
5minby 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
AntigravityKillswitchErrorcarrying 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.
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.
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.
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, optionalquotaBackoffUntilandlastError.
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.
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]loginperforms the same PKCE + local callback flow as the plugin.--project <id>pins the project ID before the request;--no-browserprints the URL instead of opening it. The CLI auto-detects a headless session (SSH_*,OPENCODE_HEADLESS=1) and combines that with--no-browserfor you.listprints a fixed-width table (INDEX,EMAIL,STATUS) whereSTATUSisactive,disabled,ineligible, orverification-required.--jsonemits the same data as JSON.quotaprintsACCOUNT,STATUS,GROUP,REMAINING,RESET. With--refreshit forces a live refresh; with--jsonit emits the same array as JSON. Exit code is0on success,1on a thrown error, and2on 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 install npm:@cortexkit/pi-antigravity-auth
pi
/login google-antigravity # OAuth flowThe 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.
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).
| 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).
- Refresh tokens live in
antigravity-accounts.jsonwith file mode0600. The file is rewritten throughacquireFencedFileLockso 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.redactAccountForSidebaris the single conversion point. - The killswitch
accountsfield is keyed bysha256(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. debugfile logs MAY include request headers and response bodies for the model under test. Do not enabledebugon 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 vialog_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.
| 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.
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.
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/.
./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/pushingThe 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 typecheck → bun run test → bun 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:
- Test job — checkout the tag ref → install Bun 1.3 + Node 24 →
bun install --frozen-lockfile→bun run typecheck→bun run build→bun run --cwd packages/opencode smoke:tui→bun test→bun run test:e2e→bun run format:check→bun run lint. - Publish job — matrixed across the three packages (
@cortexkit/antigravity-auth-core,@cortexkit/opencode-antigravity-auth,@cortexkit/pi-antigravity-auth) withmax-parallel: 1so 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. - GitHub Release job —
softprops/action-gh-release@v2creates the GitHub release withgenerate_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.md — system-of-record architecture doc across all three packages.
- STRUCTURE.md — file-system map of the monorepo.
- packages/opencode/ARCHITECTURE.md — OpenCode plugin architecture in detail.
- packages/opencode/STRUCTURE.md — OpenCode package file-system map.
- packages/opencode/CHANGELOG.md — version history (current is
2.0.0; previous major was1.6.0). - packages/opencode/docs/CONFIGURATION.md — detailed option walkthrough.
- packages/opencode/docs/MULTI-ACCOUNT.md — load balancing, dual quota pools, account storage.
- packages/opencode/docs/MODEL-VARIANTS.md — variants, thinking budgets, model selection.
- packages/opencode/docs/TROUBLESHOOTING.md — long-form troubleshooting with platform-specific commands.
- packages/opencode/docs/ANTIGRAVITY_API_SPEC.md — Antigravity API reference.
- packages/opencode/assets/antigravity.schema.json — generated JSON Schema for IDE validation.
MIT. See LICENSE.