feat(auth): sync OAuth from Claude/Codex CLIs

Add source profiles anthropic:claude-cli and openai-codex:codex-cli; surface them in onboarding/configure.

Co-authored-by: pepicrft <pepicrft@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-07 10:47:24 +01:00
parent 0914517ee3
commit 7a917602c5
10 changed files with 629 additions and 40 deletions

View File

@@ -2,6 +2,7 @@ import {
resolveAgentDir,
resolveAgentWorkspaceDir,
} from "../agents/agent-scope.js";
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
import type { ClawdbotConfig } from "../config/config.js";
import {
CONFIG_PATH_CLAWDBOT,
@@ -21,6 +22,7 @@ import { resolveDefaultWhatsAppAccountId } from "../web/accounts.js";
import { createClackPrompter } from "../wizard/clack-prompter.js";
import { WizardCancelledError } from "../wizard/prompts.js";
import { applyAuthChoice, warnIfModelConfigLooksOff } from "./auth-choice.js";
import { buildAuthChoiceOptions } from "./auth-choice-options.js";
import { ensureWorkspaceAndSessions, moveToTrash } from "./onboard-helpers.js";
import { setupProviders } from "./onboard-providers.js";
import type { AuthChoice, ProviderChoice } from "./onboard-types.js";
@@ -458,19 +460,13 @@ export async function agentsAddCommand(
initialValue: false,
});
if (wantsAuth) {
const authStore = ensureAuthProfileStore(agentDir);
const authChoice = (await prompter.select({
message: "Model/auth choice",
options: [
{ value: "oauth", label: "Anthropic OAuth (Claude Pro/Max)" },
{ value: "openai-codex", label: "OpenAI Codex (ChatGPT OAuth)" },
{
value: "antigravity",
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
},
{ value: "apiKey", label: "Anthropic API key" },
{ value: "minimax", label: "Minimax M2.1 (LM Studio)" },
{ value: "skip", label: "Skip for now" },
],
options: buildAuthChoiceOptions({
store: authStore,
includeSkip: true,
}),
})) as AuthChoice;
const authResult = await applyAuthChoice({

View File

@@ -0,0 +1,49 @@
import type { AuthProfileStore } from "../agents/auth-profiles.js";
import {
CLAUDE_CLI_PROFILE_ID,
CODEX_CLI_PROFILE_ID,
} from "../agents/auth-profiles.js";
import type { AuthChoice } from "./onboard-types.js";
export type AuthChoiceOption = { value: AuthChoice; label: string };
export function buildAuthChoiceOptions(params: {
store: AuthProfileStore;
includeSkip: boolean;
}): AuthChoiceOption[] {
const options: AuthChoiceOption[] = [];
const claudeCli = params.store.profiles[CLAUDE_CLI_PROFILE_ID];
if (claudeCli?.type === "oauth") {
options.push({
value: "claude-cli",
label: "Anthropic OAuth (Claude CLI)",
});
}
options.push({ value: "oauth", label: "Anthropic OAuth (Claude Pro/Max)" });
const codexCli = params.store.profiles[CODEX_CLI_PROFILE_ID];
if (codexCli?.type === "oauth") {
options.push({
value: "codex-cli",
label: "OpenAI Codex OAuth (Codex CLI)",
});
}
options.push({
value: "openai-codex",
label: "OpenAI Codex (ChatGPT OAuth)",
});
options.push({
value: "antigravity",
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
});
options.push({ value: "apiKey", label: "Anthropic API key" });
options.push({ value: "minimax", label: "Minimax M2.1 (LM Studio)" });
if (params.includeSkip) {
options.push({ value: "skip", label: "Skip for now" });
}
return options;
}

View File

@@ -6,6 +6,8 @@ import {
} from "@mariozechner/pi-ai";
import { resolveAgentConfig } from "../agents/agent-scope.js";
import {
CLAUDE_CLI_PROFILE_ID,
CODEX_CLI_PROFILE_ID,
ensureAuthProfileStore,
listProfilesForProvider,
} from "../agents/auth-profiles.js";
@@ -165,6 +167,20 @@ export async function applyAuthChoice(params: {
"OAuth help",
);
}
} else if (params.authChoice === "claude-cli") {
const store = ensureAuthProfileStore(params.agentDir);
if (!store.profiles[CLAUDE_CLI_PROFILE_ID]) {
await params.prompter.note(
"No Claude CLI credentials found at ~/.claude/.credentials.json.",
"Claude CLI OAuth",
);
return { config: nextConfig, agentModelOverride };
}
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: CLAUDE_CLI_PROFILE_ID,
provider: "anthropic",
mode: "oauth",
});
} else if (params.authChoice === "openai-codex") {
const isRemote = isRemoteEnvironment();
await params.prompter.note(
@@ -250,6 +266,33 @@ export async function applyAuthChoice(params: {
"OAuth help",
);
}
} else if (params.authChoice === "codex-cli") {
const store = ensureAuthProfileStore(params.agentDir);
if (!store.profiles[CODEX_CLI_PROFILE_ID]) {
await params.prompter.note(
"No Codex CLI credentials found at ~/.codex/auth.json.",
"Codex CLI OAuth",
);
return { config: nextConfig, agentModelOverride };
}
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: CODEX_CLI_PROFILE_ID,
provider: "openai-codex",
mode: "oauth",
});
if (params.setDefaultModel) {
const applied = applyOpenAICodexModelDefault(nextConfig);
nextConfig = applied.next;
if (applied.changed) {
await params.prompter.note(
`Default model set to ${OPENAI_CODEX_DEFAULT_MODEL}`,
"Model configured",
);
}
} else {
agentModelOverride = OPENAI_CODEX_DEFAULT_MODEL;
await noteAgentModel(OPENAI_CODEX_DEFAULT_MODEL);
}
} else if (params.authChoice === "antigravity") {
const isRemote = isRemoteEnvironment();
await params.prompter.note(

View File

@@ -16,6 +16,11 @@ import {
type OAuthCredentials,
type OAuthProvider,
} from "@mariozechner/pi-ai";
import {
CLAUDE_CLI_PROFILE_ID,
CODEX_CLI_PROFILE_ID,
ensureAuthProfileStore,
} from "../agents/auth-profiles.js";
import type { ClawdbotConfig } from "../config/config.js";
import {
CONFIG_PATH_CLAWDBOT,
@@ -35,6 +40,7 @@ import {
isRemoteEnvironment,
loginAntigravityVpsAware,
} from "./antigravity-oauth.js";
import { buildAuthChoiceOptions } from "./auth-choice-options.js";
import {
DEFAULT_GATEWAY_DAEMON_RUNTIME,
GATEWAY_DAEMON_RUNTIME_OPTIONS,
@@ -254,20 +260,21 @@ async function promptAuthConfig(
const authChoice = guardCancel(
await select({
message: "Model/auth choice",
options: [
{ value: "oauth", label: "Anthropic OAuth (Claude Pro/Max)" },
{ value: "openai-codex", label: "OpenAI Codex (ChatGPT OAuth)" },
{
value: "antigravity",
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
},
{ value: "apiKey", label: "Anthropic API key" },
{ value: "minimax", label: "Minimax M2.1 (LM Studio)" },
{ value: "skip", label: "Skip for now" },
],
options: buildAuthChoiceOptions({
store: ensureAuthProfileStore(),
includeSkip: true,
}),
}),
runtime,
) as "oauth" | "openai-codex" | "antigravity" | "apiKey" | "minimax" | "skip";
) as
| "oauth"
| "claude-cli"
| "openai-codex"
| "codex-cli"
| "antigravity"
| "apiKey"
| "minimax"
| "skip";
let next = cfg;
@@ -312,6 +319,12 @@ async function promptAuthConfig(
runtime.error(String(err));
note("Trouble with OAuth? See https://docs.clawd.bot/start/faq", "OAuth");
}
} else if (authChoice === "claude-cli") {
next = applyAuthProfileConfig(next, {
profileId: CLAUDE_CLI_PROFILE_ID,
provider: "anthropic",
mode: "oauth",
});
} else if (authChoice === "openai-codex") {
const isRemote = isRemoteEnvironment();
note(
@@ -386,6 +399,20 @@ async function promptAuthConfig(
runtime.error(String(err));
note("Trouble with OAuth? See https://docs.clawd.bot/start/faq", "OAuth");
}
} else if (authChoice === "codex-cli") {
next = applyAuthProfileConfig(next, {
profileId: CODEX_CLI_PROFILE_ID,
provider: "openai-codex",
mode: "oauth",
});
const applied = applyOpenAICodexModelDefault(next);
next = applied.next;
if (applied.changed) {
note(
`Default model set to ${OPENAI_CODEX_DEFAULT_MODEL}`,
"Model configured",
);
}
} else if (authChoice === "antigravity") {
const isRemote = isRemoteEnvironment();
note(

View File

@@ -1,5 +1,9 @@
import path from "node:path";
import {
CLAUDE_CLI_PROFILE_ID,
CODEX_CLI_PROFILE_ID,
ensureAuthProfileStore,
} from "../agents/auth-profiles.js";
import {
type ClawdbotConfig,
CONFIG_PATH_CLAWDBOT,
@@ -30,6 +34,7 @@ import {
randomToken,
} from "./onboard-helpers.js";
import type { AuthChoice, OnboardOptions } from "./onboard-types.js";
import { applyOpenAICodexModelDefault } from "./openai-codex-model-default.js";
import { ensureSystemdUserLingerNonInteractive } from "./systemd-linger.js";
export async function runNonInteractiveOnboarding(
@@ -112,6 +117,33 @@ export async function runNonInteractiveOnboarding(
provider: "anthropic",
mode: "api_key",
});
} else if (authChoice === "claude-cli") {
const store = ensureAuthProfileStore();
if (!store.profiles[CLAUDE_CLI_PROFILE_ID]) {
runtime.error(
"No Claude CLI credentials found at ~/.claude/.credentials.json",
);
runtime.exit(1);
return;
}
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: CLAUDE_CLI_PROFILE_ID,
provider: "anthropic",
mode: "oauth",
});
} else if (authChoice === "codex-cli") {
const store = ensureAuthProfileStore();
if (!store.profiles[CODEX_CLI_PROFILE_ID]) {
runtime.error("No Codex CLI credentials found at ~/.codex/auth.json");
runtime.exit(1);
return;
}
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: CODEX_CLI_PROFILE_ID,
provider: "openai-codex",
mode: "oauth",
});
nextConfig = applyOpenAICodexModelDefault(nextConfig).next;
} else if (authChoice === "minimax") {
nextConfig = applyMinimaxConfig(nextConfig);
} else if (

View File

@@ -3,7 +3,9 @@ import type { GatewayDaemonRuntime } from "./daemon-runtime.js";
export type OnboardMode = "local" | "remote";
export type AuthChoice =
| "oauth"
| "claude-cli"
| "openai-codex"
| "codex-cli"
| "antigravity"
| "apiKey"
| "minimax"