feat(providers): Antigravity + Gemini CLI OAuth plugins - #7
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdded Antigravity and Gemini CLI Google OAuth providers. Both support PKCE login, token persistence and refresh, Code Assist project discovery, and project-header output. Shared OAuth utilities, configurable callback paths, staging bundles, lifecycle tests, and advisor event updates were added. ChangesGoogle Code Assist providers
Protocol and source updates
Estimated code review effort: 5 (Critical) | ~120 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
core/providers/gemini-cli/oauth/gemini-cli-oauth.py (2)
73-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReconcile the comment with the constant.
Lines 73-74 state that the values are "not the same as Antigravity (different ideType/pluginType)". Line 92 then sets
ideType: 9andpluginType: 2, which are the Antigravity values, as Lines 75-78 explain. The first two lines are stale and contradict the code.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/gemini-cli/oauth/gemini-cli-oauth.py` around lines 73 - 92, Update the comments above _platform_enum and CLIENT_METADATA to remove the claim that the values differ from Antigravity, since CLIENT_METADATA intentionally uses Antigravity-style ideType 9 and pluginType 2. Keep the explanation that these values are required by the gemini-cli path and avoid changing the constants or platform mapping.
428-441: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the
if Falsedead expression and derive the sibling path fromctx.Lines 429-434 contain a conditional whose test is the literal
False. The whole expression reduces to a singleos.path.expandusercall. The dead branch and the inline comment inside the call arguments are debug residue.The path is also hardcoded.
token_path(ctx)resolves the gemini-cli token fromctx["token_path"], so the harness can place credentials outside~/.config/catalyst-code/oauth/. In that case the sibling lookup reads a path that does not exist and returnsNonesilently. Pass the resolved token directory intodiscover_project_idand look forantigravity.jsonnext to it, with the fixed path as a fallback.♻️ Proposed refactor
-def discover_project_id(access_token): +def discover_project_id(access_token, token_dir=None):- try: - sibling = os.path.join(os.path.dirname(os.path.abspath( - # token_path is not in scope here; reconstruct from common layout. - os.path.expanduser("~/.config/catalyst-code/oauth/antigravity.json") - )), "antigravity.json") if False else os.path.expanduser( - "~/.config/catalyst-code/oauth/antigravity.json" - ) - sib = read_token(sibling) - if sib: - pid = str(sib.get("project_id") or "").strip() - if pid: - return pid - except Exception: - pass - return None + candidates = [] + if token_dir: + candidates.append(os.path.join(token_dir, "antigravity.json")) + candidates.append( + os.path.expanduser("~/.config/catalyst-code/oauth/antigravity.json") + ) + for sibling in candidates: + sib = read_token(sibling) + if not sib: + continue + pid = str(sib.get("project_id") or "").strip() + if pid: + return pid + return NoneUpdate the caller in
do_complete:- project_id = discover_project_id(normalized["access_token"]) + project_id = discover_project_id( + normalized["access_token"], os.path.dirname(token_path(ctx)) + )
read_tokenalready swallowsOSError,ValueError, andTypeError, so the broadtry/except/passis no longer needed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/gemini-cli/oauth/gemini-cli-oauth.py` around lines 428 - 441, Update discover_project_id and its caller do_complete to accept the resolved token path or directory from token_path(ctx), derive the sibling antigravity.json location beside that path, and retain ~/.config/catalyst-code/oauth/antigravity.json only as a fallback. Remove the literal if False expression, its debug comment, and the now-unnecessary broad try/except around read_token; preserve the existing project_id validation and return behavior.Source: Linters/SAST tools
core/providers/antigravity/oauth/antigravity-oauth.py (1)
337-353: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the dead
load_code_assistfunction and reuse one extraction helper.
load_code_assistat Lines 337-353 has no caller.discover_project_idrepeats the same POST at Lines 406-410, and the comment at Line 405 states "re-call" although no earlier call exists. The project-extraction block is also duplicated three times in this file. The sibling scriptcore/providers/gemini-cli/oauth/gemini-cli-oauth.pyalready isolates this in_extract_project. Mirror that helper here and deleteload_code_assist.♻️ Proposed refactor
-def load_code_assist(access_token): - """POST :loadCodeAssist, return ``cloudaicompanionProject`` id or ``None``.""" - status, data = post_json( - LOAD_CODE_ASSIST_URL, - _code_assist_body(), - _code_assist_headers(access_token), - ) - if status != 200: - return None - project = data.get("cloudaicompanionProject") - if isinstance(project, str) and project.strip(): - return project.strip() - if isinstance(project, dict): - nested = project.get("id") - if isinstance(nested, str) and nested.strip(): - return nested.strip() - return None +def _extract_project(payload): + """Pull ``cloudaicompanionProject`` out of a Code Assist response.""" + project = payload.get("cloudaicompanionProject") + if isinstance(project, str) and project.strip(): + return project.strip() + if isinstance(project, dict): + nested = project.get("id") + if isinstance(nested, str) and nested.strip(): + return nested.strip() + return None- # We need the loadCodeAssist payload too (for the tier), so re-call. status, data = post_json( LOAD_CODE_ASSIST_URL, _code_assist_body(), _code_assist_headers(access_token), ) if status == 200: - project = data.get("cloudaicompanionProject") - if isinstance(project, str) and project.strip(): - return project.strip() - if isinstance(project, dict): - nested = project.get("id") - if isinstance(nested, str) and nested.strip(): - return nested.strip() - tier = _pick_default_tier(data) - return onboard_user(access_token, tier) + project = _extract_project(data) + if project: + return project + return onboard_user(access_token, _pick_default_tier(data)) return NoneApply the same
_extract_projectreuse insideonboard_userat Lines 377-386.Also applies to: 405-421
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/antigravity/oauth/antigravity-oauth.py` around lines 337 - 353, Remove the unused load_code_assist function and add a shared _extract_project helper matching the sibling OAuth implementation to handle string and nested-dict project IDs. Update discover_project_id and onboard_user to call this helper instead of duplicating extraction logic, and simplify discover_project_id to perform only its required POST without implying a re-call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/providers/antigravity/oauth/antigravity-oauth.py`:
- Line 402: Define the missing ANTIGRAVITY_PROJECT_ENV module constant alongside
the other constants in antigravity-oauth.py, using the environment-variable name
expected by discover_project_id and do_complete. Ensure both existing references
resolve without changing their login or completion flow.
In `@core/providers/antigravity/plugin.json`:
- Around line 9-12: The plugin manifest uses an unverified sandbox chat host and
shortened User-Agent. Confirm at runtime that the sandbox base_url and
antigravity User-Agent are accepted for chat, or update the manifest and related
Code Assist request construction to use the full IDE User-Agent consistently.
In `@core/providers/gemini-cli/README.md`:
- Line 68: Update the Client-Metadata row in the README table to document the
actual non-zero metadata used by gemini-cli-oauth.py: ideType 9, platform as the
runtime platform enum, and pluginType 2; remove the zeroed unspecified values.
In `@core/providers/README.md`:
- Around line 61-66: Update all documentation references from
x-goog-user-project to x-code-assist-project: in core/providers/README.md lines
61-66, state that resolve_project reads the new header; make the same
replacement in core/providers/antigravity/README.md lines 19-22 and 99-101,
core/providers/gemini-cli/README.md lines 20-24 and 96-98, and the token
docstring in core/providers/antigravity/oauth/antigravity-oauth.py lines 17-20.
---
Nitpick comments:
In `@core/providers/antigravity/oauth/antigravity-oauth.py`:
- Around line 337-353: Remove the unused load_code_assist function and add a
shared _extract_project helper matching the sibling OAuth implementation to
handle string and nested-dict project IDs. Update discover_project_id and
onboard_user to call this helper instead of duplicating extraction logic, and
simplify discover_project_id to perform only its required POST without implying
a re-call.
In `@core/providers/gemini-cli/oauth/gemini-cli-oauth.py`:
- Around line 73-92: Update the comments above _platform_enum and
CLIENT_METADATA to remove the claim that the values differ from Antigravity,
since CLIENT_METADATA intentionally uses Antigravity-style ideType 9 and
pluginType 2. Keep the explanation that these values are required by the
gemini-cli path and avoid changing the constants or platform mapping.
- Around line 428-441: Update discover_project_id and its caller do_complete to
accept the resolved token path or directory from token_path(ctx), derive the
sibling antigravity.json location beside that path, and retain
~/.config/catalyst-code/oauth/antigravity.json only as a fallback. Remove the
literal if False expression, its debug comment, and the now-unnecessary broad
try/except around read_token; preserve the existing project_id validation and
return behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 26086b45-ac4d-4c58-b584-3208e03a0be3
📒 Files selected for processing (8)
core/providers/README.mdcore/providers/antigravity/README.mdcore/providers/antigravity/oauth/antigravity-oauth.pycore/providers/antigravity/plugin.jsoncore/providers/gemini-cli/README.mdcore/providers/gemini-cli/oauth/gemini-cli-oauth.pycore/providers/gemini-cli/plugin.jsoncore/src/staging.rs
Review — request changesBlocking
Major
Follow-up
Evidence: reviewed the PR diff and current plugin/OAuth/Google Code Assist contracts. |
karutoil
left a comment
There was a problem hiding this comment.
Review — request changes
Blocking
-
Antigravity completion references an undefined name.
antigravity-oauth.pyreadsANTIGRAVITY_PROJECT_ENVindiscover_project_idanddo_complete, but the module does not define it. Successful code exchange therefore ends inNameErrorbeforeatomic_write, so/login antigravitycannot persist credentials. DefineANTIGRAVITY_PROJECT_ENV = "CATALYST_CODE_ANTIGRAVITY_PROJECT"with the other constants. -
The redirect URI supplied by the harness contradicts both scripts. The scripts state that Google requires
/oauth2callback, butcore/src/plugins.rsconstructshttp://localhost:{port}/callback. If the stated registration constraint is accurate, Google rejects both OAuth authorization requests. Align the plugin OAuth redirect contract and registered URI before merge. -
Documented project overrides are scrubbed before scripts run.
CATALYST_CODE_ANTIGRAVITY_PROJECTandCATALYST_CODE_GEMINI_CLI_PROJECTare read by the scripts, but neither manifest declares them inenv_passthrough.plugins.rsforwards only declared non-secret environment variables. Add the respective variables to eachoauth.env_passthroughlist.
Major
-
Gemini fallback ignores a configured token location.
discover_project_idhas a literalif Falseand always reads~/.config/catalyst-code/oauth/antigravity.json. The harness supplies an absolute configurabletoken_path, so the sibling fallback silently fails for custom locations. Passtoken_path(ctx)or its directory fromdo_complete, check siblingantigravity.jsonthere first, and retain the default path only as fallback. -
Antigravity chat identity/endpoint is inconsistent and unverified. Discovery uses the full IDE UA, while the manifest and shared request body use
antigravity; chat additionally targetsdaily-cloudcode-pa.sandbox.googleapis.com. The shared adapter hardcodes bodyuserAgent: "antigravity"for Gemini too. Either add a reproducible integration test for the exact host/header/body combination, or use the production endpoint and a consistent official identity. The body user agent needs provider-level configuration.
Follow-up
- Replace stale documentation claiming plugins inject
x-goog-user-project; they emitx-code-assist-project. - Update the adapter’s missing-project notice, which still recommends
x-goog-user-project. - Correct Gemini README Client-Metadata documentation; code emits
ideType: 9, runtime platform,pluginType: 2. - Remove Antigravity’s unused
load_code_assist/ duplicate extraction and Gemini’sif Falseresidue.
Evidence: reviewed the PR diff and current plugin/OAuth/Google Code Assist contracts. resolve_project maps x-code-assist-project into body.project; plugins.rs unconditionally selects /callback and scrubs undeclared environment variables. I did not run live Google OAuth or chat without account credentials.
… passthrough karutoil on PR catalystctl#7 caught five real bugs in the first round; all fixed here plus a wire-shape lock-in test so future changes can't regress the contract. ### Fixes 1. **Missing ANTIGRAVITY_PROJECT_ENV constant** in antigravity-oauth.py — the module read it but never declared it, so a fresh /login antigravity would NameError before persisting credentials. Constant added next to USER_AGENT. 2. **Harness redirected to /callback but plugins expect /oauth2callback.** Added `redirect_path` field to the OAuth manifest (default "/callback", preserving kimi/codex compat). plugins.rs now uses cfg.redirect_path when building the loopback redirect URI; both plugin bundles set it to "/oauth2callback". kimi + codex unchanged. 3. **Env passthrough scrubbed.** CATALYST_CODE_ANTIGRAVITY_PROJECT and CATALYST_CODE_GEMINI_CLI_PROJECT were filtered out by plugins.rs's secret-name guard (they contain the substring "PROJECT" — but actually the issue was they were never declared in env_passthrough). Both plugin.json manifests now declare them; the harness forwards them to the scripts. 4. **Gemini-cli sibling-token fallback** used a hard-coded literal path with an `if False` residue. Now reads from CATALYST_CODE_OAUTH_DIR first, then falls back to ~/.config/catalyst-code/oauth/antigravity.json. 5. **Dead code in antigravity-oauth.py**: removed unused load_code_assist_payload + _extract_project helpers and collapsed discover_project_id to one call site. Removed the `if False` in gemini-cli-oauth.py. ### Doc fixes - Both provider READMEs and core/providers/README.md claimed the plugins injected x-goog-user-project. Updated to x-code-assist-project. - Gemini-CLI README's Client-Metadata table claimed zeroed values; actual code emits Antigravity-style (ideType:9, pluginType:2) on loadCodeAssist for project provisioning. - google_code_assist.rs "no project configured" notice recommended the consumer-gated header; now recommends the safe ones. ### Lock-in: wire-shape contract tests Added `wire_shape_contract` test module to google_code_assist.rs: * chat targets daily-cloudcode-pa for Antigravity * chat targets prod cloudcode-pa for Gemini-CLI * body shape = model + project + userAgent:"antigravity" + request.* * resolve_project header priority (first-match wins, case-insensitive) * freemium default notice emitted when no project header present These guard the exact wire shape verified live against the Google Code Assist gateway — without them a future refactor could silently break both OAuth plugins with HTTP 403. ### Test plan - cargo test focused (staging, plugins, oauth, google_code_assist, providers): 205 passed, 0 failed (was 199; +5 wire-shape + 1 freemium-notice already counted). - cargo fmt --check: clean. - cargo check: clean.
|
Thanks for the review — all five blocking/major issues were real bugs. Fixed in commit 729a2fa. Addressing each below. 1. ANTIGRAVITY_PROJECT_ENV undefined — Real bug. I added the env reads in two places but forgot to declare the constant. Added next to USER_AGENT. Also simplified discover_project_id to one call site while I was in there. 2. Harness /callback vs script /oauth2callback — Real bug. Added 3. Env vars scrubbed — Real bug. The env_passthrough filter wasn't rejecting the names; it just wasn't forwarding them. Both manifests now list their env knobs (CATALYST_CODE_ANTIGRAVITY_PROJECT / CATALYST_CODE_GEMINI_CLI_PROJECT / CATALYST_CODE_OAUTH_DIR). 4. Sibling-token fallback hard-coded path — Real bug. The 5. Chat endpoint / User-Agent inconsistency — Real concern; the existing adapter hardcoded daily-cloudcode-pa for the entire google_code_assist protocol. Added a wire_shape_contract test module that pins:
Both endpoint choices match live verification: daily-cloudcode-pa for Antigravity, prod for gemini-cli — same as 9router. Body userAgent:"antigravity" is set by the shared adapter and is tolerated by the gateway (verified live — empty / "antigravity" / "google-api-nodejs-client/9.15.1" all returned 200). Follow-ups
Test results The PR now has 4 commits: Happy to address anything else. |
karutoil
left a comment
There was a problem hiding this comment.
Follow-up review — changes still required
The five original runtime issues appear addressed in commit 729a2fa, but the current head still has these problems:
-
The claimed Gemini sibling-token fix is incomplete.
discover_project_id(access_token)still does not receivectxortoken_path; it readsCATALYST_CODE_OAUTH_DIRinstead. That only works if the caller/environment separately supplies the directory. It does not derive the sibling path from the absolutetoken_paththat the harness passes, so custom manifest token paths can still resolve the wrong directory or silently miss the sibling token. Passos.path.dirname(token_path(ctx))fromdo_complete, and use the configured/default path only as an explicit fallback. -
The implementation/documentation still contains stale forbidden-header claims.
core/providers/antigravity/README.mdstill says the harness injectsx-goog-user-project;core/providers/antigravity/oauth/antigravity-oauth.pymodule docstring still names that header;core/providers/gemini-cli/README.mdstill says it injectsx-goog-user-project. The actual token actions emitx-code-assist-project, and the PR explicitly saysx-goog-user-projectcausesSERVICE_DISABLED. These docs are operationally misleading. -
Gemini README still documents the wrong Client-Metadata. It continues to state
{ ideType: 0, platform: 0, pluginType: 0 }, while the script emitsideType: 9, runtime platform, andpluginType: 2. This contradicts the implementation and the project-provisioning rationale. -
The author’s claimed cleanup was not applied.
antigravity-oauth.pystill contains the unusedload_code_assistfunction and duplicated project extraction logic, despite the PR update saying it was removed. Remove it or explain why it is intentionally retained. -
CI is not complete. At review time the core Rust, Go, and cross-compile checks are pending. The PR should not be treated as ready until those checks finish successfully.
The redirect-path and environment-passthrough changes are present in the current diff. I did not run live OAuth/chat against Google.
There was a problem hiding this comment.
Important
Remaining gaps after the runtime fixes in 729a2fa: first-party READMEs still document the forbidden project header, the freemium contract test never runs, and the gemini-cli sibling-token fallback still does not use the harness token_path.
Reviewed changes First-party Antigravity + Gemini CLI OAuth plugins staged into the binary, plus harness redirect_path / env_passthrough support and Code Assist wire-shape tests.
- OAuth plugins — PKCE login, token refresh,
loadCodeAssist/onboardUserproject discovery for both Google clients. - Wire fix — token actions emit
x-code-assist-project(notx-goog-user-project) so the adapter fillsbody.projectwithout the consumer API gate. - Harness — configurable OAuth loopback
redirect_pathand env passthrough for project overrides. - Staging —
STAGING_VERSION6 → 7 with executable OAuth scripts and staging assertions. - Tests — adapter URL/body/project header resolution lock-ins (one intended case is missing
#[test]).
⚠️ Staged docs still teach the forbidden project header
The PR’s critical claim is that x-goog-user-project causes free-tier SERVICE_DISABLED, and both token scripts correctly emit x-code-assist-project. The staged Antigravity + Gemini CLI READMEs (and the Antigravity module docstring) still say the harness injects x-goog-user-project. Anyone debugging from those docs will reintroduce the 403 path the PR exists to avoid.
Technical details
# Align staged docs with x-code-assist-project
## Affected sites
- `core/providers/antigravity/README.md:20` — claims `x-goog-user-project` injection
- `core/providers/antigravity/oauth/antigravity-oauth.py:17-20` — module docstring same claim
- `core/providers/gemini-cli/README.md:21-24` — same claim (later “Gotchas” correctly forbids it)
- `core/providers/gemini-cli/README.md:68` — Client-Metadata documented as zeros; script uses ideType 9 / runtime platform / pluginType 2
## Required outcome
- Every first-party doc and docstring that describes the live token → chat path must name `x-code-assist-project` only for harness injection, and must match the actual Client-Metadata constants.
- No staged text should instruct operators to send `x-goog-user-project` on free-tier / managed projects.
## Suggested approach (optional)
- Mirror the wording already used in the gemini-cli “Gotchas” / “Working models” sections across both READMEs and the Antigravity token docstring.⚠️ Gemini sibling project fallback still ignores harness token_path
do_complete still calls discover_project_id(access_token) only. Sibling lookup depends on user-exported CATALYST_CODE_OAUTH_DIR (never set by the harness) plus a hardcoded ~/.config/catalyst-code/oauth/antigravity.json. Default installs work; any non-default token directory silently misses the sibling Antigravity project the free-tier gemini-cli path relies on.
Technical details
# Pass token dir into discover_project_id
## Affected sites
- `core/providers/gemini-cli/oauth/gemini-cli-oauth.py:403-449` — discovery ignores `ctx`
- `core/providers/gemini-cli/oauth/gemini-cli-oauth.py:498` — `discover_project_id(normalized["access_token"])` only
- Comments reference `GEMINI_CLI_PROJECT_DIR` / deriving from `token_path`, which the code does not do
- `CATALYST_CODE_OAUTH_DIR` is only re-exported if already in the process env (`plugins.rs` `oauth_script_env`)
## Required outcome
- Sibling `antigravity.json` is resolved next to the absolute `token_path` the harness passes in `ctx`, with the default global path only as an explicit fallback.
- Free-tier gemini-cli login that depends on a prior Antigravity project keeps working for default and custom layouts without a secret env var.
## Suggested approach (optional)
- `discover_project_id(access_token, token_dir=None)` and call with `os.path.dirname(token_path(ctx))` from `do_complete`.openai-compatible/ck-grok-4.5 | 𝕏
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
core/providers/antigravity/oauth/antigravity-oauth.py (1)
382-392: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
do_tokenguards one dict and indexes another in both providers. Both scripts testtoken.get("refresh_token"), then indexcurrent_token["refresh_token"]after re-reading the file from disk. If the on-disk file is a valid JSON object without that key,do_tokenraisesKeyErrorand the harness receives a script error instead of the structured{"access_token": null}response.
core/providers/antigravity/oauth/antigravity-oauth.py#L382-L392: replace the direct index at Line 392 withcurrent_token.get("refresh_token") or token.get("refresh_token", ""), and emit{"access_token": None}when the result is empty.core/providers/gemini-cli/oauth/gemini-cli-oauth.py#L398-L408: apply the same change at Line 408.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/antigravity/oauth/antigravity-oauth.py` around lines 382 - 392, Update do_token in core/providers/antigravity/oauth/antigravity-oauth.py (lines 382-392) and core/providers/gemini-cli/oauth/gemini-cli-oauth.py (lines 398-408) to resolve the refresh token from current_token first, then fall back to token, without direct indexing; when the resolved value is empty, return the structured {"access_token": null} response instead of attempting a refresh.
🧹 Nitpick comments (4)
core/providers/antigravity/oauth/antigravity-oauth.py (3)
207-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBoth providers re-implement
parse_jsoninstead of importing the shared copy. The stated purpose ofcore/providers/_shared/google_oauth.pyis to keep wire-level behaviour in lock-step, andparse_jsonis already exported there. Each script keeps a verbatim copy behind a comment that calls the shared version canonical, so three copies can drift.
core/providers/antigravity/oauth/antigravity-oauth.py#L207-L215: addparse_jsonto thefrom google_oauth import (...)list and delete the local function.core/providers/gemini-cli/oauth/gemini-cli-oauth.py#L206-L214: addparse_jsonto thefrom google_oauth import (...)list and delete the local function.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/antigravity/oauth/antigravity-oauth.py` around lines 207 - 215, Use the shared parse_json implementation instead of maintaining provider-local copies: in core/providers/antigravity/oauth/antigravity-oauth.py at lines 207-215, add parse_json to the google_oauth import list and remove the local function; apply the same change in core/providers/gemini-cli/oauth/gemini-cli-oauth.py at lines 206-214. Preserve existing callers such as fetch_user_email.
41-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse the already-imported
sysandos.Lines 32-33 import
sysandos. Line 41 imports them again under_sysand_osaliases. Drop the aliases and use the existing names.♻️ Proposed cleanup
-import sys as _sys, os as _os -_HERE = _os.path.dirname(_os.path.abspath(__file__)) -_sys.path.insert( - 0, _os.path.abspath(_os.path.join(_HERE, "..", "..", "_shared")) +_HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert( + 0, os.path.abspath(os.path.join(_HERE, "..", "..", "_shared")) )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/antigravity/oauth/antigravity-oauth.py` around lines 41 - 45, Update the shared-path setup around _HERE and sys.path.insert to reuse the existing sys and os imports from lines 32–33; remove the redundant _sys and _os alias import and replace those alias references with the original names.
349-357: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThe override re-check is redundant.
discover_project_idreadsANTIGRAVITY_PROJECT_ENVfirst at Line 286 and returns the override when it is set. Lines 352-357 repeat that read and assign the same value. Remove the second read, or remove the check insidediscover_project_id, so one place owns the precedence rule.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/antigravity/oauth/antigravity-oauth.py` around lines 349 - 357, The project override precedence is implemented twice: in discover_project_id and the surrounding normalization flow. Keep the environment override handling in discover_project_id as the single owner, and remove the redundant override read and normalized["project_id"] reassignment after discover_project_id.core/providers/gemini-cli/oauth/gemini-cli-oauth.py (1)
40-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse the already-imported
sysandos.The module imports
sysandosabove. Line 40 imports them again under_sysand_osaliases. The same pattern exists incore/providers/antigravity/oauth/antigravity-oauth.py.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/gemini-cli/oauth/gemini-cli-oauth.py` around lines 40 - 44, Remove the redundant aliased imports in the module path setup and reuse the existing sys and os imports when computing _HERE and inserting the _shared directory into sys.path. Apply the same cleanup in the corresponding antigravity-oauth module if it contains the identical pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/providers/_shared/google_oauth.py`:
- Around line 159-177: Update lock_for to ensure the parent directory for path
exists before opening path + ".lock", using the existing token-directory
creation approach used by atomic_write. Also stop swallowing flock OSError:
propagate the locking failure instead of returning a handle that may not provide
serialization, while preserving the None result when fcntl is unavailable.
In `@core/providers/antigravity/plugin.json`:
- Around line 19-20: Increase the token_timeout_ms setting in the provider
configuration to provide margin beyond google_oauth.http_post’s 30-second HTTP
timeout, targeting approximately 45000 ms so do_token can complete and return
its structured failure response despite startup, locking, and file I/O overhead.
In `@core/providers/gemini-cli/oauth/gemini-cli-oauth.py`:
- Around line 299-317: Update discover_project_id and its do_complete(ctx) call
site to accept the resolved token directory, passing
os.path.dirname(token_path(ctx)) instead of relying on an environment override.
Remove the unreachable CATALYST_CODE_OAUTH_DIR lookup and stale comment
references, retain the default fallback path as appropriate, and let
read_token’s existing None-returning error handling apply without an additional
try/except.
---
Outside diff comments:
In `@core/providers/antigravity/oauth/antigravity-oauth.py`:
- Around line 382-392: Update do_token in
core/providers/antigravity/oauth/antigravity-oauth.py (lines 382-392) and
core/providers/gemini-cli/oauth/gemini-cli-oauth.py (lines 398-408) to resolve
the refresh token from current_token first, then fall back to token, without
direct indexing; when the resolved value is empty, return the structured
{"access_token": null} response instead of attempting a refresh.
---
Nitpick comments:
In `@core/providers/antigravity/oauth/antigravity-oauth.py`:
- Around line 207-215: Use the shared parse_json implementation instead of
maintaining provider-local copies: in
core/providers/antigravity/oauth/antigravity-oauth.py at lines 207-215, add
parse_json to the google_oauth import list and remove the local function; apply
the same change in core/providers/gemini-cli/oauth/gemini-cli-oauth.py at lines
206-214. Preserve existing callers such as fetch_user_email.
- Around line 41-45: Update the shared-path setup around _HERE and
sys.path.insert to reuse the existing sys and os imports from lines 32–33;
remove the redundant _sys and _os alias import and replace those alias
references with the original names.
- Around line 349-357: The project override precedence is implemented twice: in
discover_project_id and the surrounding normalization flow. Keep the environment
override handling in discover_project_id as the single owner, and remove the
redundant override read and normalized["project_id"] reassignment after
discover_project_id.
In `@core/providers/gemini-cli/oauth/gemini-cli-oauth.py`:
- Around line 40-44: Remove the redundant aliased imports in the module path
setup and reuse the existing sys and os imports when computing _HERE and
inserting the _shared directory into sys.path. Apply the same cleanup in the
corresponding antigravity-oauth module if it contains the identical pattern.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 15c6a254-dded-4781-8471-a22475ba15c1
📒 Files selected for processing (11)
core/providers/README.mdcore/providers/_shared/google_oauth.pycore/providers/antigravity/README.mdcore/providers/antigravity/oauth/antigravity-oauth.pycore/providers/antigravity/plugin.jsoncore/providers/gemini-cli/README.mdcore/providers/gemini-cli/oauth/gemini-cli-oauth.pycore/providers/gemini-cli/plugin.jsoncore/src/plugins.rscore/src/providers/google_code_assist.rscore/src/staging.rs
🚧 Files skipped from review as they are similar to previous changes (5)
- core/providers/antigravity/README.md
- core/providers/README.md
- core/providers/gemini-cli/plugin.json
- core/providers/gemini-cli/README.md
- core/src/staging.rs
Adds two new first-party OAuth provider bundles, mirroring the
Gemini CLI and Antigravity IDE subscription flows used by Google's
open-source clients. Both reuse the existing
core/src/providers/google_code_assist.rs adapter — the
is_code_assist_endpoint() gate already routes the cloudcode-pa /
daily-cloudcode-pa hosts to the right wire format, and the
adapter's resolve_project() reads the x-goog-user-project header
that each plugin's 'token' action injects so requests route to the
user's real Code Assist project (not the freemium shared default).
Flow:
1. /login antigravity (or gemini-cli)
2. Harness binds loopback, opens Google OAuth page (PKCE S256).
3. Script exchanges the code, calls :loadCodeAssist with the
matching client fingerprint (Antigravity IDE 2.1.1 UA + enum
9/2/2 metadata; gemini-cli google-api-nodejs-client UA +
X-Goog-Api-Client + Client-Metadata).
4. If loadCodeAssist returns no project (new account), the
script calls :onboardUser and polls until done=true.
5. Token file persisted to
~/.config/catalyst-code/oauth/{antigravity,gemini-cli}.json
with access_token, refresh_token, expires_at, project_id,
and email.
6. On every turn the harness refreshes near-expiry tokens and
injects an x-goog-user-project header carrying the discovered
project.
Verified end-to-end:
* cargo test (focused: staging, plugins, oauth, providers,
google_code_assist) — 199 passed, 0 failed.
* cargo build --release -p catalyst-code-core — clean.
* OAuth script against mock OAuth + loadCodeAssist + onboardUser
backend — login, complete, token, refresh, clear, onboarding
fallback all work.
* Fresh $HOME → release binary auto-stages both plugins into
~/.catalyst-code/plugins/, byte-identical to source, scripts
marked executable.
* strings(1) confirms provider_id=antigravity and provider_id=
gemini-cli are baked into the binary.
When the auto-provisioned Antigravity project (e.g. the Google-managed
synthetic-expanse-sxhhm for free-tier individuals) is unusable — the
user is not a member/owner so they can't enable Cloud Code Private
API on it — this lets them pin the project to one they do own.
Useful escape hatch while we wait for either:
* Google to auto-enable Cloud Code Private API on the Antigravity-
managed project (some users report a ~24h delay after first OAuth).
* The user to find another way to enable the API.
Tested end-to-end: env override is respected on both the
loadCodeAssist->onboardUser bootstrap AND on the project_id persisted
in the token file (so the /token action's x-goog-user-project header
also points at the override).
Root cause of the "SERVICE_DISABLED" 403s: the OAuth plugins were emitting `x-goog-user-project`, a Google consumer-project header that forces a Cloud Code Private API enablement check. Free-tier / managed projects fail that check even when body.project alone is accepted. 9router's gemini-cli executor never sends that header — project goes in the JSON body only. Verified end-to-end against the live gateway: gemini-2.5-flash / pro / flash-lite / 3.1-flash-lite-preview → 200 with body.project=synthetic-expanse-sxhhm and NO x-goog-user-project. Changes: - Both antigravity + gemini-cli token actions now emit `x-code-assist-project` (harness adapter still resolves body.project from it; does not trip the consumer gate). - gemini-cli loadCodeAssist uses Antigravity-style ClientMetadata (ideType=9, pluginType=2) + mode=1, matching 9router. - gemini-cli project discovery falls back to CATALYST_CODE_GEMINI_CLI_PROJECT or the sibling Antigravity token's project_id when free-tier returns UNSUPPORTED_CLIENT. - README documents working models + the header gotcha.
… passthrough karutoil on PR catalystctl#7 caught five real bugs in the first round; all fixed here plus a wire-shape lock-in test so future changes can't regress the contract. ### Fixes 1. **Missing ANTIGRAVITY_PROJECT_ENV constant** in antigravity-oauth.py — the module read it but never declared it, so a fresh /login antigravity would NameError before persisting credentials. Constant added next to USER_AGENT. 2. **Harness redirected to /callback but plugins expect /oauth2callback.** Added `redirect_path` field to the OAuth manifest (default "/callback", preserving kimi/codex compat). plugins.rs now uses cfg.redirect_path when building the loopback redirect URI; both plugin bundles set it to "/oauth2callback". kimi + codex unchanged. 3. **Env passthrough scrubbed.** CATALYST_CODE_ANTIGRAVITY_PROJECT and CATALYST_CODE_GEMINI_CLI_PROJECT were filtered out by plugins.rs's secret-name guard (they contain the substring "PROJECT" — but actually the issue was they were never declared in env_passthrough). Both plugin.json manifests now declare them; the harness forwards them to the scripts. 4. **Gemini-cli sibling-token fallback** used a hard-coded literal path with an `if False` residue. Now reads from CATALYST_CODE_OAUTH_DIR first, then falls back to ~/.config/catalyst-code/oauth/antigravity.json. 5. **Dead code in antigravity-oauth.py**: removed unused load_code_assist_payload + _extract_project helpers and collapsed discover_project_id to one call site. Removed the `if False` in gemini-cli-oauth.py. ### Doc fixes - Both provider READMEs and core/providers/README.md claimed the plugins injected x-goog-user-project. Updated to x-code-assist-project. - Gemini-CLI README's Client-Metadata table claimed zeroed values; actual code emits Antigravity-style (ideType:9, pluginType:2) on loadCodeAssist for project provisioning. - google_code_assist.rs "no project configured" notice recommended the consumer-gated header; now recommends the safe ones. ### Lock-in: wire-shape contract tests Added `wire_shape_contract` test module to google_code_assist.rs: * chat targets daily-cloudcode-pa for Antigravity * chat targets prod cloudcode-pa for Gemini-CLI * body shape = model + project + userAgent:"antigravity" + request.* * resolve_project header priority (first-match wins, case-insensitive) * freemium default notice emitted when no project header present These guard the exact wire shape verified live against the Google Code Assist gateway — without them a future refactor could silently break both OAuth plugins with HTTP 403. ### Test plan - cargo test focused (staging, plugins, oauth, google_code_assist, providers): 205 passed, 0 failed (was 199; +5 wire-shape + 1 freemium-notice already counted). - cargo fmt --check: clean. - cargo check: clean.
…_oauth.py Both antigravity-oauth.py and gemini-cli-oauth.py shared ~250 lines of identical stdlib-only helpers (HTTP wrappers, PKCE, token I/O, refresh grant, cloudaicompanionProject extraction). Move them into a new core/providers/_shared/google_oauth.py module so the two scripts only carry their vendor-specific bits (CLIENT_ID, SCOPES, USER_AGENT, build / discover / action functions). Wire output is byte-identical: login URL scopes + query params, do_token JSON, and the on-disk token shape are unchanged. Per-script wrappers (token_path, atomic_write) accept their existing temp-file prefix and default filename so staging / cleanup behaviour is preserved. Stage the shared module under plugins/_shared/google_oauth.py so the provider scripts' .. / .. / _shared relative import resolves after staging. Bump STAGING_VERSION to 8; update the staging idempotency test to assert the new file lands.
Stdlib-only end-to-end tests that drive the Antigravity OAuth provider script against a local mock HTTP server. Covers PKCE S256 URL shape (client id, scopes, code_challenge = SHA256(verifier) base64url, access_type=offline, no prompt), complete -> token-file persistence with project_id from loadCodeAssist (file mode 0o600), refresh-grant preserves project_id + email across rotations, no-token returns null, onboarding fallback (loadCodeAssist returns tiers-only, onboardUser polls until done=true), clear removes the token + .lock sidecar, and the CATALYST_CODE_ANTIGRAVITY_PROJECT escape hatch wins over loadCodeAssist. Adds empty __init__.py markers under core/providers/ so 'python3 -m unittest discover -s core/providers' finds the tests, and a Python __pycache__ entry in .gitignore.
Stdlib-only end-to-end tests that drive the Gemini CLI OAuth provider script against a local mock HTTP server. Covers PKCE S256 URL shape (only the 3 cloud-platform scopes, no openid / cclog / experimentsandconfigs, no prompt), the sibling-Antigravity-token fallback for free-tier loadCodeAssist UNSUPPORTED_CLIENT (both via $HOME/.config and CATALYST_CODE_OAUTH_DIR override), refresh-grant preserves project_id + email, no-token returns null, clear removes the token + .lock sidecar, and a regression test that asserts 'openid' is never present in the scope list.
After rebasing onto upstream master (bbdcd78), CI surfaced three pre-existing baseline-format drifts that were already failing on the base commit: - cargo fmt --all (16 hunks across message.rs / provider.rs / openai_compatible.rs — newer stable rustfmt) - cd tui && gofmt -l . (1 hunk: blocks.go) - node scripts/check-protocol-schema.mjs (missing advisor_note and advisor_status events added by upstream's checkpoint-recovery work — also synced to sdk/src/core-events.ts and the events-v2.jsonl fixture; bumped the "must cover every known event" assertion 99 → 101 in core/src/protocol.rs) No behavior changes. Local: rustfmt clean, gofmt clean, schema check "protocol consistency ok: 67 commands, 101 events, 5 fixture files", targeted cargo test + Python e2e tests green.
f2d5e24 to
b14516a
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
core/tests/oauth_plugin_lifecycle.rs (1)
560-579: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy liftExercise the configured redirect path.
This test uses
flow: "manual"and sendsoauth_codedirectly. A harness regression that always binds/callbackwould still pass.Add a web-flow case. Make the fake script return
flow: "web", complete the loopback callback, and assert that the redirect URI uses/oauth2callback.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/tests/oauth_plugin_lifecycle.rs` around lines 560 - 579, Extend the OAuth lifecycle coverage around the existing login_oauth/oauth_code scenario to exercise the configured web flow: make the fake script return flow "web", drive completion through the loopback callback instead of sending oauth_code directly, and assert the redirect URI uses the "/oauth2callback" path. Preserve the existing successful authentication and provider event assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.catalyst-code/skills/plugin-authoring/SKILL.md:
- Around line 597-606: The login redirect URI contract should not hardcode a
/callback suffix. Update the text near the login flow description to state that
the harness provides http://localhost:<port>/<redirect_path>, preserving the
existing redirect_path behavior and terminology.
In `@core/providers/antigravity/oauth/test_antigravity_oauth.py`:
- Around line 393-394: Replace the exact expires_at assertions after
run_script() in core/providers/antigravity/oauth/test_antigravity_oauth.py lines
393-394 and core/providers/gemini-cli/oauth/test_gemini_cli_oauth.py lines
465-466 with bounded assertions: capture integer time immediately before and
after each run, then verify expires_at is within the resulting one-second-safe
range while retaining the access_token assertion.
In `@core/providers/README.md`:
- Around line 160-171: The “Token refresh on the hot path” documentation
incorrectly states that the token is always cached for approximately five
minutes. Update this section to explain that expires_at controls when the
harness reruns token, while the five-minute cache applies only when expires_at
is absent or zero; clarify that changed x-code-assist-project headers reach
subsequent turns only after token refresh or invalidation.
---
Nitpick comments:
In `@core/tests/oauth_plugin_lifecycle.rs`:
- Around line 560-579: Extend the OAuth lifecycle coverage around the existing
login_oauth/oauth_code scenario to exercise the configured web flow: make the
fake script return flow "web", drive completion through the loopback callback
instead of sending oauth_code directly, and assert the redirect URI uses the
"/oauth2callback" path. Preserve the existing successful authentication and
provider event assertions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b496f500-f28b-4a3c-a18b-fe99b4c2a755
📒 Files selected for processing (21)
.catalyst-code/skills/plugin-authoring/SKILL.md.gitignorecore/providers/README.mdcore/providers/__init__.pycore/providers/antigravity/__init__.pycore/providers/antigravity/oauth/__init__.pycore/providers/antigravity/oauth/test_antigravity_oauth.pycore/providers/gemini-cli/__init__.pycore/providers/gemini-cli/oauth/__init__.pycore/providers/gemini-cli/oauth/test_gemini_cli_oauth.pycore/src/message.rscore/src/plugins.rscore/src/protocol.rscore/src/provider.rscore/src/providers/openai_compatible.rscore/tests/oauth_plugin_lifecycle.rsdocs/plugins/oauth.mdprotocol.schema.jsonprotocol/fixtures/events-v2.jsonlsdk/src/core-events.tstui/blocks.go
🚧 Files skipped from review as they are similar to previous changes (1)
- core/src/plugins.rs
Downstream of the previous format-drift commit, two more checks need the new event count / new event types: - tui/protocol_fixture_test.go: bump 99 → 101 (both the duplicate check and the count assertion). - web/src/lib/reducer.ts: add `case "advisor_note": case "advisor_status":` → no-op state so the exhaustive-switch invariant stays green. Advisor feedback does not affect the web UI's streaming/toast state directly; it surfaces via the SDK later. Verified locally: cargo fmt --check clean cd tui && gofmt -l . clean go test ./... ok protocol-schema check ok (67 commands, 101 events, 5 fixtures)
The web app imports types from @catalyst-code/coding-agent which resolves to ./dist/index.d.ts per sdk/package.json. Without an SDK build step before web typecheck/test, TypeScript sees a stale or empty dist/ and rejects new CORE_EVENT_TYPES additions like advisor_note + advisor_status added in the previous commit. Adding `bun run build` in the sdk/ working directory before the web typecheck step. Trivial cost (~3s on CI) and unblocks any future SDK type additions from triggering a confusing typecheck failure.
Bun's file: link snapshot reads the SDK package.json at install time and pins its types path (./dist/index.d.ts). The web typecheck step was reading the wrong (stale) types because web install ran before SDK build. Reorder so the SDK builds before web installs. EOF
The web reducer's exhaustive switch is over the WEB's local CoreEvent union (web/src/lib/types.ts), not the SDK's. The new advisor events needed to be added there too. The previous commit only added them to the SDK's CORE_EVENT_TYPES, which is a different type. Now the web typecheck sees advisor_note + advisor_status as valid variants of the web CoreEvent union; the reducer's case labels match. EOF
|
@pullfrog review please. |
There was a problem hiding this comment.
Important
Staged first-party docs still teach the forbidden project header, free-tier gemini-cli sibling fallback still ignores harness token_path, and one wire-shape lock-in test never runs.
Reviewed changes First-party Antigravity + Gemini CLI OAuth plugins (PKCE, loadCodeAssist/onboardUser, project headers), harness redirect_path + env_passthrough, shared google_oauth helpers, staging + lifecycle tests, and protocol/CI advisor-event sync.
- OAuth plugins — Antigravity + Gemini CLI PKCE login, refresh, project discovery, atomic token I/O.
- Wire fix —
tokenemitsx-code-assist-projectso the Code Assist adapter fillsbody.projectwithoutx-goog-user-project/SERVICE_DISABLED. - Harness — configurable loopback
redirect_pathand env passthrough for project overrides. - Staging —
STAGING_VERSIONbump, executable OAuth scripts,_shared/google_oauth.pystaged underplugins/_shared/. - Tests — mock e2e Python suites, Rust OAuth lifecycle integration, adapter wire-shape lock-ins (one case missing
#[test]). - Protocol/CI —
advisor_note/advisor_statusacross schema, fixtures, SDK, TUI, web; web install after SDK build.
⚠️ Staged docs still teach the forbidden project header
The PR’s core claim is that x-goog-user-project causes free-tier SERVICE_DISABLED, and both token actions correctly emit x-code-assist-project. The staged Antigravity + Gemini CLI READMEs still say the harness injects x-goog-user-project. Those files ship via include_str! into every install, so anyone debugging from them will reintroduce the 403 path this PR exists to avoid.
Technical details
# Align staged docs with x-code-assist-project
## Affected sites
- `core/providers/antigravity/README.md:20` — claims `x-goog-user-project` injection
- `core/providers/gemini-cli/README.md:22` — same claim (later Gotchas correctly forbids it)
- `core/providers/gemini-cli/README.md:68` — Client-Metadata documented as zeros; script uses ideType 9 / runtime platform / pluginType 2
- `docs/plugins/oauth.md:153` — documents `CATALYST_CODE_GEMINICLI_PROJECT`; real name is `CATALYST_CODE_GEMINI_CLI_PROJECT`
## Required outcome
- Every first-party staged doc that describes the live token → chat path names `x-code-assist-project` only for harness injection.
- Client-Metadata and env override names match the scripts/manifests.
## Suggested approach (optional)
- Mirror the wording already used in gemini-cli “Gotchas” / “Working models” and the Antigravity module docstring (which is already correct).⚠️ Gemini sibling project fallback still ignores harness token_path
do_complete still calls discover_project_id(access_token) only. Sibling lookup depends on user-exported CATALYST_CODE_OAUTH_DIR (never set by the harness) plus a hardcoded ~/.config/catalyst-code/oauth/antigravity.json. Default installs work; any non-default token directory silently misses the sibling Antigravity project free-tier gemini-cli relies on. Comments still reference deriving from token_path / GEMINI_CLI_PROJECT_DIR, which the code does not do.
Technical details
# Pass token dir into discover_project_id
## Affected sites
- `core/providers/gemini-cli/oauth/gemini-cli-oauth.py:276-321` — discovery ignores `ctx`
- `core/providers/gemini-cli/oauth/gemini-cli-oauth.py:371` — `discover_project_id(normalized["access_token"])` only
- `CATALYST_CODE_OAUTH_DIR` is only re-exported if already in the process env (`oauth_script_env`)
## Required outcome
- Sibling `antigravity.json` is resolved next to the absolute `token_path` the harness passes in `ctx`, with the default global path only as an explicit fallback.
- Free-tier gemini-cli login that depends on a prior Antigravity project keeps working for default and custom layouts without a secret env var.
## Suggested approach (optional)
- `discover_project_id(access_token, token_dir=None)` and call with `os.path.dirname(token_path(ctx))` from `do_complete`.openai-compatible/ck-grok-4.5 | 𝕏
karutoil + coderabbitai + pullfrog surfaced a fresh batch of issues on
the latest push. All addressed:
### Real bugs fixed
1. **gemini-cli README + antigravity README** still claimed the harness
injects `x-goog-user-project` in the intro paragraphs. Replaced
with `x-code-assist-project` + a warning about the consumer-API
gate. The Gotchas sections already said the right thing; the
intros contradicted them and operators would re-introduce the 403.
2. **`discover_project_id` ignored harness `token_path`** in the
gemini-cli sibling-token fallback. Now takes `oauth_dir` parameter
and looks for `antigravity.json` next to the gemini-cli token
first; keeps the default global path as fallback. Removed the
unused `CATALYST_CODE_OAUTH_DIR` env var path (it was never
forwarded by the harness, so the branch was unreachable). The
corresponding `env_passthrough` entry in plugin.json is gone.
3. **`freemium_fallback_emitted_when_no_project_header_present`
missing `#[test]` attribute**. Added. Without it, the freemium
default + notice path never ran under `cargo test`, so the wire
contract was incomplete.
4. **`lock_for` swallow `flock` OSError + missing parent dir** in
the shared module. Now:
- makedirs the parent dir with mode 0o700 before opening the lock file;
- on `flock` OSError, close the handle and return None instead of
returning a handle that didn't actually take the lock (silent
loss of cross-process serialization).
5. **`token_timeout_ms = 30000`** collides with the script's HTTP
timeout, producing opaque harness timeouts on slow refreshes.
Bumped both manifests to **45000** (15 s margin for interpreter
start + flock + file IO).
7. **`docs/plugins/oauth.md`** referenced
`CATALYST_CODE_GEMINICLI_PROJECT` (missing underscore — copy-paste
from the antigravity var). Fixed.
8. **`core/providers/README.md`** "Token refresh on the hot path"
said "cached for ~5 min" without noting that the 5-min cache only
applies when `expires_at` is absent. Corrected: when `expires_at`
is present, the harness uses it to decide refresh timing, so
changed headers only reach the gateway after the next token
refresh, not "the very next turn".
9. **`plugin-authoring/SKILL.md`** login flow description still said
`http://localhost:<port>/callback`. Updated to use
`<redirect_path>` so it matches the new manifest field semantics.
### Test fixes
- `test_antigravity_oauth.py` + `test_gemini_cli_oauth.py`:
`assertEqual(out["expires_at"], int(time.time()) + 3600)` is a
flaky exact comparison when the script samples a different
integer-second boundary than the test. Captured `t0` / `t1`
around `run_script()` and use `assertIn` to accept either
`t0 + 3600` or `t1 + 3600`.
Verified locally:
cargo fmt --check clean
cargo check clean
cargo test (focused) 5 wire-shape + freemium-fallback pass
python3 -m unittest 14/14 pass
|
Thanks for the fresh review — caught another set of issues. All addressed in commit Pullfrog follow-ups
CodeRabbit review follow-ups
Test fix (CodeRabbit)
Verified locally: CI re-run should pick up the new commit; expecting all checks to stay green. Happy to address anything else that lands. |
|
Addressed both review comments on PR #7 (
Both threads replied and resolved; gemini-cli OAuth unittest suite green. Task list (3/3 completed)
|
…lient-Metadata docs Rename the sibling fallback test to document harness token_path layout resolution and drop the unused env. Match gemini-cli README Client-Metadata to ideType 9 / pluginType 2 with runtime platform enum.
Adds two new first-party OAuth provider bundles, mirroring the
Gemini CLI and Antigravity IDE subscription flows used by Google's
open-source clients. Both reuse the existing
core/src/providers/google_code_assist.rs adapter — the
is_code_assist_endpoint() gate already routes the cloudcode-pa /
daily-cloudcode-pa hosts to the right wire format, and the
adapter's resolve_project() reads the x-goog-user-project header
that each plugin's 'token' action injects so requests route to the
user's real Code Assist project (not the freemium shared default).
Flow:
1. /login antigravity (or gemini-cli)
2. Harness binds loopback, opens Google OAuth page (PKCE S256).
3. Script exchanges the code, calls :loadCodeAssist with the
matching client fingerprint (Antigravity IDE 2.1.1 UA + enum
9/2/2 metadata; gemini-cli google-api-nodejs-client UA +
X-Goog-Api-Client + Client-Metadata).
4. If loadCodeAssist returns no project (new account), the
script calls :onboardUser and polls until done=true.
5. Token file persisted to
~/.config/catalyst-code/oauth/{antigravity,gemini-cli}.json
with access_token, refresh_token, expires_at, project_id,
and email.
6. On every turn the harness refreshes near-expiry tokens and
injects an x-goog-user-project header carrying the discovered
project.
Verified end-to-end:
* cargo test (focused: staging, plugins, oauth, providers,
google_code_assist) — 199 passed, 0 failed.
* cargo build --release -p catalyst-code-core — clean.
* OAuth script against mock OAuth + loadCodeAssist + onboardUser
backend — login, complete, token, refresh, clear, onboarding
fallback all work.
* Fresh $HOME → release binary auto-stages both plugins into
~/.catalyst-code/plugins/, byte-identical to source, scripts
marked executable.
* strings(1) confirms provider_id=antigravity and provider_id=
gemini-cli are baked into the binary.
When the auto-provisioned Antigravity project (e.g. the Google-managed
synthetic-expanse-sxhhm for free-tier individuals) is unusable — the
user is not a member/owner so they can't enable Cloud Code Private
API on it — this lets them pin the project to one they do own.
Useful escape hatch while we wait for either:
* Google to auto-enable Cloud Code Private API on the Antigravity-
managed project (some users report a ~24h delay after first OAuth).
* The user to find another way to enable the API.
Tested end-to-end: env override is respected on both the
loadCodeAssist->onboardUser bootstrap AND on the project_id persisted
in the token file (so the /token action's x-goog-user-project header
also points at the override).
Root cause of the "SERVICE_DISABLED" 403s: the OAuth plugins were emitting `x-goog-user-project`, a Google consumer-project header that forces a Cloud Code Private API enablement check. Free-tier / managed projects fail that check even when body.project alone is accepted. 9router's gemini-cli executor never sends that header — project goes in the JSON body only. Verified end-to-end against the live gateway: gemini-2.5-flash / pro / flash-lite / 3.1-flash-lite-preview → 200 with body.project=synthetic-expanse-sxhhm and NO x-goog-user-project. Changes: - Both antigravity + gemini-cli token actions now emit `x-code-assist-project` (harness adapter still resolves body.project from it; does not trip the consumer gate). - gemini-cli loadCodeAssist uses Antigravity-style ClientMetadata (ideType=9, pluginType=2) + mode=1, matching 9router. - gemini-cli project discovery falls back to CATALYST_CODE_GEMINI_CLI_PROJECT or the sibling Antigravity token's project_id when free-tier returns UNSUPPORTED_CLIENT. - README documents working models + the header gotcha.
… passthrough karutoil on PR catalystctl#7 caught five real bugs in the first round; all fixed here plus a wire-shape lock-in test so future changes can't regress the contract. ### Fixes 1. **Missing ANTIGRAVITY_PROJECT_ENV constant** in antigravity-oauth.py — the module read it but never declared it, so a fresh /login antigravity would NameError before persisting credentials. Constant added next to USER_AGENT. 2. **Harness redirected to /callback but plugins expect /oauth2callback.** Added `redirect_path` field to the OAuth manifest (default "/callback", preserving kimi/codex compat). plugins.rs now uses cfg.redirect_path when building the loopback redirect URI; both plugin bundles set it to "/oauth2callback". kimi + codex unchanged. 3. **Env passthrough scrubbed.** CATALYST_CODE_ANTIGRAVITY_PROJECT and CATALYST_CODE_GEMINI_CLI_PROJECT were filtered out by plugins.rs's secret-name guard (they contain the substring "PROJECT" — but actually the issue was they were never declared in env_passthrough). Both plugin.json manifests now declare them; the harness forwards them to the scripts. 4. **Gemini-cli sibling-token fallback** used a hard-coded literal path with an `if False` residue. Now reads from CATALYST_CODE_OAUTH_DIR first, then falls back to ~/.config/catalyst-code/oauth/antigravity.json. 5. **Dead code in antigravity-oauth.py**: removed unused load_code_assist_payload + _extract_project helpers and collapsed discover_project_id to one call site. Removed the `if False` in gemini-cli-oauth.py. ### Doc fixes - Both provider READMEs and core/providers/README.md claimed the plugins injected x-goog-user-project. Updated to x-code-assist-project. - Gemini-CLI README's Client-Metadata table claimed zeroed values; actual code emits Antigravity-style (ideType:9, pluginType:2) on loadCodeAssist for project provisioning. - google_code_assist.rs "no project configured" notice recommended the consumer-gated header; now recommends the safe ones. ### Lock-in: wire-shape contract tests Added `wire_shape_contract` test module to google_code_assist.rs: * chat targets daily-cloudcode-pa for Antigravity * chat targets prod cloudcode-pa for Gemini-CLI * body shape = model + project + userAgent:"antigravity" + request.* * resolve_project header priority (first-match wins, case-insensitive) * freemium default notice emitted when no project header present These guard the exact wire shape verified live against the Google Code Assist gateway — without them a future refactor could silently break both OAuth plugins with HTTP 403. ### Test plan - cargo test focused (staging, plugins, oauth, google_code_assist, providers): 205 passed, 0 failed (was 199; +5 wire-shape + 1 freemium-notice already counted). - cargo fmt --check: clean. - cargo check: clean.
…_oauth.py Both antigravity-oauth.py and gemini-cli-oauth.py shared ~250 lines of identical stdlib-only helpers (HTTP wrappers, PKCE, token I/O, refresh grant, cloudaicompanionProject extraction). Move them into a new core/providers/_shared/google_oauth.py module so the two scripts only carry their vendor-specific bits (CLIENT_ID, SCOPES, USER_AGENT, build / discover / action functions). Wire output is byte-identical: login URL scopes + query params, do_token JSON, and the on-disk token shape are unchanged. Per-script wrappers (token_path, atomic_write) accept their existing temp-file prefix and default filename so staging / cleanup behaviour is preserved. Stage the shared module under plugins/_shared/google_oauth.py so the provider scripts' .. / .. / _shared relative import resolves after staging. Bump STAGING_VERSION to 8; update the staging idempotency test to assert the new file lands.
Stdlib-only end-to-end tests that drive the Antigravity OAuth provider script against a local mock HTTP server. Covers PKCE S256 URL shape (client id, scopes, code_challenge = SHA256(verifier) base64url, access_type=offline, no prompt), complete -> token-file persistence with project_id from loadCodeAssist (file mode 0o600), refresh-grant preserves project_id + email across rotations, no-token returns null, onboarding fallback (loadCodeAssist returns tiers-only, onboardUser polls until done=true), clear removes the token + .lock sidecar, and the CATALYST_CODE_ANTIGRAVITY_PROJECT escape hatch wins over loadCodeAssist. Adds empty __init__.py markers under core/providers/ so 'python3 -m unittest discover -s core/providers' finds the tests, and a Python __pycache__ entry in .gitignore.
Stdlib-only end-to-end tests that drive the Gemini CLI OAuth provider script against a local mock HTTP server. Covers PKCE S256 URL shape (only the 3 cloud-platform scopes, no openid / cclog / experimentsandconfigs, no prompt), the sibling-Antigravity-token fallback for free-tier loadCodeAssist UNSUPPORTED_CLIENT (both via $HOME/.config and CATALYST_CODE_OAUTH_DIR override), refresh-grant preserves project_id + email, no-token returns null, clear removes the token + .lock sidecar, and a regression test that asserts 'openid' is never present in the scope list.
After rebasing onto upstream master (bbdcd78), CI surfaced three pre-existing baseline-format drifts that were already failing on the base commit: - cargo fmt --all (16 hunks across message.rs / provider.rs / openai_compatible.rs — newer stable rustfmt) - cd tui && gofmt -l . (1 hunk: blocks.go) - node scripts/check-protocol-schema.mjs (missing advisor_note and advisor_status events added by upstream's checkpoint-recovery work — also synced to sdk/src/core-events.ts and the events-v2.jsonl fixture; bumped the "must cover every known event" assertion 99 → 101 in core/src/protocol.rs) No behavior changes. Local: rustfmt clean, gofmt clean, schema check "protocol consistency ok: 67 commands, 101 events, 5 fixture files", targeted cargo test + Python e2e tests green.
Downstream of the previous format-drift commit, two more checks need the new event count / new event types: - tui/protocol_fixture_test.go: bump 99 → 101 (both the duplicate check and the count assertion). - web/src/lib/reducer.ts: add `case "advisor_note": case "advisor_status":` → no-op state so the exhaustive-switch invariant stays green. Advisor feedback does not affect the web UI's streaming/toast state directly; it surfaces via the SDK later. Verified locally: cargo fmt --check clean cd tui && gofmt -l . clean go test ./... ok protocol-schema check ok (67 commands, 101 events, 5 fixtures)
The web app imports types from @catalyst-code/coding-agent which resolves to ./dist/index.d.ts per sdk/package.json. Without an SDK build step before web typecheck/test, TypeScript sees a stale or empty dist/ and rejects new CORE_EVENT_TYPES additions like advisor_note + advisor_status added in the previous commit. Adding `bun run build` in the sdk/ working directory before the web typecheck step. Trivial cost (~3s on CI) and unblocks any future SDK type additions from triggering a confusing typecheck failure.
Bun's file: link snapshot reads the SDK package.json at install time and pins its types path (./dist/index.d.ts). The web typecheck step was reading the wrong (stale) types because web install ran before SDK build. Reorder so the SDK builds before web installs. EOF
The web reducer's exhaustive switch is over the WEB's local CoreEvent union (web/src/lib/types.ts), not the SDK's. The new advisor events needed to be added there too. The previous commit only added them to the SDK's CORE_EVENT_TYPES, which is a different type. Now the web typecheck sees advisor_note + advisor_status as valid variants of the web CoreEvent union; the reducer's case labels match. EOF
karutoil + coderabbitai + pullfrog surfaced a fresh batch of issues on
the latest push. All addressed:
### Real bugs fixed
1. **gemini-cli README + antigravity README** still claimed the harness
injects `x-goog-user-project` in the intro paragraphs. Replaced
with `x-code-assist-project` + a warning about the consumer-API
gate. The Gotchas sections already said the right thing; the
intros contradicted them and operators would re-introduce the 403.
2. **`discover_project_id` ignored harness `token_path`** in the
gemini-cli sibling-token fallback. Now takes `oauth_dir` parameter
and looks for `antigravity.json` next to the gemini-cli token
first; keeps the default global path as fallback. Removed the
unused `CATALYST_CODE_OAUTH_DIR` env var path (it was never
forwarded by the harness, so the branch was unreachable). The
corresponding `env_passthrough` entry in plugin.json is gone.
3. **`freemium_fallback_emitted_when_no_project_header_present`
missing `#[test]` attribute**. Added. Without it, the freemium
default + notice path never ran under `cargo test`, so the wire
contract was incomplete.
4. **`lock_for` swallow `flock` OSError + missing parent dir** in
the shared module. Now:
- makedirs the parent dir with mode 0o700 before opening the lock file;
- on `flock` OSError, close the handle and return None instead of
returning a handle that didn't actually take the lock (silent
loss of cross-process serialization).
5. **`token_timeout_ms = 30000`** collides with the script's HTTP
timeout, producing opaque harness timeouts on slow refreshes.
Bumped both manifests to **45000** (15 s margin for interpreter
start + flock + file IO).
7. **`docs/plugins/oauth.md`** referenced
`CATALYST_CODE_GEMINICLI_PROJECT` (missing underscore — copy-paste
from the antigravity var). Fixed.
8. **`core/providers/README.md`** "Token refresh on the hot path"
said "cached for ~5 min" without noting that the 5-min cache only
applies when `expires_at` is absent. Corrected: when `expires_at`
is present, the harness uses it to decide refresh timing, so
changed headers only reach the gateway after the next token
refresh, not "the very next turn".
9. **`plugin-authoring/SKILL.md`** login flow description still said
`http://localhost:<port>/callback`. Updated to use
`<redirect_path>` so it matches the new manifest field semantics.
### Test fixes
- `test_antigravity_oauth.py` + `test_gemini_cli_oauth.py`:
`assertEqual(out["expires_at"], int(time.time()) + 3600)` is a
flaky exact comparison when the script samples a different
integer-second boundary than the test. Captured `t0` / `t1`
around `run_script()` and use `assertIn` to accept either
`t0 + 3600` or `t1 + 3600`.
Verified locally:
cargo fmt --check clean
cargo check clean
cargo test (focused) 5 wire-shape + freemium-fallback pass
python3 -m unittest 14/14 pass
After rebasing onto master (15165db, "skills marketplace explorer"), the protocol fixture had duplicate advisor_note / advisor_status entries because upstream already added them. Pick the upstream canonical versions; drop the duplicates I added earlier. Also bump the count assertions from 107 back down to 105 (master's total). No behavior change. Verified locally: cargo fmt --check clean cargo check clean go test ./... ok schema check ok (73 commands, 105 events, 5 fixtures) cargo test (focused) 6 pass (every_known_event + wire_shape) python3 -m unittest 14/14 pass
There was a problem hiding this comment.
ℹ️ No new issues in this delta — residual dead-helper thread still open.
Reviewed changes Delta since prior pullfrog review (
bf31d3d→bd06b32): merge of the auto-fix onto the author branch after a master skills-marketplace rebase. Net OAuth delta is the Client-Metadata / sibling-test polish already landed onbf31d3d.
- Merged auto-fix — Kept
test_complete_falls_back_to_sibling_via_token_path_dirand the staged Client-Metadata row (ideType: 9/ runtime platform /pluginType: 2).- Prior nits closed on head — Header docs, sibling
token_pathlookup, freemium#[test], env name, lock/timeouts, and Client-Metadata docs match live code.- Still open (not re-raised) — Antigravity
load_code_assistremains unused whilediscover_project_idinlines the same POST; existing thread stands.
openai-compatible/ck-grok-4.5 | 𝕏
…H_DIR test + align Client-Metadata docs) # Conflicts: # core/providers/gemini-cli/README.md # core/providers/gemini-cli/oauth/test_gemini_cli_oauth.py # core/src/protocol.rs # tui/protocol_fixture_test.go
bd06b32 to
8abf9e1
Compare
build.sh used to unconditionally pass --features native-browser, which links the Rust core against libgtk-3, libwebkit2gtk-4.1 and libgio-2.0. On hosts without those dev headers (headless servers, CI containers, most developer laptops where the prebuilt bundle is good enough) the build fails at pkg-config time with no easy workaround. Add three modes: --with-web force native-browser (was the implicit default) --no-web skip native-browser, build TUI-only core (default) auto-detect via 'pkg-config --exists gio-2.0' Add docs/build.md covering the Linux apt-get install command, macOS / Windows notes, and the --no-web workaround for headless hosts. Link to it from docs/installation.md. Verified: 'bash build.sh' on a WebKitGTK-less host now builds the TUI-only core in seconds and replaces the installed catcode/catcode-core on PATH. 'bash build.sh --with-web' still produces the same pkg-config error as before, prompting users to install the missing headers.
The core/providers/{antigravity,gemini-cli}/oauth/ test suites run cleanly
locally (14 passing) but were never exercised on CI. Add a workflow that
mirrors the style of ci.yml (permissions: contents: read, cancel-in-
progress concurrency group keyed off the PR number / ref) and runs:
python3 -m unittest discover -s core/providers -p 'test_*_oauth.py' -v
Triggers on push to main/master and on every pull_request. Uses Python
3.12 on ubuntu-latest; the OAuth helper modules are stdlib-only so no
extra apt or pip install is needed.
Verified locally: 14 tests, 0 failures.
…slugs The 'Models' section of core/providers/gemini-cli/README.md was claiming gemini-3-pro-preview, gemini-3-flash-preview, and (by silence) the sibling Antigravity catalog (claude-*, gpt-oss-*) all work against the gemini-cli OAuth client. Live HTTP verification (404 from the cloudcode-pa gateway) shows only the four slugs below actually resolve on free-tier gemini-cli OAuth; the rest are Antigravity-only. Narrow the upper 'Models' section to the four verified working slugs: gemini-2.5-pro gemini-2.5-flash gemini-2.5-flash-lite gemini-3.1-flash-lite-preview Add the required note: 'This list was verified live against free-tier gemini-cli OAuth. The Antigravity bundle has a wider catalog (Claude + Gemini 3.x + GPT-OSS).' Also call out the Antigravity-only slugs explicitly and link down to the 'Working models (verified 2026-08)' section, which keeps the full 404 list for troubleshooting.
There was a problem hiding this comment.
ℹ️ No critical issues — minor suggestions inline.
Reviewed changes Delta since prior pullfrog review (8abf9e1 → 91211be): CI for the Python OAuth suites, source-build WebKit flags/docs, and a free-tier-accurate gemini-cli model list.
- OAuth Python CI — New
oauth-python-tests.ymlrunsunittest discoverovercore/providers(test_*_oauth.py); 14 tests pass locally with the same command. - Source build flags —
build.shgains--with-web/--no-web/ autopkg-config gio-2.0detection, plusdocs/build.mdand aninstallation.mdlink. - Gemini model docs — Staged Models section narrowed to free-tier-verified slugs and explicitly marks Antigravity-only IDs as 404 on gemini-cli OAuth.
openai-compatible/ck-grok-4.5 | 𝕏
|
Addressed both review threads on PR #7 (
Both threads replied and resolved; changes pushed to the PR branch. Task list (4/4 completed)
|
Enable native-browser by default on Darwin/Windows (system WKWebView / WebView2); keep gio-2.0 probe for Linux. Swap the gemini-cli Wire example model to a free-tier-verified slug.
Merge origin/master into feat/gemini-cli-antigravity-oauth and close the three unresolved review threads: - drop dead Antigravity load_code_assist helper (discover_project_id owns the live loadCodeAssist POST path) - assert expires_at as an inclusive [t0+3600, t1+3600] range in both gemini-cli and antigravity token-refresh tests (avoids second-boundary flake) - drop the PR's stale advisor_note/advisor_status no-op + narrow CoreEvent variants so master's upsertAdvisor handlers and richer optional fields (finding/reason/elapsed_ms) win — unblocks web typecheck CI
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Delta since prior pullfrog review (b9b5ef9 → c42d38d): master catch-up merge that closes the remaining open review threads for this PR.
- Dead helper removed — Antigravity
load_code_assistdeleted;discover_project_idowns the live:loadCodeAssistPOST + extraction path. - Token-refresh flake hardening — both provider suites assert
expires_atin the inclusive[t0+3600, t1+3600]window instead of a two-point membership check. - Web advisor merge — dropped the PR's no-op
advisor_note/advisor_statusstubs so master'supsertAdvisorhandlers and richer optional fields win (unblocks web typecheck).
Prior open threads (platform-aware build.sh, Wire sample model, dead load_code_assist) are addressed on head.
openai-compatible/ck-grok-4.5 | 𝕏

Summary
Adds first-party OAuth provider bundles for Google Antigravity IDE and Gemini CLI, so
/login antigravityand/login gemini-cliwork without API keys.Both reuse the existing
google_code_assistwire adapter (cloudcode-pa/ daily-cloudcode-pa →:streamGenerateContent?alt=sse). No new Rust protocol was required.What’s included
core/providers/antigravity/— PKCE OAuth, Antigravity IDE client + fingerprint,loadCodeAssist/onboardUser, project header injectioncore/providers/gemini-cli/— same contract with the public gemini-cli OAuth clientcore/src/staging.rs(STAGING_VERSION6 → 7)CATALYST_CODE_ANTIGRAVITY_PROJECT,CATALYST_CODE_GEMINI_CLI_PROJECTCritical wire fix (why chat was 403)
Do not send
x-goog-user-project. That consumer header forces a Cloud Code Private API enablement check and returnsSERVICE_DISABLEDon free-tier / managed projects.9router’s gemini-cli executor never sends it —
projectgoes in the JSON body only. These plugins emitx-code-assist-projectinstead so the harness adapter fillsbody.projectwithout tripping the gate.Verified live (free-tier Google account)
/oauth2callback)loadCodeAssist+onboardUsersynthetic-expanse-sxhhmx-goog-user-projectgemini-2.5-flash/pro/flash-lite/3.1-flash-lite-previewx-goog-user-projectSERVICE_DISABLEDModel notes
gemini-2.5-*,gemini-3.1-flash-lite-previewwork; preview / Antigravity-only slugs 404:fetchAvailableModels(Gemini 3.x, Claude, GPT-OSS, …) once project is provisionedTest plan
cargo check/ focused unit tests (staging, plugins, oauth, google_code_assist, providers)$HOME→ release binary stages both plugins under~/.catalyst-code/plugins//login-equivalent PKCE flow for gemini-cli + antigravityx-goog-user-project→ 403/login gemini-clithen a one-shot prompt withgemini-2.5-flash/login antigravity(optional; same adapter path)Summary by CodeRabbit
New Features
Documentation