From 85976eed196e294d7d983c2ba01976f268ca4316 Mon Sep 17 00:00:00 2001 From: Mohtasham Murshid <154406804+MohtashamMurshid@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:35:44 +0000 Subject: [PATCH] fix(server): support Antigravity auth in standalone binary --- apps/server/src/bin.ts | 2 + .../server/src/cli/antigravityBrowser.test.ts | 20 ++++++++ apps/server/src/cli/antigravityBrowser.ts | 21 +++++++++ .../provider/antigravityAuthSupport.test.ts | 46 ++++++++++++++++++- .../src/provider/antigravityAuthSupport.ts | 15 ++++-- 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 apps/server/src/cli/antigravityBrowser.test.ts create mode 100644 apps/server/src/cli/antigravityBrowser.ts diff --git a/apps/server/src/bin.ts b/apps/server/src/bin.ts index 1ec78ff2e173..2a2c2b5249dc 100644 --- a/apps/server/src/bin.ts +++ b/apps/server/src/bin.ts @@ -7,6 +7,7 @@ import * as CliError from "effect/unstable/cli/CliError"; import * as NetService from "@t3tools/shared/Net"; import packageJson from "../package.json" with { type: "json" }; +import { antigravityBrowserCommand } from "./cli/antigravityBrowser.ts"; import { authCommand } from "./cli/auth.ts"; import { appCommand } from "./cli/app.ts"; import { connectCommand } from "./cli/connect.ts"; @@ -70,6 +71,7 @@ export const makeCli = ({ cloudEnabled = hasCloudPublicConfig } = {}) => claudeHistoryCommand, servicePreflightCommand, sshHelperCommand, + antigravityBrowserCommand, themeCommand, triageCommand, cloudEnabled ? connectCommand : connectUnavailableCommand, diff --git a/apps/server/src/cli/antigravityBrowser.test.ts b/apps/server/src/cli/antigravityBrowser.test.ts new file mode 100644 index 000000000000..ba3b69a71440 --- /dev/null +++ b/apps/server/src/cli/antigravityBrowser.test.ts @@ -0,0 +1,20 @@ +import * as NodeServices from "@effect/platform-node/NodeServices"; +import { assert, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as TestConsole from "effect/testing/TestConsole"; +import { Command } from "effect/unstable/cli"; + +import { ANTIGRAVITY_AUTH_BROWSER_MARKER } from "../provider/antigravityAuthSupport.ts"; +import { antigravityBrowserCommand } from "./antigravityBrowser.ts"; + +it.effect("writes the Antigravity authorization URL marker to stderr", () => + Effect.gen(function* () { + const url = "https://accounts.google.com/example?state=opaque"; + yield* Command.runWith(antigravityBrowserCommand, { version: "0.0.0" })([url]); + + assert.deepEqual(yield* TestConsole.errorLines, [ + `${ANTIGRAVITY_AUTH_BROWSER_MARKER}"https://accounts.google.com/example?state=opaque"`, + ]); + }).pipe(Effect.provide(Layer.mergeAll(NodeServices.layer, TestConsole.layer))), +); diff --git a/apps/server/src/cli/antigravityBrowser.ts b/apps/server/src/cli/antigravityBrowser.ts new file mode 100644 index 000000000000..e0db3528f18c --- /dev/null +++ b/apps/server/src/cli/antigravityBrowser.ts @@ -0,0 +1,21 @@ +import * as Console from "effect/Console"; +import { Argument, Command } from "effect/unstable/cli"; + +import { + ANTIGRAVITY_AUTH_BROWSER_MARKER, + ANTIGRAVITY_BROWSER_HELPER_COMMAND, +} from "../provider/antigravityAuthSupport.ts"; + +/** + * Hosts the no-browser helper inside the single executable. Script installs + * run the equivalent inline source under Node, while a SEA has no Node + * interpreter for `-e` and invokes this hidden subcommand instead. + */ +export const antigravityBrowserCommand = Command.make(ANTIGRAVITY_BROWSER_HELPER_COMMAND, { + url: Argument.string("url"), +}).pipe( + Command.unlisted, + Command.withHandler(({ url }) => + Console.error(`${ANTIGRAVITY_AUTH_BROWSER_MARKER}${JSON.stringify(url)}`), + ), +); diff --git a/apps/server/src/provider/antigravityAuthSupport.test.ts b/apps/server/src/provider/antigravityAuthSupport.test.ts index 7f065caa4f6a..0a3c290bab8e 100644 --- a/apps/server/src/provider/antigravityAuthSupport.test.ts +++ b/apps/server/src/provider/antigravityAuthSupport.test.ts @@ -3,7 +3,11 @@ import * as NodeChildProcess from "node:child_process"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { ProviderInstanceId } from "@t3tools/contracts"; -import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; +import { + HostProcessExecutablePath, + HostProcessIsExecutable, + HostProcessPlatform, +} from "@t3tools/shared/hostProcess"; import { describe, expect, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; @@ -511,6 +515,46 @@ it.layer(NodeServices.layer)("Antigravity profile preparation", (it) => { }), ); + it.effect("uses the hidden browser helper when the host is a single executable", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const temporaryDirectory = yield* fs.makeTempDirectoryScoped(); + const executablePath = "/opt/t3-runtime/t3"; + let helperCommand: ChildProcess.StandardCommand | undefined; + const profile = yield* prepareAntigravityProfile({ + profileDirectory: temporaryDirectory, + platform: "linux", + }).pipe( + Effect.provideService(HostProcessExecutablePath, executablePath), + Effect.provideService(HostProcessIsExecutable, true), + Effect.provideService( + ChildProcessSpawner.ChildProcessSpawner, + ChildProcessSpawner.make((command) => { + if (!ChildProcess.isStandardCommand(command)) return spawner.spawn(command); + helperCommand = command; + const url = command.args.at(-1) ?? ""; + return spawner.spawn( + ChildProcess.make(process.execPath, [ + "-e", + `process.stderr.write(${JSON.stringify( + `${ANTIGRAVITY_AUTH_BROWSER_MARKER}${JSON.stringify(url)}\n`, + )})`, + ]), + ); + }), + ), + ); + + expect(helperCommand?.command).toBe(executablePath); + expect(helperCommand?.args).toEqual([ + "__antigravity-browser", + "https://example.invalid/t3-antigravity-browser-preflight", + ]); + expect(profile.browserCommand).toBe(`'/opt/t3-runtime/t3' '__antigravity-browser' '%s'`); + }), + ); + it.effect.skipIf(!symlinksSupported)( "links the user's global skill directories into the profile without touching real content", () => diff --git a/apps/server/src/provider/antigravityAuthSupport.ts b/apps/server/src/provider/antigravityAuthSupport.ts index 8e0040ae0c3e..414e5994aff2 100644 --- a/apps/server/src/provider/antigravityAuthSupport.ts +++ b/apps/server/src/provider/antigravityAuthSupport.ts @@ -5,7 +5,11 @@ import * as NodeFSP from "node:fs/promises"; import * as NodePath from "node:path"; import type { AntigravityAuthMethod, ProviderInstanceId } from "@t3tools/contracts"; -import { HostProcessExecutablePath, HostProcessPlatform } from "@t3tools/shared/hostProcess"; +import { + HostProcessExecutablePath, + HostProcessIsExecutable, + HostProcessPlatform, +} from "@t3tools/shared/hostProcess"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; @@ -26,6 +30,7 @@ import { export const ANTIGRAVITY_AUTH_STDOUT_PREFIX = "Open the following link to authenticate the ACP server: "; export const ANTIGRAVITY_AUTH_BROWSER_MARKER = "__T3_ANTIGRAVITY_AUTH_URL__"; +export const ANTIGRAVITY_BROWSER_HELPER_COMMAND = "__antigravity-browser"; export const ANTIGRAVITY_SIGN_IN_REQUIRED_MESSAGE = "Sign in to Antigravity in Settings before you continue."; @@ -293,9 +298,13 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( const userHome = input.userHome ?? resolveAntigravityUserHome(platform, input.baseEnv ?? process.env); const runtimeExecutablePath = input.runtimeExecutablePath ?? (yield* HostProcessExecutablePath); + const runtimeIsExecutable = yield* HostProcessIsExecutable; const helperExecutable = platform === "win32" ? runtimeExecutablePath.replaceAll("\\", "/") : runtimeExecutablePath; - const browserArguments = [helperExecutable, "-e", browserHelperSource, "--", "%s"]; + const helperArguments = runtimeIsExecutable + ? [ANTIGRAVITY_BROWSER_HELPER_COMMAND] + : ["-e", browserHelperSource, "--"]; + const browserArguments = [helperExecutable, ...helperArguments, "%s"]; const browserCommand = browserArguments.map(quoteBrowserArgument).join(" "); if ( browserCommand.includes(platform === "win32" ? ";" : ":") || @@ -321,7 +330,7 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( const environment = antigravityEnvironment(profile, input.baseEnv ?? process.env, auth); yield* Effect.gen(function* () { const child = yield* spawner.spawn( - ChildProcess.make(helperExecutable, ["-e", browserHelperSource, "--", browserPreflightUrl], { + ChildProcess.make(helperExecutable, [...helperArguments, browserPreflightUrl], { env: environment, extendEnv: false, shell: false,