mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 04:51:25 +00:00
refactor(auth)!: remove external CLI OAuth reuse
This commit is contained in:
@@ -258,7 +258,6 @@ export async function agentsAddCommand(
|
||||
prompter,
|
||||
store: authStore,
|
||||
includeSkip: true,
|
||||
includeClaudeCliIfMissing: true,
|
||||
});
|
||||
|
||||
const authResult = await applyAuthChoice({
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import type { AuthProfileStore } from "../agents/auth-profiles.js";
|
||||
import { CLAUDE_CLI_PROFILE_ID, CODEX_CLI_PROFILE_ID } from "../agents/auth-profiles.js";
|
||||
import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||
import type { AuthChoice } from "./onboard-types.js";
|
||||
|
||||
export type AuthChoiceOption = {
|
||||
@@ -41,13 +39,13 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
||||
value: "openai",
|
||||
label: "OpenAI",
|
||||
hint: "Codex OAuth + API key",
|
||||
choices: ["codex-cli", "openai-codex", "openai-api-key"],
|
||||
choices: ["openai-codex", "openai-api-key"],
|
||||
},
|
||||
{
|
||||
value: "anthropic",
|
||||
label: "Anthropic",
|
||||
hint: "Claude Code CLI + API key",
|
||||
choices: ["token", "claude-cli", "apiKey"],
|
||||
hint: "setup-token + API key",
|
||||
choices: ["token", "apiKey"],
|
||||
},
|
||||
{
|
||||
value: "minimax",
|
||||
@@ -117,65 +115,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
||||
},
|
||||
];
|
||||
|
||||
function formatOAuthHint(expires?: number, opts?: { allowStale?: boolean }): string {
|
||||
const rich = isRich();
|
||||
if (!expires) {
|
||||
return colorize(rich, theme.muted, "token unavailable");
|
||||
}
|
||||
const now = Date.now();
|
||||
const remaining = expires - now;
|
||||
if (remaining <= 0) {
|
||||
if (opts?.allowStale) {
|
||||
return colorize(rich, theme.warn, "token present · refresh on use");
|
||||
}
|
||||
return colorize(rich, theme.error, "token expired");
|
||||
}
|
||||
const minutes = Math.round(remaining / (60 * 1000));
|
||||
const duration =
|
||||
minutes >= 120
|
||||
? `${Math.round(minutes / 60)}h`
|
||||
: minutes >= 60
|
||||
? "1h"
|
||||
: `${Math.max(minutes, 1)}m`;
|
||||
const label = `token ok · expires in ${duration}`;
|
||||
if (minutes <= 10) {
|
||||
return colorize(rich, theme.warn, label);
|
||||
}
|
||||
return colorize(rich, theme.success, label);
|
||||
}
|
||||
|
||||
export function buildAuthChoiceOptions(params: {
|
||||
store: AuthProfileStore;
|
||||
includeSkip: boolean;
|
||||
includeClaudeCliIfMissing?: boolean;
|
||||
platform?: NodeJS.Platform;
|
||||
}): AuthChoiceOption[] {
|
||||
void params.store;
|
||||
const options: AuthChoiceOption[] = [];
|
||||
const platform = params.platform ?? process.platform;
|
||||
|
||||
const codexCli = params.store.profiles[CODEX_CLI_PROFILE_ID];
|
||||
if (codexCli?.type === "oauth") {
|
||||
options.push({
|
||||
value: "codex-cli",
|
||||
label: "OpenAI Codex OAuth (Codex CLI)",
|
||||
hint: formatOAuthHint(codexCli.expires, { allowStale: true }),
|
||||
});
|
||||
}
|
||||
|
||||
const claudeCli = params.store.profiles[CLAUDE_CLI_PROFILE_ID];
|
||||
if (claudeCli?.type === "oauth" || claudeCli?.type === "token") {
|
||||
options.push({
|
||||
value: "claude-cli",
|
||||
label: "Anthropic token (Claude Code CLI)",
|
||||
hint: `reuses existing Claude Code auth · ${formatOAuthHint(claudeCli.expires)}`,
|
||||
});
|
||||
} else if (params.includeClaudeCliIfMissing && platform === "darwin") {
|
||||
options.push({
|
||||
value: "claude-cli",
|
||||
label: "Anthropic token (Claude Code CLI)",
|
||||
hint: "reuses existing Claude Code auth · requires Keychain access",
|
||||
});
|
||||
}
|
||||
|
||||
options.push({
|
||||
value: "token",
|
||||
@@ -245,12 +190,7 @@ export function buildAuthChoiceOptions(params: {
|
||||
return options;
|
||||
}
|
||||
|
||||
export function buildAuthChoiceGroups(params: {
|
||||
store: AuthProfileStore;
|
||||
includeSkip: boolean;
|
||||
includeClaudeCliIfMissing?: boolean;
|
||||
platform?: NodeJS.Platform;
|
||||
}): {
|
||||
export function buildAuthChoiceGroups(params: { store: AuthProfileStore; includeSkip: boolean }): {
|
||||
groups: AuthChoiceGroup[];
|
||||
skipOption?: AuthChoiceOption;
|
||||
} {
|
||||
|
||||
@@ -9,8 +9,6 @@ export async function promptAuthChoiceGrouped(params: {
|
||||
prompter: WizardPrompter;
|
||||
store: AuthProfileStore;
|
||||
includeSkip: boolean;
|
||||
includeClaudeCliIfMissing?: boolean;
|
||||
platform?: NodeJS.Platform;
|
||||
}): Promise<AuthChoice> {
|
||||
const { groups, skipOption } = buildAuthChoiceGroups(params);
|
||||
const availableGroups = groups.filter((group) => group.options.length > 0);
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
CLAUDE_CLI_PROFILE_ID,
|
||||
ensureAuthProfileStore,
|
||||
upsertAuthProfile,
|
||||
} from "../agents/auth-profiles.js";
|
||||
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||
import {
|
||||
formatApiKeyPreview,
|
||||
normalizeApiKeyInput,
|
||||
@@ -15,153 +11,17 @@ import { applyAuthProfileConfig, setAnthropicApiKey } from "./onboard-auth.js";
|
||||
export async function applyAuthChoiceAnthropic(
|
||||
params: ApplyAuthChoiceParams,
|
||||
): Promise<ApplyAuthChoiceResult | null> {
|
||||
if (params.authChoice === "claude-cli") {
|
||||
if (
|
||||
params.authChoice === "setup-token" ||
|
||||
params.authChoice === "oauth" ||
|
||||
params.authChoice === "token"
|
||||
) {
|
||||
let nextConfig = params.config;
|
||||
const store = ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
const hasClaudeCli = Boolean(store.profiles[CLAUDE_CLI_PROFILE_ID]);
|
||||
if (!hasClaudeCli && process.platform === "darwin") {
|
||||
await params.prompter.note(
|
||||
[
|
||||
"macOS will show a Keychain prompt next.",
|
||||
'Choose "Always Allow" so the launchd gateway can start without prompts.',
|
||||
'If you choose "Allow" or "Deny", each restart will block on a Keychain alert.',
|
||||
].join("\n"),
|
||||
"Claude Code CLI Keychain",
|
||||
);
|
||||
const proceed = await params.prompter.confirm({
|
||||
message: "Check Keychain for Claude Code CLI credentials now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (!proceed) return { config: nextConfig };
|
||||
}
|
||||
|
||||
const storeWithKeychain = hasClaudeCli
|
||||
? store
|
||||
: ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: true,
|
||||
});
|
||||
|
||||
if (!storeWithKeychain.profiles[CLAUDE_CLI_PROFILE_ID]) {
|
||||
if (process.stdin.isTTY) {
|
||||
const runNow = await params.prompter.confirm({
|
||||
message: "Run `claude setup-token` now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (runNow) {
|
||||
const res = await (async () => {
|
||||
const { spawnSync } = await import("node:child_process");
|
||||
return spawnSync("claude", ["setup-token"], { stdio: "inherit" });
|
||||
})();
|
||||
if (res.error) {
|
||||
await params.prompter.note(
|
||||
`Failed to run claude: ${String(res.error)}`,
|
||||
"Claude setup-token",
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await params.prompter.note(
|
||||
"`claude setup-token` requires an interactive TTY.",
|
||||
"Claude setup-token",
|
||||
);
|
||||
}
|
||||
|
||||
const refreshed = ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: true,
|
||||
});
|
||||
if (!refreshed.profiles[CLAUDE_CLI_PROFILE_ID]) {
|
||||
await params.prompter.note(
|
||||
process.platform === "darwin"
|
||||
? 'No Claude Code CLI credentials found in Keychain ("Claude Code-credentials") or ~/.claude/.credentials.json.'
|
||||
: "No Claude Code CLI credentials found at ~/.claude/.credentials.json.",
|
||||
"Claude Code CLI OAuth",
|
||||
);
|
||||
return { config: nextConfig };
|
||||
}
|
||||
}
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
provider: "anthropic",
|
||||
mode: "oauth",
|
||||
});
|
||||
return { config: nextConfig };
|
||||
}
|
||||
|
||||
if (params.authChoice === "setup-token" || params.authChoice === "oauth") {
|
||||
let nextConfig = params.config;
|
||||
await params.prompter.note(
|
||||
[
|
||||
"This will run `claude setup-token` to create a long-lived Anthropic token.",
|
||||
"Requires an interactive TTY and a Claude Pro/Max subscription.",
|
||||
].join("\n"),
|
||||
"Anthropic setup-token",
|
||||
);
|
||||
|
||||
if (!process.stdin.isTTY) {
|
||||
await params.prompter.note(
|
||||
"`claude setup-token` requires an interactive TTY.",
|
||||
"Anthropic setup-token",
|
||||
);
|
||||
return { config: nextConfig };
|
||||
}
|
||||
|
||||
const proceed = await params.prompter.confirm({
|
||||
message: "Run `claude setup-token` now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (!proceed) return { config: nextConfig };
|
||||
|
||||
const res = await (async () => {
|
||||
const { spawnSync } = await import("node:child_process");
|
||||
return spawnSync("claude", ["setup-token"], { stdio: "inherit" });
|
||||
})();
|
||||
if (res.error) {
|
||||
await params.prompter.note(
|
||||
`Failed to run claude: ${String(res.error)}`,
|
||||
"Anthropic setup-token",
|
||||
);
|
||||
return { config: nextConfig };
|
||||
}
|
||||
if (typeof res.status === "number" && res.status !== 0) {
|
||||
await params.prompter.note(
|
||||
`claude setup-token failed (exit ${res.status})`,
|
||||
"Anthropic setup-token",
|
||||
);
|
||||
return { config: nextConfig };
|
||||
}
|
||||
|
||||
const store = ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: true,
|
||||
});
|
||||
if (!store.profiles[CLAUDE_CLI_PROFILE_ID]) {
|
||||
await params.prompter.note(
|
||||
`No Claude Code CLI credentials found after setup-token. Expected ${CLAUDE_CLI_PROFILE_ID}.`,
|
||||
"Anthropic setup-token",
|
||||
);
|
||||
return { config: nextConfig };
|
||||
}
|
||||
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
provider: "anthropic",
|
||||
mode: "oauth",
|
||||
});
|
||||
return { config: nextConfig };
|
||||
}
|
||||
|
||||
if (params.authChoice === "token") {
|
||||
let nextConfig = params.config;
|
||||
const provider = (await params.prompter.select({
|
||||
message: "Token provider",
|
||||
options: [{ value: "anthropic", label: "Anthropic (only supported)" }],
|
||||
})) as "anthropic";
|
||||
await params.prompter.note(
|
||||
["Run `claude setup-token` in your terminal.", "Then paste the generated token below."].join(
|
||||
"\n",
|
||||
),
|
||||
"Anthropic token",
|
||||
"Anthropic setup-token",
|
||||
);
|
||||
|
||||
const tokenRaw = await params.prompter.text({
|
||||
@@ -174,6 +34,7 @@ export async function applyAuthChoiceAnthropic(
|
||||
message: "Token name (blank = default)",
|
||||
placeholder: "default",
|
||||
});
|
||||
const provider = "anthropic";
|
||||
const namedProfileId = buildTokenProfileId({
|
||||
provider,
|
||||
name: String(profileNameRaw ?? ""),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { loginOpenAICodex } from "@mariozechner/pi-ai";
|
||||
import { CODEX_CLI_PROFILE_ID, ensureAuthProfileStore } from "../agents/auth-profiles.js";
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import { upsertSharedEnvVar } from "../infra/env-file.js";
|
||||
import { isRemoteEnvironment } from "./oauth-env.js";
|
||||
@@ -146,45 +145,5 @@ export async function applyAuthChoiceOpenAI(
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
if (params.authChoice === "codex-cli") {
|
||||
let nextConfig = params.config;
|
||||
let agentModelOverride: string | undefined;
|
||||
const noteAgentModel = async (model: string) => {
|
||||
if (!params.agentId) return;
|
||||
await params.prompter.note(
|
||||
`Default model set to ${model} for agent "${params.agentId}".`,
|
||||
"Model configured",
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
CLAUDE_CLI_PROFILE_ID,
|
||||
CODEX_CLI_PROFILE_ID,
|
||||
loadAuthProfileStore,
|
||||
} from "../../agents/auth-profiles.js";
|
||||
import { loadAuthProfileStore } from "../../agents/auth-profiles.js";
|
||||
import { listChannelPlugins } from "../../channels/plugins/index.js";
|
||||
import { buildChannelAccountSnapshot } from "../../channels/plugins/status.js";
|
||||
import type { ChannelAccountSnapshot, ChannelPlugin } from "../../channels/plugins/types.js";
|
||||
@@ -115,7 +111,7 @@ export async function channelsListCommand(
|
||||
id: profileId,
|
||||
provider: profile.provider,
|
||||
type: profile.type,
|
||||
isExternal: profileId === CLAUDE_CLI_PROFILE_ID || profileId === CODEX_CLI_PROFILE_ID,
|
||||
isExternal: false,
|
||||
}));
|
||||
if (opts.json) {
|
||||
const usage = includeUsage ? await loadProviderUsageSummary() : undefined;
|
||||
|
||||
@@ -47,7 +47,6 @@ export async function promptAuthConfig(
|
||||
allowKeychainPrompt: false,
|
||||
}),
|
||||
includeSkip: true,
|
||||
includeClaudeCliIfMissing: true,
|
||||
});
|
||||
|
||||
let next = cfg;
|
||||
@@ -74,10 +73,7 @@ export async function promptAuthConfig(
|
||||
}
|
||||
|
||||
const anthropicOAuth =
|
||||
authChoice === "claude-cli" ||
|
||||
authChoice === "setup-token" ||
|
||||
authChoice === "token" ||
|
||||
authChoice === "oauth";
|
||||
authChoice === "setup-token" || authChoice === "token" || authChoice === "oauth";
|
||||
|
||||
const allowlistSelection = await promptModelAllowlist({
|
||||
config: next,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
resolveApiKeyForProfile,
|
||||
resolveProfileUnusableUntilForDisplay,
|
||||
} from "../agents/auth-profiles.js";
|
||||
import { updateAuthProfileStoreWithLock } from "../agents/auth-profiles/store.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
@@ -38,6 +39,148 @@ export async function maybeRepairAnthropicOAuthProfileId(
|
||||
return repair.config;
|
||||
}
|
||||
|
||||
function pruneAuthOrder(
|
||||
order: Record<string, string[]> | undefined,
|
||||
profileIds: Set<string>,
|
||||
): { next: Record<string, string[]> | undefined; changed: boolean } {
|
||||
if (!order) return { next: order, changed: false };
|
||||
let changed = false;
|
||||
const next: Record<string, string[]> = {};
|
||||
for (const [provider, list] of Object.entries(order)) {
|
||||
const filtered = list.filter((id) => !profileIds.has(id));
|
||||
if (filtered.length !== list.length) changed = true;
|
||||
if (filtered.length > 0) next[provider] = filtered;
|
||||
}
|
||||
return { next: Object.keys(next).length > 0 ? next : undefined, changed };
|
||||
}
|
||||
|
||||
function pruneAuthProfiles(
|
||||
cfg: ClawdbotConfig,
|
||||
profileIds: Set<string>,
|
||||
): { next: ClawdbotConfig; changed: boolean } {
|
||||
const profiles = cfg.auth?.profiles;
|
||||
const order = cfg.auth?.order;
|
||||
const nextProfiles = profiles ? { ...profiles } : undefined;
|
||||
let changed = false;
|
||||
|
||||
if (nextProfiles) {
|
||||
for (const id of profileIds) {
|
||||
if (id in nextProfiles) {
|
||||
delete nextProfiles[id];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prunedOrder = pruneAuthOrder(order, profileIds);
|
||||
if (prunedOrder.changed) changed = true;
|
||||
|
||||
if (!changed) return { next: cfg, changed: false };
|
||||
|
||||
const nextAuth =
|
||||
nextProfiles || prunedOrder.next
|
||||
? {
|
||||
...cfg.auth,
|
||||
profiles: nextProfiles && Object.keys(nextProfiles).length > 0 ? nextProfiles : undefined,
|
||||
order: prunedOrder.next,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
next: {
|
||||
...cfg,
|
||||
auth: nextAuth,
|
||||
},
|
||||
changed: true,
|
||||
};
|
||||
}
|
||||
|
||||
export async function maybeRemoveDeprecatedCliAuthProfiles(
|
||||
cfg: ClawdbotConfig,
|
||||
prompter: DoctorPrompter,
|
||||
): Promise<ClawdbotConfig> {
|
||||
const store = ensureAuthProfileStore(undefined, { allowKeychainPrompt: false });
|
||||
const deprecated = new Set<string>();
|
||||
if (store.profiles[CLAUDE_CLI_PROFILE_ID] || cfg.auth?.profiles?.[CLAUDE_CLI_PROFILE_ID]) {
|
||||
deprecated.add(CLAUDE_CLI_PROFILE_ID);
|
||||
}
|
||||
if (store.profiles[CODEX_CLI_PROFILE_ID] || cfg.auth?.profiles?.[CODEX_CLI_PROFILE_ID]) {
|
||||
deprecated.add(CODEX_CLI_PROFILE_ID);
|
||||
}
|
||||
|
||||
if (deprecated.size === 0) return cfg;
|
||||
|
||||
const lines = ["Deprecated external CLI auth profiles detected (no longer supported):"];
|
||||
if (deprecated.has(CLAUDE_CLI_PROFILE_ID)) {
|
||||
lines.push(
|
||||
`- ${CLAUDE_CLI_PROFILE_ID} (Anthropic): use setup-token → ${formatCliCommand("clawdbot models auth setup-token")}`,
|
||||
);
|
||||
}
|
||||
if (deprecated.has(CODEX_CLI_PROFILE_ID)) {
|
||||
lines.push(
|
||||
`- ${CODEX_CLI_PROFILE_ID} (OpenAI Codex): use OAuth → ${formatCliCommand(
|
||||
"clawdbot models auth login --provider openai-codex",
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
note(lines.join("\n"), "Auth profiles");
|
||||
|
||||
const shouldRemove = await prompter.confirmRepair({
|
||||
message: "Remove deprecated CLI auth profiles now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (!shouldRemove) return cfg;
|
||||
|
||||
await updateAuthProfileStoreWithLock({
|
||||
updater: (nextStore) => {
|
||||
let mutated = false;
|
||||
for (const id of deprecated) {
|
||||
if (nextStore.profiles[id]) {
|
||||
delete nextStore.profiles[id];
|
||||
mutated = true;
|
||||
}
|
||||
if (nextStore.usageStats?.[id]) {
|
||||
delete nextStore.usageStats[id];
|
||||
mutated = true;
|
||||
}
|
||||
}
|
||||
if (nextStore.order) {
|
||||
for (const [provider, list] of Object.entries(nextStore.order)) {
|
||||
const filtered = list.filter((id) => !deprecated.has(id));
|
||||
if (filtered.length !== list.length) {
|
||||
mutated = true;
|
||||
if (filtered.length > 0) {
|
||||
nextStore.order[provider] = filtered;
|
||||
} else {
|
||||
delete nextStore.order[provider];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextStore.lastGood) {
|
||||
for (const [provider, profileId] of Object.entries(nextStore.lastGood)) {
|
||||
if (deprecated.has(profileId)) {
|
||||
delete nextStore.lastGood[provider];
|
||||
mutated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mutated;
|
||||
},
|
||||
});
|
||||
|
||||
const pruned = pruneAuthProfiles(cfg, deprecated);
|
||||
if (pruned.changed) {
|
||||
note(
|
||||
Array.from(deprecated.values())
|
||||
.map((id) => `- removed ${id} from config`)
|
||||
.join("\n"),
|
||||
"Doctor changes",
|
||||
);
|
||||
}
|
||||
return pruned.next;
|
||||
}
|
||||
|
||||
type AuthIssue = {
|
||||
profileId: string;
|
||||
provider: string;
|
||||
@@ -47,10 +190,14 @@ type AuthIssue = {
|
||||
|
||||
function formatAuthIssueHint(issue: AuthIssue): string | null {
|
||||
if (issue.provider === "anthropic" && issue.profileId === CLAUDE_CLI_PROFILE_ID) {
|
||||
return "Run `claude setup-token` on the gateway host.";
|
||||
return `Deprecated profile. Use ${formatCliCommand("clawdbot models auth setup-token")} or ${formatCliCommand(
|
||||
"clawdbot configure",
|
||||
)}.`;
|
||||
}
|
||||
if (issue.provider === "openai-codex" && issue.profileId === CODEX_CLI_PROFILE_ID) {
|
||||
return `Run \`codex login\` (or \`${formatCliCommand("clawdbot configure")}\` → OpenAI Codex OAuth).`;
|
||||
return `Deprecated profile. Use ${formatCliCommand(
|
||||
"clawdbot models auth login --provider openai-codex",
|
||||
)} or ${formatCliCommand("clawdbot configure")}.`;
|
||||
}
|
||||
return `Re-auth via \`${formatCliCommand("clawdbot configure")}\` or \`${formatCliCommand("clawdbot onboard")}\`.`;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,11 @@ import { defaultRuntime } from "../runtime.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
import { maybeRepairAnthropicOAuthProfileId, noteAuthProfileHealth } from "./doctor-auth.js";
|
||||
import {
|
||||
maybeRemoveDeprecatedCliAuthProfiles,
|
||||
maybeRepairAnthropicOAuthProfileId,
|
||||
noteAuthProfileHealth,
|
||||
} from "./doctor-auth.js";
|
||||
import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js";
|
||||
import { maybeRepairGatewayDaemon } from "./doctor-gateway-daemon-flow.js";
|
||||
import { checkGatewayHealth } from "./doctor-gateway-health.js";
|
||||
@@ -104,6 +108,7 @@ export async function doctorCommand(
|
||||
}
|
||||
|
||||
cfg = await maybeRepairAnthropicOAuthProfileId(cfg, prompter);
|
||||
cfg = await maybeRemoveDeprecatedCliAuthProfiles(cfg, prompter);
|
||||
await noteAuthProfileHealth({
|
||||
cfg,
|
||||
prompter,
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
import { confirm as clackConfirm, select as clackSelect, text as clackText } from "@clack/prompts";
|
||||
|
||||
import {
|
||||
CLAUDE_CLI_PROFILE_ID,
|
||||
ensureAuthProfileStore,
|
||||
upsertAuthProfile,
|
||||
} from "../../agents/auth-profiles.js";
|
||||
import { upsertAuthProfile } from "../../agents/auth-profiles.js";
|
||||
import { normalizeProviderId } from "../../agents/model-selection.js";
|
||||
import {
|
||||
resolveAgentDir,
|
||||
@@ -33,6 +27,7 @@ import type {
|
||||
ProviderPlugin,
|
||||
} from "../../plugins/types.js";
|
||||
import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js";
|
||||
import { validateAnthropicSetupToken } from "../auth-token.js";
|
||||
|
||||
const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
|
||||
clackConfirm({
|
||||
@@ -73,9 +68,7 @@ export async function modelsAuthSetupTokenCommand(
|
||||
) {
|
||||
const provider = resolveTokenProvider(opts.provider ?? "anthropic");
|
||||
if (provider !== "anthropic") {
|
||||
throw new Error(
|
||||
"Only --provider anthropic is supported for setup-token (uses `claude setup-token`).",
|
||||
);
|
||||
throw new Error("Only --provider anthropic is supported for setup-token.");
|
||||
}
|
||||
|
||||
if (!process.stdin.isTTY) {
|
||||
@@ -84,38 +77,38 @@ export async function modelsAuthSetupTokenCommand(
|
||||
|
||||
if (!opts.yes) {
|
||||
const proceed = await confirm({
|
||||
message: "Run `claude setup-token` now?",
|
||||
message: "Have you run `claude setup-token` and copied the token?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (!proceed) return;
|
||||
}
|
||||
|
||||
const res = spawnSync("claude", ["setup-token"], { stdio: "inherit" });
|
||||
if (res.error) throw res.error;
|
||||
if (typeof res.status === "number" && res.status !== 0) {
|
||||
throw new Error(`claude setup-token failed (exit ${res.status})`);
|
||||
}
|
||||
|
||||
const store = ensureAuthProfileStore(undefined, {
|
||||
allowKeychainPrompt: true,
|
||||
const tokenInput = await text({
|
||||
message: "Paste Anthropic setup-token",
|
||||
validate: (value) => validateAnthropicSetupToken(String(value ?? "")),
|
||||
});
|
||||
const token = String(tokenInput).trim();
|
||||
const profileId = resolveDefaultTokenProfileId(provider);
|
||||
|
||||
upsertAuthProfile({
|
||||
profileId,
|
||||
credential: {
|
||||
type: "token",
|
||||
provider,
|
||||
token,
|
||||
},
|
||||
});
|
||||
const synced = store.profiles[CLAUDE_CLI_PROFILE_ID];
|
||||
if (!synced) {
|
||||
throw new Error(
|
||||
`No Claude Code CLI credentials found after setup-token. Expected auth profile ${CLAUDE_CLI_PROFILE_ID}.`,
|
||||
);
|
||||
}
|
||||
|
||||
await updateConfig((cfg) =>
|
||||
applyAuthProfileConfig(cfg, {
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
provider: "anthropic",
|
||||
mode: "oauth",
|
||||
profileId,
|
||||
provider,
|
||||
mode: "token",
|
||||
}),
|
||||
);
|
||||
|
||||
logConfigUpdated(runtime);
|
||||
runtime.log(`Auth profile: ${CLAUDE_CLI_PROFILE_ID} (anthropic/oauth)`);
|
||||
runtime.log(`Auth profile: ${profileId} (${provider}/token)`);
|
||||
}
|
||||
|
||||
export async function modelsAuthPasteTokenCommand(
|
||||
@@ -189,7 +182,7 @@ export async function modelsAuthAddCommand(_opts: Record<string, never>, runtime
|
||||
{
|
||||
value: "setup-token",
|
||||
label: "setup-token (claude)",
|
||||
hint: "Runs `claude setup-token` (recommended)",
|
||||
hint: "Paste a setup-token from `claude setup-token`",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
@@ -487,7 +487,7 @@ export async function modelsStatusCommand(
|
||||
for (const provider of missingProvidersInUse) {
|
||||
const hint =
|
||||
provider === "anthropic"
|
||||
? `Run \`claude setup-token\` or \`${formatCliCommand("clawdbot configure")}\`.`
|
||||
? `Run \`claude setup-token\`, then \`${formatCliCommand("clawdbot models auth setup-token")}\` or \`${formatCliCommand("clawdbot configure")}\`.`
|
||||
: `Run \`${formatCliCommand("clawdbot configure")}\` or set an API key env var.`;
|
||||
runtime.log(`- ${theme.heading(provider)} ${hint}`);
|
||||
}
|
||||
@@ -558,9 +558,7 @@ export async function modelsStatusCommand(
|
||||
: profile.expiresAt
|
||||
? ` expires in ${formatRemainingShort(profile.remainingMs)}`
|
||||
: " expires unknown";
|
||||
const source =
|
||||
profile.source !== "store" ? colorize(rich, theme.muted, ` (${profile.source})`) : "";
|
||||
runtime.log(` - ${label} ${status}${expiry}${source}`);
|
||||
runtime.log(` - ${label} ${status}${expiry}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
CLAUDE_CLI_PROFILE_ID,
|
||||
CODEX_CLI_PROFILE_ID,
|
||||
ensureAuthProfileStore,
|
||||
upsertAuthProfile,
|
||||
} from "../../../agents/auth-profiles.js";
|
||||
import { upsertAuthProfile } from "../../../agents/auth-profiles.js";
|
||||
import { normalizeProviderId } from "../../../agents/model-selection.js";
|
||||
import { parseDurationMs } from "../../../cli/parse-duration.js";
|
||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||
@@ -36,7 +31,6 @@ import {
|
||||
setZaiApiKey,
|
||||
} from "../../onboard-auth.js";
|
||||
import type { AuthChoice, OnboardOptions } from "../../onboard-types.js";
|
||||
import { applyOpenAICodexModelDefault } from "../../openai-codex-model-default.js";
|
||||
import { resolveNonInteractiveApiKey } from "../api-keys.js";
|
||||
import { shortenHomePath } from "../../../utils.js";
|
||||
|
||||
@@ -50,6 +44,28 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
const { authChoice, opts, runtime, baseConfig } = params;
|
||||
let nextConfig = params.nextConfig;
|
||||
|
||||
if (authChoice === "claude-cli" || authChoice === "codex-cli") {
|
||||
runtime.error(
|
||||
[
|
||||
`Auth choice "${authChoice}" is deprecated.`,
|
||||
'Use "--auth-choice token" (Anthropic setup-token) or "--auth-choice openai-codex".',
|
||||
].join("\n"),
|
||||
);
|
||||
runtime.exit(1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (authChoice === "setup-token") {
|
||||
runtime.error(
|
||||
[
|
||||
'Auth choice "setup-token" requires interactive mode.',
|
||||
'Use "--auth-choice token" with --token and --token-provider anthropic.',
|
||||
].join("\n"),
|
||||
);
|
||||
runtime.exit(1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (authChoice === "apiKey") {
|
||||
const resolved = await resolveNonInteractiveApiKey({
|
||||
provider: "anthropic",
|
||||
@@ -318,41 +334,6 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
return applyMinimaxApiConfig(nextConfig, modelId);
|
||||
}
|
||||
|
||||
if (authChoice === "claude-cli") {
|
||||
const store = ensureAuthProfileStore(undefined, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
if (!store.profiles[CLAUDE_CLI_PROFILE_ID]) {
|
||||
runtime.error(
|
||||
process.platform === "darwin"
|
||||
? 'No Claude Code CLI credentials found. Run interactive onboarding to approve Keychain access for "Claude Code-credentials".'
|
||||
: "No Claude Code CLI credentials found at ~/.claude/.credentials.json",
|
||||
);
|
||||
runtime.exit(1);
|
||||
return null;
|
||||
}
|
||||
return applyAuthProfileConfig(nextConfig, {
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
provider: "anthropic",
|
||||
mode: "oauth",
|
||||
});
|
||||
}
|
||||
|
||||
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 null;
|
||||
}
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: CODEX_CLI_PROFILE_ID,
|
||||
provider: "openai-codex",
|
||||
mode: "oauth",
|
||||
});
|
||||
return applyOpenAICodexModelDefault(nextConfig).next;
|
||||
}
|
||||
|
||||
if (authChoice === "minimax") return applyMinimaxConfig(nextConfig);
|
||||
|
||||
if (authChoice === "opencode-zen") {
|
||||
|
||||
@@ -12,9 +12,33 @@ import type { OnboardOptions } from "./onboard-types.js";
|
||||
export async function onboardCommand(opts: OnboardOptions, runtime: RuntimeEnv = defaultRuntime) {
|
||||
assertSupportedRuntime(runtime);
|
||||
const authChoice = opts.authChoice === "oauth" ? ("setup-token" as const) : opts.authChoice;
|
||||
const normalizedAuthChoice =
|
||||
authChoice === "claude-cli"
|
||||
? ("setup-token" as const)
|
||||
: authChoice === "codex-cli"
|
||||
? ("openai-codex" as const)
|
||||
: authChoice;
|
||||
if (opts.nonInteractive && (authChoice === "claude-cli" || authChoice === "codex-cli")) {
|
||||
runtime.error(
|
||||
[
|
||||
`Auth choice "${authChoice}" is deprecated.`,
|
||||
'Use "--auth-choice token" (Anthropic setup-token) or "--auth-choice openai-codex".',
|
||||
].join("\n"),
|
||||
);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
if (authChoice === "claude-cli") {
|
||||
runtime.log('Auth choice "claude-cli" is deprecated; using setup-token flow instead.');
|
||||
}
|
||||
if (authChoice === "codex-cli") {
|
||||
runtime.log('Auth choice "codex-cli" is deprecated; using OpenAI Codex OAuth instead.');
|
||||
}
|
||||
const flow = opts.flow === "manual" ? ("advanced" as const) : opts.flow;
|
||||
const normalizedOpts =
|
||||
authChoice === opts.authChoice && flow === opts.flow ? opts : { ...opts, authChoice, flow };
|
||||
normalizedAuthChoice === opts.authChoice && flow === opts.flow
|
||||
? opts
|
||||
: { ...opts, authChoice: normalizedAuthChoice, flow };
|
||||
|
||||
if (normalizedOpts.nonInteractive && normalizedOpts.acceptRisk !== true) {
|
||||
runtime.error(
|
||||
|
||||
Reference in New Issue
Block a user