From e2fed321319491f02a9193779f5b10ca5ca6dcb7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 16 Jul 2026 01:12:15 -0700 Subject: [PATCH] fix(onboarding): explain how to get provider tokens and revive logged-out CLI candidates (#108739) * fix(onboarding): explain how to obtain provider tokens in auth setup copy * fix(onboarding): add per-CLI login remediation to detected candidate details --- docs/concepts/oauth.md | 2 +- docs/providers/anthropic.md | 12 +++++++ docs/providers/google.md | 9 +++++- docs/start/onboarding.md | 5 +++ extensions/anthropic/openclaw.plugin.json | 4 +-- extensions/anthropic/provider-contract-api.ts | 8 ++--- extensions/anthropic/register.runtime.ts | 8 ++--- extensions/google/gemini-cli-provider.ts | 2 +- extensions/google/openclaw.plugin.json | 3 +- extensions/google/provider-contract-api.ts | 4 +-- extensions/google/provider-registration.ts | 2 +- .../official-external-provider-catalog.json | 2 +- src/commands/onboard-inference.test.ts | 32 ++++++++++++++++++- src/commands/onboard-inference.ts | 10 +++--- src/system-agent/setup-inference.test.ts | 2 +- src/system-agent/setup-inference.ts | 6 ++-- 16 files changed, 84 insertions(+), 27 deletions(-) diff --git a/docs/concepts/oauth.md b/docs/concepts/oauth.md index 35358dbd1f03..0f8c6950c198 100644 --- a/docs/concepts/oauth.md +++ b/docs/concepts/oauth.md @@ -121,7 +121,7 @@ OpenClaw's interactive login flows are implemented in `openclaw/plugin-sdk/llm.t Flow shape: -1. start Anthropic setup-token or paste-token from OpenClaw +1. create the token by running `claude setup-token` on any machine with Claude Code, then start Anthropic setup-token or paste-token from OpenClaw 2. OpenClaw stores the resulting Anthropic credential in an auth profile 3. model selection stays on `anthropic/...` 4. existing Anthropic auth profiles remain available for rollback/order control diff --git a/docs/providers/anthropic.md b/docs/providers/anthropic.md index d67e693b6d0a..fbb1473801b3 100644 --- a/docs/providers/anthropic.md +++ b/docs/providers/anthropic.md @@ -127,6 +127,18 @@ OpenClaw release: [OpenAI Codex](/providers/openai). + ### Get a setup token + + Run `claude setup-token` on any machine with Claude Code installed. It prints + a long-lived token starting with `sk-ant-oat01-`. + + During onboarding, paste the token in the macOS app by choosing + **Anthropic setup-token** under **Connect with an API key or token**, or use: + + ```bash + openclaw models auth login --provider anthropic --method setup-token + ``` + ### Config example Prefer the canonical Anthropic model ref plus a CLI runtime override: diff --git a/docs/providers/google.md b/docs/providers/google.md index 0198a5e74410..11c33c4c3c89 100644 --- a/docs/providers/google.md +++ b/docs/providers/google.md @@ -22,6 +22,9 @@ Choose your preferred auth method and follow the setup steps. **Best for:** standard Gemini API access through Google AI Studio. + + Create a free key in [Google AI Studio](https://aistudio.google.com/apikey). + ```bash openclaw onboard --auth-choice gemini-api-key @@ -61,7 +64,7 @@ Choose your preferred auth method and follow the setup steps. - **Best for:** reusing an existing Gemini CLI login via PKCE OAuth instead of a separate API key. + **Best for:** signing in with your Google account through Gemini CLI OAuth instead of using a separate API key. The `google-gemini-cli` provider is an unofficial integration. Some users @@ -116,6 +119,10 @@ Choose your preferred auth method and follow the setup steps. command is installed and on `PATH`. + Onboarding auto-detection lists an existing Gemini CLI login but never + auto-tests it because Gemini CLI has no tool-free probe. Choose Gemini CLI + OAuth or a Gemini API key to continue. + `google-gemini-cli/*` model refs are legacy compatibility aliases. New configs should use `google/*` model refs plus the `google-gemini-cli` runtime when they want local Gemini CLI execution. diff --git a/docs/start/onboarding.md b/docs/start/onboarding.md index 4e47c8ba9ebe..1b991fc3e4c6 100644 --- a/docs/start/onboarding.md +++ b/docs/start/onboarding.md @@ -80,6 +80,11 @@ and shows why the previous one failed. If several options are found you can switch between them before continuing. Automatic local discovery never pulls or downloads a model. +To use a Claude subscription when the Gateway host has no Claude CLI login, run +`claude setup-token` on any machine with Claude Code installed, then paste the +printed token as **Anthropic setup-token** under **Connect with an API key or +token**. + Gemini CLI and Antigravity remain available for normal use after setup. Their installed CLIs are shown for context but are not auto-tested because neither can enforce the tool-free inference probe. diff --git a/extensions/anthropic/openclaw.plugin.json b/extensions/anthropic/openclaw.plugin.json index 18ace659f48b..dbaf269c2a80 100644 --- a/extensions/anthropic/openclaw.plugin.json +++ b/extensions/anthropic/openclaw.plugin.json @@ -262,7 +262,7 @@ "choiceId": "anthropic-cli", "deprecatedChoiceIds": ["claude-cli"], "choiceLabel": "Anthropic Claude CLI", - "choiceHint": "Reuse a local Claude CLI login on this host", + "choiceHint": "Keep using an existing Claude Code CLI login on this host", "assistantPriority": -20, "groupId": "anthropic", "groupLabel": "Anthropic", @@ -275,7 +275,7 @@ "choiceId": "setup-token", "appGuidedSecret": true, "choiceLabel": "Anthropic setup-token", - "choiceHint": "Manual token path", + "choiceHint": "Token created by running 'claude setup-token' in your terminal", "assistantPriority": 40, "groupId": "anthropic", "groupLabel": "Anthropic", diff --git a/extensions/anthropic/provider-contract-api.ts b/extensions/anthropic/provider-contract-api.ts index de7c4a1622e9..ba5b2ec2f1aa 100644 --- a/extensions/anthropic/provider-contract-api.ts +++ b/extensions/anthropic/provider-contract-api.ts @@ -19,12 +19,12 @@ export function createAnthropicProvider(): ProviderPlugin { id: "cli", kind: "custom", label: "Claude CLI", - hint: "Reuse a local Claude CLI login and switch model selection to claude-cli/*", + hint: "Keep using a local Claude CLI login and run Anthropic models through the Claude CLI runtime", run: noopAuth, wizard: { choiceId: "anthropic-cli", choiceLabel: "Anthropic Claude CLI", - choiceHint: "Reuse a local Claude CLI login on this host", + choiceHint: "Keep using an existing Claude Code CLI login on this host", groupId: "anthropic", groupLabel: "Anthropic", groupHint: "Claude CLI + API key", @@ -34,12 +34,12 @@ export function createAnthropicProvider(): ProviderPlugin { id: "setup-token", kind: "token", label: "Anthropic setup-token", - hint: "Manual bearer token path", + hint: "Paste a long-lived token created with 'claude setup-token'", run: noopAuth, wizard: { choiceId: "setup-token", choiceLabel: "Anthropic setup-token", - choiceHint: "Manual token path", + choiceHint: "Token created by running 'claude setup-token' in your terminal", groupId: "anthropic", groupLabel: "Anthropic", groupHint: "Claude CLI + API key + token", diff --git a/extensions/anthropic/register.runtime.ts b/extensions/anthropic/register.runtime.ts index 90455d84a7e6..d8a85bfbd692 100644 --- a/extensions/anthropic/register.runtime.ts +++ b/extensions/anthropic/register.runtime.ts @@ -810,12 +810,12 @@ export function buildAnthropicProvider(): ProviderPlugin { { id: "cli", label: "Claude CLI", - hint: "Reuse a local Claude CLI login and run Anthropic models through the Claude CLI runtime", + hint: "Keep using a local Claude CLI login and run Anthropic models through the Claude CLI runtime", kind: "custom", wizard: { choiceId: "anthropic-cli", choiceLabel: "Anthropic Claude CLI", - choiceHint: "Reuse a local Claude CLI login on this host", + choiceHint: "Keep using an existing Claude Code CLI login on this host", assistantPriority: -20, groupId: "anthropic", groupLabel: "Anthropic", @@ -837,12 +837,12 @@ export function buildAnthropicProvider(): ProviderPlugin { { id: "setup-token", label: "Anthropic setup-token", - hint: "Manual bearer token path", + hint: "Paste a long-lived token created with 'claude setup-token'", kind: "token", wizard: { choiceId: "setup-token", choiceLabel: "Anthropic setup-token", - choiceHint: "Manual token path", + choiceHint: "Token created by running 'claude setup-token' in your terminal", assistantPriority: 40, groupId: "anthropic", groupLabel: "Anthropic", diff --git a/extensions/google/gemini-cli-provider.ts b/extensions/google/gemini-cli-provider.ts index 4a18cb4cbef7..534557a63644 100644 --- a/extensions/google/gemini-cli-provider.ts +++ b/extensions/google/gemini-cli-provider.ts @@ -115,7 +115,7 @@ export function buildGoogleGeminiCliProvider(): ProviderPlugin { setup: { choiceId: "google-gemini-cli", choiceLabel: "Gemini CLI OAuth", - choiceHint: "Google OAuth with project-aware token payload", + choiceHint: "Sign in with your Google account (opens a browser)", methodId: "oauth", }, }, diff --git a/extensions/google/openclaw.plugin.json b/extensions/google/openclaw.plugin.json index 7190d3fb6ffd..dadb42766001 100644 --- a/extensions/google/openclaw.plugin.json +++ b/extensions/google/openclaw.plugin.json @@ -630,6 +630,7 @@ "choiceId": "gemini-api-key", "appGuidedSecret": true, "choiceLabel": "Google Gemini API key", + "choiceHint": "Free API key from aistudio.google.com/apikey", "groupId": "google", "groupLabel": "Google", "groupHint": "Gemini API key + OAuth", @@ -645,7 +646,7 @@ "choiceId": "google-gemini-cli", "appGuidedAuth": "oauth", "choiceLabel": "Gemini CLI OAuth", - "choiceHint": "Google OAuth with project-aware token payload", + "choiceHint": "Sign in with your Google account (opens a browser)", "groupId": "google", "groupLabel": "Google", "groupHint": "Gemini API key + OAuth", diff --git a/extensions/google/provider-contract-api.ts b/extensions/google/provider-contract-api.ts index 87738aeb1416..9fc246ece0ed 100644 --- a/extensions/google/provider-contract-api.ts +++ b/extensions/google/provider-contract-api.ts @@ -15,7 +15,7 @@ export function createGoogleProvider(): ProviderPlugin { id: "api-key", kind: "api_key", label: "Google Gemini API key", - hint: "AI Studio / Gemini API key", + hint: "Free API key from aistudio.google.com/apikey", run: noopAuth, wizard: { choiceId: "gemini-api-key", @@ -70,7 +70,7 @@ export function createGoogleGeminiCliProvider(): ProviderPlugin { setup: { choiceId: "google-gemini-cli", choiceLabel: "Gemini CLI OAuth", - choiceHint: "Google OAuth with project-aware token payload", + choiceHint: "Sign in with your Google account (opens a browser)", methodId: "oauth", }, }, diff --git a/extensions/google/provider-registration.ts b/extensions/google/provider-registration.ts index 81098da7bf12..4a58905f4fe0 100644 --- a/extensions/google/provider-registration.ts +++ b/extensions/google/provider-registration.ts @@ -48,7 +48,7 @@ export function buildGoogleProvider(): ProviderPlugin { providerId: "google", methodId: "api-key", label: "Google Gemini API key", - hint: "AI Studio / Gemini API key", + hint: "Free API key from aistudio.google.com/apikey", optionKey: "geminiApiKey", flagName: "--gemini-api-key", envVar: "GEMINI_API_KEY", diff --git a/scripts/lib/official-external-provider-catalog.json b/scripts/lib/official-external-provider-catalog.json index 093ab0170fd7..314b569f8353 100644 --- a/scripts/lib/official-external-provider-catalog.json +++ b/scripts/lib/official-external-provider-catalog.json @@ -389,7 +389,7 @@ "method": "app-server", "choiceId": "codex", "choiceLabel": "Codex app-server", - "choiceHint": "Use the Codex app-server runtime and managed model catalog.", + "choiceHint": "Keep using your Codex CLI or ChatGPT app sign-in via the Codex app-server runtime.", "assistantPriority": -40, "groupId": "codex", "groupLabel": "Codex", diff --git a/src/commands/onboard-inference.test.ts b/src/commands/onboard-inference.test.ts index ef0a945826d6..6530b01f3e30 100644 --- a/src/commands/onboard-inference.test.ts +++ b/src/commands/onboard-inference.test.ts @@ -131,7 +131,37 @@ describe("detectInferenceBackends", () => { expect(candidates.map((candidate) => candidate.kind)).toEqual(["codex-cli", "claude-cli"]); expect(candidates[0]?.credentials).toBe(true); expect(candidates[1]?.credentials).toBe(false); - expect(candidates[1]?.detail).toBe("installed, not logged in"); + expect(candidates[1]?.detail).toBe( + "installed, not logged in — run `claude auth login`, then check again", + ); + }); + + it("gives each logged-out CLI its sign-in remediation", async () => { + const candidates = await detectInferenceBackends({ + env: {}, + platform: "linux", + deps: { + probeLocalCommand: probeDeps({ claude: true, codex: true, gemini: true }), + readClaudeCliCredentials: () => null, + readCodexCliCredentials: () => null, + readGeminiCliCredentials: () => null, + }, + }); + + expect(candidates).toMatchObject([ + { + kind: "claude-cli", + detail: "installed, not logged in — run `claude auth login`, then check again", + }, + { + kind: "codex-cli", + detail: "installed, not logged in — run `codex login`, then check again", + }, + { + kind: "gemini-cli", + detail: "installed, not logged in — sign in to Gemini CLI, then check again", + }, + ]); }); it("recognizes Codex login status across native credential stores", async () => { diff --git a/src/commands/onboard-inference.ts b/src/commands/onboard-inference.ts index 20fc4cf03ead..7d03bf3b7f7d 100644 --- a/src/commands/onboard-inference.ts +++ b/src/commands/onboard-inference.ts @@ -85,12 +85,12 @@ function detectCliCredentialState(params: { return params.platform === "darwin" ? undefined : false; } -function describeCliDetail(credentials: boolean | undefined): string { +function describeCliDetail(credentials: boolean | undefined, loginHint: string): string { if (credentials === true) { return "logged in"; } if (credentials === false) { - return "installed, not logged in"; + return `installed, not logged in — ${loginHint}, then check again`; } return "installed"; } @@ -247,7 +247,7 @@ export async function detectInferenceBackends( kind: "claude-cli", modelRef: CLAUDE_CLI_DEFAULT_MODEL_REF, label: "Claude Code", - detail: describeCliDetail(credentials), + detail: describeCliDetail(credentials, "run `claude auth login`"), ...(credentials === undefined ? {} : { credentials }), }); } @@ -263,7 +263,7 @@ export async function detectInferenceBackends( kind: "codex-cli", modelRef: CODEX_APP_SERVER_DEFAULT_MODEL_REF, label: "Codex", - detail: describeCliDetail(credentials), + detail: describeCliDetail(credentials, "run `codex login`"), ...(credentials === undefined ? {} : { credentials }), }); } @@ -275,7 +275,7 @@ export async function detectInferenceBackends( kind: "gemini-cli", modelRef: GEMINI_CLI_DEFAULT_MODEL_REF, label: "Gemini CLI", - detail: describeCliDetail(credentials), + detail: describeCliDetail(credentials, "sign in to Gemini CLI"), credentials, }); } diff --git a/src/system-agent/setup-inference.test.ts b/src/system-agent/setup-inference.test.ts index 54088444100c..83ba71a326ab 100644 --- a/src/system-agent/setup-inference.test.ts +++ b/src/system-agent/setup-inference.test.ts @@ -101,7 +101,7 @@ vi.mock("../commands/onboard-inference.js", async (importActual) => { kind: "codex-cli", modelRef: "openai/gpt-5.5", label: "Codex", - detail: "installed, not logged in", + detail: "installed, not logged in — run `codex login`, then check again", credentials: false, }, ]), diff --git a/src/system-agent/setup-inference.ts b/src/system-agent/setup-inference.ts index f44ad41fce1b..a5ff44a5142d 100644 --- a/src/system-agent/setup-inference.ts +++ b/src/system-agent/setup-inference.ts @@ -362,7 +362,8 @@ export async function detectSetupInference( id: candidate.kind, label: candidate.label, detail: candidate.detail, - reason: "Automatic setup cannot enforce a tool-free Gemini CLI probe.", + reason: + "Can't be auto-tested safely here. Use 'Gemini CLI OAuth' or a Gemini API key instead.", })); const antigravity = await (deps.probeLocalCommand ?? probeLocalCommand)("agy"); if (antigravity.found) { @@ -370,7 +371,8 @@ export async function detectSetupInference( id: "antigravity-cli", label: "Antigravity CLI", detail: "installed", - reason: "Automatic setup cannot enforce a tool-free Antigravity probe.", + reason: + "Can't be auto-tested safely here. Sign in with a provider or use an API key instead.", }); } const raw = detected.filter((candidate) => candidate.kind !== "gemini-cli");