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
This commit is contained in:
Peter Steinberger
2026-07-16 01:12:15 -07:00
committed by GitHub
parent 555c1a4e6a
commit e2fed32131
16 changed files with 84 additions and 27 deletions
+1 -1
View File
@@ -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
+12
View File
@@ -127,6 +127,18 @@ OpenClaw release:
[OpenAI Codex](/providers/openai).
</Warning>
### 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:
+8 -1
View File
@@ -22,6 +22,9 @@ Choose your preferred auth method and follow the setup steps.
**Best for:** standard Gemini API access through Google AI Studio.
<Steps>
<Step title="Get an API key">
Create a free key in [Google AI Studio](https://aistudio.google.com/apikey).
</Step>
<Step title="Run onboarding">
```bash
openclaw onboard --auth-choice gemini-api-key
@@ -61,7 +64,7 @@ Choose your preferred auth method and follow the setup steps.
</Tab>
<Tab title="Gemini CLI (OAuth)">
**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.
<Warning>
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`.
</Note>
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.
+5
View File
@@ -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.
+2 -2
View File
@@ -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",
@@ -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",
+4 -4
View File
@@ -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",
+1 -1
View File
@@ -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",
},
},
+2 -1
View File
@@ -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",
+2 -2
View File
@@ -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",
},
},
+1 -1
View File
@@ -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",
@@ -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",
+31 -1
View File
@@ -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 () => {
+5 -5
View File
@@ -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,
});
}
+1 -1
View File
@@ -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,
},
]),
+4 -2
View File
@@ -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");