diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index 94675b639a0..fccd0b84249 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -104,6 +104,7 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no** - Providers: `google-vertex`, `google-antigravity`, `google-gemini-cli` - Auth: Vertex uses gcloud ADC; Antigravity/Gemini CLI use their respective auth flows +- Caution: Antigravity and Gemini CLI OAuth in OpenClaw are unofficial integrations. Some users have reported Google account restrictions after using third-party clients. Review Google terms and use a non-critical account if you choose to proceed. - Antigravity OAuth is shipped as a bundled plugin (`google-antigravity-auth`, disabled by default). - Enable: `openclaw plugins enable google-antigravity-auth` - Login: `openclaw models auth login --provider google-antigravity --set-default` diff --git a/extensions/google-gemini-cli-auth/README.md b/extensions/google-gemini-cli-auth/README.md index 07dcd13c52a..bbca53ba1ce 100644 --- a/extensions/google-gemini-cli-auth/README.md +++ b/extensions/google-gemini-cli-auth/README.md @@ -2,6 +2,12 @@ OAuth provider plugin for **Gemini CLI** (Google Code Assist). +## Account safety caution + +- This plugin is an unofficial integration and is not endorsed by Google. +- Some users have reported account restrictions or suspensions after using third-party Gemini CLI and Antigravity OAuth clients. +- Use caution, review the applicable Google terms, and avoid using a mission-critical account. + ## Enable Bundled plugins are disabled by default. Enable this one: diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 43ef7c4eda0..0296b306de1 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -242,7 +242,7 @@ const BASE_AUTH_CHOICE_OPTIONS: ReadonlyArray = [ { value: "google-gemini-cli", label: "Google Gemini CLI OAuth", - hint: "Uses the bundled Gemini CLI auth plugin", + hint: "Unofficial flow; review account-risk warning before use", }, { value: "zai-api-key", label: "Z.AI API key" }, { diff --git a/src/commands/auth-choice.apply.google-gemini-cli.test.ts b/src/commands/auth-choice.apply.google-gemini-cli.test.ts new file mode 100644 index 00000000000..f07f970a18d --- /dev/null +++ b/src/commands/auth-choice.apply.google-gemini-cli.test.ts @@ -0,0 +1,86 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { applyAuthChoiceGoogleGeminiCli } from "./auth-choice.apply.google-gemini-cli.js"; +import type { ApplyAuthChoiceParams } from "./auth-choice.apply.js"; +import { applyAuthChoicePluginProvider } from "./auth-choice.apply.plugin-provider.js"; +import { createExitThrowingRuntime, createWizardPrompter } from "./test-wizard-helpers.js"; + +vi.mock("./auth-choice.apply.plugin-provider.js", () => ({ + applyAuthChoicePluginProvider: vi.fn(), +})); + +function createParams( + authChoice: ApplyAuthChoiceParams["authChoice"], + overrides: Partial = {}, +): ApplyAuthChoiceParams { + return { + authChoice, + config: {}, + prompter: createWizardPrompter({}, { defaultSelect: "" }), + runtime: createExitThrowingRuntime(), + setDefaultModel: true, + ...overrides, + }; +} + +describe("applyAuthChoiceGoogleGeminiCli", () => { + const mockedApplyAuthChoicePluginProvider = vi.mocked(applyAuthChoicePluginProvider); + + beforeEach(() => { + mockedApplyAuthChoicePluginProvider.mockReset(); + }); + + it("returns null for unrelated authChoice", async () => { + const result = await applyAuthChoiceGoogleGeminiCli(createParams("openrouter-api-key")); + + expect(result).toBeNull(); + expect(mockedApplyAuthChoicePluginProvider).not.toHaveBeenCalled(); + }); + + it("shows caution and skips setup when user declines", async () => { + const confirm = vi.fn(async () => false); + const note = vi.fn(async () => {}); + const params = createParams("google-gemini-cli", { + prompter: createWizardPrompter({ confirm, note }, { defaultSelect: "" }), + }); + + const result = await applyAuthChoiceGoogleGeminiCli(params); + + expect(result).toEqual({ config: params.config }); + expect(note).toHaveBeenNthCalledWith( + 1, + expect.stringContaining("This is an unofficial integration and is not endorsed by Google."), + "Google Gemini CLI caution", + ); + expect(confirm).toHaveBeenCalledWith({ + message: "Continue with Google Gemini CLI OAuth?", + initialValue: false, + }); + expect(note).toHaveBeenNthCalledWith( + 2, + "Skipped Google Gemini CLI OAuth setup.", + "Setup skipped", + ); + expect(mockedApplyAuthChoicePluginProvider).not.toHaveBeenCalled(); + }); + + it("continues to plugin provider flow when user confirms", async () => { + const confirm = vi.fn(async () => true); + const note = vi.fn(async () => {}); + const params = createParams("google-gemini-cli", { + prompter: createWizardPrompter({ confirm, note }, { defaultSelect: "" }), + }); + const expected = { config: {} }; + mockedApplyAuthChoicePluginProvider.mockResolvedValue(expected); + + const result = await applyAuthChoiceGoogleGeminiCli(params); + + expect(result).toBe(expected); + expect(mockedApplyAuthChoicePluginProvider).toHaveBeenCalledWith(params, { + authChoice: "google-gemini-cli", + pluginId: "google-gemini-cli-auth", + providerId: "google-gemini-cli", + methodId: "oauth", + label: "Google Gemini CLI", + }); + }); +}); diff --git a/src/commands/auth-choice.apply.google-gemini-cli.ts b/src/commands/auth-choice.apply.google-gemini-cli.ts index d2a3281f628..5fcbc832338 100644 --- a/src/commands/auth-choice.apply.google-gemini-cli.ts +++ b/src/commands/auth-choice.apply.google-gemini-cli.ts @@ -4,6 +4,29 @@ import { applyAuthChoicePluginProvider } from "./auth-choice.apply.plugin-provid export async function applyAuthChoiceGoogleGeminiCli( params: ApplyAuthChoiceParams, ): Promise { + if (params.authChoice !== "google-gemini-cli") { + return null; + } + + await params.prompter.note( + [ + "This is an unofficial integration and is not endorsed by Google.", + "Some users have reported account restrictions or suspensions after using third-party Gemini CLI and Antigravity OAuth clients.", + "Proceed only if you understand and accept this risk.", + ].join("\n"), + "Google Gemini CLI caution", + ); + + const proceed = await params.prompter.confirm({ + message: "Continue with Google Gemini CLI OAuth?", + initialValue: false, + }); + + if (!proceed) { + await params.prompter.note("Skipped Google Gemini CLI OAuth setup.", "Setup skipped"); + return { config: params.config }; + } + return await applyAuthChoicePluginProvider(params, { authChoice: "google-gemini-cli", pluginId: "google-gemini-cli-auth",