Skip to content

[v3.8.50] feat(cli): deliver the Antigravity credential straight to the remote install - #8834

Merged
diegosouzapw merged 2 commits into
release/v3.8.50from
feat/cli-login-push-remote
Aug 6, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.50from
feat/cli-login-push-remote

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

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

omniroute connect 192.168.0.15        # once — mints an admin-scoped context token
npx omniroute login antigravity
#   Antigravity connected on http://192.168.0.15:20128 (connection abc123).
#   Nothing to paste — you can close this terminal.

Before: the helper printed a omniroute-cred-v1.… blob for the operator to copy into
the 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:

Piece Where Status
Admin-scoped token for the remote omniroute connect → context already there
baseUrl + Bearer injection bin/cli/api.mjs::buildHeaders already there
/api/oauth requires admin src/server/authz/accessScopes.ts:26 matches what connect mints
/api/oauth stays remote-reachable routeGuard.ts:53 only cursor/auto-import is loopback-gated, with an explicit comment that the rest must stay reachable
Bearer management token accepted apiAuth.ts:296-313 validateBearerApiKeyForManagement
Blob decode + persist /api/oauth/<p>/paste-credentials already there

Zero 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 install

A 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 antigravity cannot 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 callback
still 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:

Provider Callback Needs it?
antigravity, agy 127.0.0.1:<dashboardPort> Yes — hardest case. Consent hangs; there is no URL to paste at all
codex (+openai alias) localhost:1455 (fixed) Yes — currently needs an SSH tunnel
xai-oauth 127.0.0.1:56121 (fixed) Yes
grok-cli 127.0.0.1:56122 (fixed) Yes
windsurf, devin-cli — No — import-token-only since the PKCE retirement; the paste flow works
everything else actual origin No — the provider does redirect, so paste-the-callback-URL functions

This PR covers antigravity only, on purpose — validate the mechanism on the worst case
first. agy is deliberately not wired: bin/cli/commands/login.mjs pins
PROVIDER = "antigravity" and parsePastedCredentials() rejects a blob whose embedded
provider does not match the route, so agy needs its own blob, not a flag.

The fixed-port family (codex/xai-oauth/grok-cli) can reuse the same shape via the
existing generic exchange action, which already accepts
{code, redirectUri, codeVerifier, state} — CLI asks the remote to authorize, runs the
loopback 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 local
  • pushCredentialBlob — POSTs to the right route with the right body; surfaces a server
    rejection instead of reporting success; converts a transport throw into a result
  • remote context → auto-push, and the blob is not printed
  • failed push → falls back to printing (the firewall case still works)
  • local context → prints, with no network call at all
  • --no-push / --push override in both directions
  • a context with no token still pushes (isAuthenticated() short-circuits when
    requireLogin is off — refusing would break valid unauthenticated installs)

Validation

Gate Result
new suite 10/10 passed
existing CLI suites (cli-login-antigravity, cli-oauth-commands, cli-contexts*) 29/29 passed
typecheck:core clean
eslint clean (bin/ is eslint-ignored; 0 errors)
check-docs-sync PASS
check:any-budget:t11 / check:tracked-artifacts PASS

Not yet verified live. The push path has no unit-testable network leg; a real run
against 192.168.0.15 is the honest confirmation and has not been done. Everything
asserted 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 the
firewall/--no-push path kept as a first-class alternative rather than a footnote.

@diegosouzapw diegosouzapw changed the title feat(cli): deliver the Antigravity credential straight to the remote install [v3.8.50] feat(cli): deliver the Antigravity credential straight to the remote install Jul 28, 2026
@diegosouzapw diegosouzapw added the deferred-v3.8.50 Adiada para o ciclo v3.8.50 (validacao VPS, refactor, ou escopo grande) label Jul 28, 2026
@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Validated green on the clean release tip 3b515d90b3 (typecheck:core clean, 10/10 focused tests, good coverage of the failure paths — a failed push falls back to printing the blob, a local context makes no surprise network call).

Deferring to v3.8.50 on scheduling grounds, not quality: this is a feat touching a credential surface (it wraps a refresh token and POSTs it to a remote base URL) and v3.8.49 is in finalization. It opens the next cycle instead.

The Build (advisory) red is infra — The runner has received a shutdown signal, not code.

@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.49 to release/v3.8.50 July 28, 2026 18:40
@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Re-homed to release/v3.8.50: v3.8.49 entered its release freeze, so the branch now belongs to the release captain and development continues on the next cycle. Nothing is wrong with this PR — it just needed a live base. No action needed from you; CI will re-run against the new base.

…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.
@diegosouzapw
diegosouzapw force-pushed the feat/cli-login-push-remote branch from 384f06a to a6735cf Compare August 1, 2026 18:07
@diegosouzapw
diegosouzapw merged commit cfd9210 into release/v3.8.50 Aug 6, 2026
10 of 11 checks passed
@diegosouzapw
diegosouzapw deleted the feat/cli-login-push-remote branch August 6, 2026 01:39
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…install (cheaper-inference#8834)

Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates — only pre-existing audit.test.ts flake).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deferred-v3.8.50 Adiada para o ciclo v3.8.50 (validacao VPS, refactor, ou escopo grande)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant