Repository navigation
[v3.8.50] feat(cli): deliver the Antigravity credential straight to the remote install - #8834
Conversation
|
Validated green on the clean release tip Deferring to v3.8.50 on scheduling grounds, not quality: this is a The |
|
Re-homed to |
…install `omniroute login antigravity` already runs the OAuth where it actually works — the operator's own machine, the only place Google's firstparty/nativeapp loopback resolves — but it stopped at PRINTING a credential blob to copy into the remote dashboard by hand. Every piece needed to close that loop was already in place: - `omniroute connect <host>` saves an admin-scoped token in the active context; - `apiFetch()` injects that context's baseUrl + Bearer automatically; - `/api/oauth` requires admin scope (src/server/authz/accessScopes.ts) and stays remote-reachable — routeGuard.ts loopback-gates only `/api/oauth/cursor/auto-import`, with an explicit comment that the rest must remain reachable; - `isAuthenticated()` accepts a Bearer management token; - `/api/oauth/<provider>/paste-credentials` already decodes a blob and persists. So the CLI now POSTs the blob itself whenever the active context points at another machine. No paste, and no SSH tunnel. What it deliberately does NOT do is make the push mandatory. This helper exists because it needs no route to the VPS at all — it talks only to Google, so it works from behind a firewall. A failed push therefore falls back to printing the blob rather than discarding an authorization the operator just completed in a browser. `--no-push` forces the old behaviour, `--push` forces delivery, `--context <name>` targets a specific install. A successful push does not echo the blob: it wraps a refresh token and it already landed. Printing it would only widen the exposure. Note for the follow-up: `oauth start --provider antigravity` cannot substitute for this. `runBrowserFlow` asks the SERVER to start the flow and then polls the server, so the callback still has to reach the server's loopback — it hangs on a remote install exactly like the dashboard does.
384f06a to
a6735cf
Compare
…install (cheaper-inference#8834) Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates — only pre-existing audit.test.ts flake).
Follow-up to #8463, which could only document the workaround for a remote install.
This removes the manual step for Antigravity: the CLI delivers the credential itself.
What changes
Before: the helper printed a
omniroute-cred-v1.…blob for the operator to copy intothe remote dashboard by hand.
Why this needed almost no new machinery
The helper already ran the OAuth in the one place Google's firstparty/nativeapp loopback
resolves — the operator's own machine. Only the last hop was missing, and every piece of
it already existed:
omniroute connect→ contextbin/cli/api.mjs::buildHeaders/api/oauthrequires adminsrc/server/authz/accessScopes.ts:26connectmints/api/oauthstays remote-reachablerouteGuard.ts:53cursor/auto-importis loopback-gated, with an explicit comment that the rest must stay reachableapiAuth.ts:296-313validateBearerApiKeyForManagement/api/oauth/<p>/paste-credentialsZero server changes.
The constraint that shaped the design
This helper exists because it needs no route to the VPS — it talks only to Google, so
it works from behind a firewall, on an air-gapped desk, with no SSH. Making the push
mandatory would have quietly destroyed that property.
So the push is best-effort: it fires automatically when the active context points at
another machine, and a failure falls back to printing the blob rather than discarding an
authorization the operator just completed in their browser.
--push— force delivery even against a loopback context--no-push— force the old print-only behaviour--context <name>— target a specific installA successful push deliberately does not echo the blob: it wraps a refresh token and it
already landed, so printing it would only widen the exposure. A failed push does print it,
because then it is the only copy the operator has.
Auto-push is also skipped when the context baseUrl is unparseable or absent — we never push
into something we cannot reason about.
A finding worth recording
omniroute oauth start --provider antigravitycannot substitute for this.runBrowserFlow(bin/cli/commands/oauth.mjs) asks the server to start the flow(
POST /api/oauth/<id>/start) and then polls the server for status — so the callbackstill has to reach the server's loopback. Against a remote context it hangs exactly like
the dashboard does. It is the same bug by another route, not an alternative.
Which providers still need this treatment
Surveyed the whole OAuth surface for loopback-bound callbacks:
antigravity,agy127.0.0.1:<dashboardPort>codex(+openaialias)localhost:1455(fixed)xai-oauth127.0.0.1:56121(fixed)grok-cli127.0.0.1:56122(fixed)windsurf,devin-cliThis PR covers
antigravityonly, on purpose — validate the mechanism on the worst casefirst.
agyis deliberately not wired:bin/cli/commands/login.mjspinsPROVIDER = "antigravity"andparsePastedCredentials()rejects a blob whose embeddedprovider does not match the route, so
agyneeds its own blob, not a flag.The fixed-port family (
codex/xai-oauth/grok-cli) can reuse the same shape via theexisting generic
exchangeaction, which already accepts{code, redirectUri, codeVerifier, state}— CLI asks the remote toauthorize, runs theloopback locally on the provider's fixed port, POSTs the code back. Also likely zero server
changes. Held for a follow-up.
Tests
10 new tests, written failing-first:
isRemoteBaseUrl— loopback (incl.[::1]) is local; unparseable/absent is localpushCredentialBlob— POSTs to the right route with the right body; surfaces a serverrejection instead of reporting success; converts a transport throw into a result
--no-push/--pushoverride in both directionsisAuthenticated()short-circuits whenrequireLoginis off — refusing would break valid unauthenticated installs)Validation
cli-login-antigravity,cli-oauth-commands,cli-contexts*)typecheck:coreeslintcheck-docs-synccheck:any-budget:t11/check:tracked-artifactsNot yet verified live. The push path has no unit-testable network leg; a real run
against
192.168.0.15is the honest confirmation and has not been done. Everythingasserted above is unit-level plus code reading of the auth/route chain.
Docs
docs/guides/REMOTE-MODE.md→ Option A rewritten around the push, with thefirewall/
--no-pushpath kept as a first-class alternative rather than a footnote.