fix(onboarding): preflight claude cli keychain
This commit is contained in:
@@ -955,12 +955,15 @@ export async function agentsAddCommand(
|
||||
initialValue: false,
|
||||
});
|
||||
if (wantsAuth) {
|
||||
const authStore = ensureAuthProfileStore(agentDir);
|
||||
const authStore = ensureAuthProfileStore(agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
const authChoice = (await prompter.select({
|
||||
message: "Model/auth choice",
|
||||
options: buildAuthChoiceOptions({
|
||||
store: authStore,
|
||||
includeSkip: true,
|
||||
includeClaudeCliIfMissing: true,
|
||||
}),
|
||||
})) as AuthChoice;
|
||||
|
||||
|
||||
57
src/commands/auth-choice-options.test.ts
Normal file
57
src/commands/auth-choice-options.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { CLAUDE_CLI_PROFILE_ID, type AuthProfileStore } from "../agents/auth-profiles.js";
|
||||
import { buildAuthChoiceOptions } from "./auth-choice-options.js";
|
||||
|
||||
describe("buildAuthChoiceOptions", () => {
|
||||
it("includes Claude CLI option on macOS even when missing", () => {
|
||||
const store: AuthProfileStore = { version: 1, profiles: {} };
|
||||
const options = buildAuthChoiceOptions({
|
||||
store,
|
||||
includeSkip: false,
|
||||
includeClaudeCliIfMissing: true,
|
||||
platform: "darwin",
|
||||
});
|
||||
|
||||
const claudeCli = options.find((opt) => opt.value === "claude-cli");
|
||||
expect(claudeCli).toBeDefined();
|
||||
expect(claudeCli?.hint).toBe("requires Keychain access");
|
||||
});
|
||||
|
||||
it("skips missing Claude CLI option off macOS", () => {
|
||||
const store: AuthProfileStore = { version: 1, profiles: {} };
|
||||
const options = buildAuthChoiceOptions({
|
||||
store,
|
||||
includeSkip: false,
|
||||
includeClaudeCliIfMissing: true,
|
||||
platform: "linux",
|
||||
});
|
||||
|
||||
expect(options.find((opt) => opt.value === "claude-cli")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses token hint when Claude CLI credentials exist", () => {
|
||||
const store: AuthProfileStore = {
|
||||
version: 1,
|
||||
profiles: {
|
||||
[CLAUDE_CLI_PROFILE_ID]: {
|
||||
type: "oauth",
|
||||
provider: "anthropic",
|
||||
access: "token",
|
||||
refresh: "refresh",
|
||||
expires: Date.now() + 60 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const options = buildAuthChoiceOptions({
|
||||
store,
|
||||
includeSkip: false,
|
||||
includeClaudeCliIfMissing: true,
|
||||
platform: "darwin",
|
||||
});
|
||||
|
||||
const claudeCli = options.find((opt) => opt.value === "claude-cli");
|
||||
expect(claudeCli?.hint).toContain("token ok");
|
||||
});
|
||||
});
|
||||
@@ -45,8 +45,11 @@ function formatOAuthHint(
|
||||
export function buildAuthChoiceOptions(params: {
|
||||
store: AuthProfileStore;
|
||||
includeSkip: boolean;
|
||||
includeClaudeCliIfMissing?: boolean;
|
||||
platform?: NodeJS.Platform;
|
||||
}): AuthChoiceOption[] {
|
||||
const options: AuthChoiceOption[] = [];
|
||||
const platform = params.platform ?? process.platform;
|
||||
|
||||
const codexCli = params.store.profiles[CODEX_CLI_PROFILE_ID];
|
||||
if (codexCli?.type === "oauth") {
|
||||
@@ -64,6 +67,12 @@ export function buildAuthChoiceOptions(params: {
|
||||
label: "Anthropic OAuth (Claude CLI)",
|
||||
hint: formatOAuthHint(claudeCli.expires),
|
||||
});
|
||||
} else if (params.includeClaudeCliIfMissing && platform === "darwin") {
|
||||
options.push({
|
||||
value: "claude-cli",
|
||||
label: "Anthropic OAuth (Claude CLI)",
|
||||
hint: "requires Keychain access",
|
||||
});
|
||||
}
|
||||
|
||||
options.push({ value: "oauth", label: "Anthropic OAuth (Claude Pro/Max)" });
|
||||
|
||||
@@ -168,10 +168,39 @@ export async function applyAuthChoice(params: {
|
||||
);
|
||||
}
|
||||
} else if (params.authChoice === "claude-cli") {
|
||||
const store = ensureAuthProfileStore(params.agentDir);
|
||||
if (!store.profiles[CLAUDE_CLI_PROFILE_ID]) {
|
||||
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(
|
||||
"No Claude CLI credentials found at ~/.claude/.credentials.json.",
|
||||
[
|
||||
"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 CLI Keychain",
|
||||
);
|
||||
const proceed = await params.prompter.confirm({
|
||||
message: "Check Keychain for Claude CLI credentials now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (!proceed) {
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
}
|
||||
|
||||
const storeWithKeychain = hasClaudeCli
|
||||
? store
|
||||
: ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: true,
|
||||
});
|
||||
|
||||
if (!storeWithKeychain.profiles[CLAUDE_CLI_PROFILE_ID]) {
|
||||
await params.prompter.note(
|
||||
process.platform === "darwin"
|
||||
? 'No Claude CLI credentials found in Keychain ("Claude Code-credentials") or ~/.claude/.credentials.json.'
|
||||
: "No Claude CLI credentials found at ~/.claude/.credentials.json.",
|
||||
"Claude CLI OAuth",
|
||||
);
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
|
||||
@@ -286,8 +286,9 @@ async function promptAuthConfig(
|
||||
await select({
|
||||
message: "Model/auth choice",
|
||||
options: buildAuthChoiceOptions({
|
||||
store: ensureAuthProfileStore(),
|
||||
store: ensureAuthProfileStore(undefined, { allowKeychainPrompt: false }),
|
||||
includeSkip: true,
|
||||
includeClaudeCliIfMissing: true,
|
||||
}),
|
||||
}),
|
||||
runtime,
|
||||
|
||||
@@ -120,10 +120,14 @@ export async function runNonInteractiveOnboarding(
|
||||
mode: "api_key",
|
||||
});
|
||||
} else if (authChoice === "claude-cli") {
|
||||
const store = ensureAuthProfileStore();
|
||||
const store = ensureAuthProfileStore(undefined, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
if (!store.profiles[CLAUDE_CLI_PROFILE_ID]) {
|
||||
runtime.error(
|
||||
"No Claude CLI credentials found at ~/.claude/.credentials.json",
|
||||
process.platform === "darwin"
|
||||
? 'No Claude CLI credentials found. Run interactive onboarding to approve Keychain access for "Claude Code-credentials".'
|
||||
: "No Claude CLI credentials found at ~/.claude/.credentials.json",
|
||||
);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user