mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-28 10:22:32 +00:00
feat: add anthropic claude cli migration
This commit is contained in:
82
extensions/anthropic/cli-migration.test.ts
Normal file
82
extensions/anthropic/cli-migration.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const readClaudeCliCredentialsCached = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-auth", async (importActual) => {
|
||||
const actual = await importActual<typeof import("openclaw/plugin-sdk/provider-auth")>();
|
||||
return {
|
||||
...actual,
|
||||
readClaudeCliCredentialsCached,
|
||||
};
|
||||
});
|
||||
|
||||
const { buildAnthropicCliMigrationResult, hasClaudeCliAuth } = await import("./cli-migration.js");
|
||||
|
||||
describe("anthropic cli migration", () => {
|
||||
it("detects local Claude CLI auth", () => {
|
||||
readClaudeCliCredentialsCached.mockReturnValue({ type: "oauth" });
|
||||
|
||||
expect(hasClaudeCliAuth()).toBe(true);
|
||||
});
|
||||
|
||||
it("rewrites anthropic defaults to claude-cli defaults", () => {
|
||||
const result = buildAnthropicCliMigrationResult({
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: "anthropic/claude-sonnet-4-6",
|
||||
fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.2"],
|
||||
},
|
||||
models: {
|
||||
"anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
|
||||
"anthropic/claude-opus-4-6": { alias: "Opus" },
|
||||
"openai/gpt-5.2": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.profiles).toEqual([]);
|
||||
expect(result.defaultModel).toBe("claude-cli/claude-sonnet-4-6");
|
||||
expect(result.configPatch).toEqual({
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: "claude-cli/claude-sonnet-4-6",
|
||||
fallbacks: ["claude-cli/claude-opus-4-6", "openai/gpt-5.2"],
|
||||
},
|
||||
models: {
|
||||
"claude-cli/claude-sonnet-4-6": { alias: "Sonnet" },
|
||||
"claude-cli/claude-opus-4-6": { alias: "Opus" },
|
||||
"openai/gpt-5.2": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("adds a Claude CLI default when no anthropic default is present", () => {
|
||||
const result = buildAnthropicCliMigrationResult({
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "openai/gpt-5.2" },
|
||||
models: {
|
||||
"openai/gpt-5.2": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.defaultModel).toBe("claude-cli/claude-sonnet-4-6");
|
||||
expect(result.configPatch).toEqual({
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"openai/gpt-5.2": {},
|
||||
"claude-cli/claude-sonnet-4-6": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
131
extensions/anthropic/cli-migration.ts
Normal file
131
extensions/anthropic/cli-migration.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import type { OpenClawConfig, ProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { readClaudeCliCredentialsCached } from "openclaw/plugin-sdk/provider-auth";
|
||||
|
||||
const DEFAULT_CLAUDE_CLI_MODEL = "claude-cli/claude-sonnet-4-6";
|
||||
type AgentDefaultsModel = NonNullable<NonNullable<OpenClawConfig["agents"]>["defaults"]>["model"];
|
||||
type AgentDefaultsModels = NonNullable<NonNullable<OpenClawConfig["agents"]>["defaults"]>["models"];
|
||||
|
||||
function toClaudeCliModelRef(raw: string): string | null {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed.toLowerCase().startsWith("anthropic/")) {
|
||||
return null;
|
||||
}
|
||||
const modelId = trimmed.slice("anthropic/".length).trim();
|
||||
if (!modelId.toLowerCase().startsWith("claude-")) {
|
||||
return null;
|
||||
}
|
||||
return `claude-cli/${modelId}`;
|
||||
}
|
||||
|
||||
function rewriteModelSelection(model: AgentDefaultsModel): {
|
||||
value: AgentDefaultsModel;
|
||||
primary?: string;
|
||||
changed: boolean;
|
||||
} {
|
||||
if (typeof model === "string") {
|
||||
const converted = toClaudeCliModelRef(model);
|
||||
return converted
|
||||
? { value: converted, primary: converted, changed: true }
|
||||
: { value: model, changed: false };
|
||||
}
|
||||
if (!model || typeof model !== "object" || Array.isArray(model)) {
|
||||
return { value: model, changed: false };
|
||||
}
|
||||
|
||||
const current = model as Record<string, unknown>;
|
||||
const next: Record<string, unknown> = { ...current };
|
||||
let changed = false;
|
||||
let primary: string | undefined;
|
||||
|
||||
if (typeof current.primary === "string") {
|
||||
const converted = toClaudeCliModelRef(current.primary);
|
||||
if (converted) {
|
||||
next.primary = converted;
|
||||
primary = converted;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const currentFallbacks = current.fallbacks;
|
||||
if (Array.isArray(currentFallbacks)) {
|
||||
const nextFallbacks = currentFallbacks.map((entry) =>
|
||||
typeof entry === "string" ? (toClaudeCliModelRef(entry) ?? entry) : entry,
|
||||
);
|
||||
if (nextFallbacks.some((entry, index) => entry !== currentFallbacks[index])) {
|
||||
next.fallbacks = nextFallbacks;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
value: changed ? next : model,
|
||||
...(primary ? { primary } : {}),
|
||||
changed,
|
||||
};
|
||||
}
|
||||
|
||||
function rewriteModelEntryMap(models: Record<string, unknown> | undefined): {
|
||||
value: Record<string, unknown> | undefined;
|
||||
migrated: string[];
|
||||
} {
|
||||
if (!models) {
|
||||
return { value: models, migrated: [] };
|
||||
}
|
||||
|
||||
const next = { ...models };
|
||||
const migrated: string[] = [];
|
||||
|
||||
for (const [rawKey, value] of Object.entries(models)) {
|
||||
const converted = toClaudeCliModelRef(rawKey);
|
||||
if (!converted) {
|
||||
continue;
|
||||
}
|
||||
if (!(converted in next)) {
|
||||
next[converted] = value;
|
||||
}
|
||||
delete next[rawKey];
|
||||
migrated.push(converted);
|
||||
}
|
||||
|
||||
return {
|
||||
value: migrated.length > 0 ? next : models,
|
||||
migrated,
|
||||
};
|
||||
}
|
||||
|
||||
export function hasClaudeCliAuth(): boolean {
|
||||
return Boolean(readClaudeCliCredentialsCached());
|
||||
}
|
||||
|
||||
export function buildAnthropicCliMigrationResult(config: OpenClawConfig): ProviderAuthResult {
|
||||
const defaults = config.agents?.defaults;
|
||||
const rewrittenModel = rewriteModelSelection(defaults?.model);
|
||||
const rewrittenModels = rewriteModelEntryMap(defaults?.models);
|
||||
const existingModels = (rewrittenModels.value ??
|
||||
defaults?.models ??
|
||||
{}) as NonNullable<AgentDefaultsModels>;
|
||||
const defaultModel = rewrittenModel.primary ?? DEFAULT_CLAUDE_CLI_MODEL;
|
||||
|
||||
return {
|
||||
profiles: [],
|
||||
configPatch: {
|
||||
agents: {
|
||||
defaults: {
|
||||
...(rewrittenModel.changed ? { model: rewrittenModel.value } : {}),
|
||||
models: {
|
||||
...existingModels,
|
||||
[defaultModel]: existingModels[defaultModel] ?? {},
|
||||
} as NonNullable<AgentDefaultsModels>,
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultModel,
|
||||
notes: [
|
||||
"Claude CLI auth detected; switched Anthropic model selection to the local Claude CLI backend.",
|
||||
"Existing Anthropic auth profiles are kept for rollback.",
|
||||
...(rewrittenModels.migrated.length > 0
|
||||
? [`Migrated allowlist entries: ${rewrittenModels.migrated.join(", ")}.`]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-models";
|
||||
import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage";
|
||||
import { buildAnthropicCliBackend } from "./cli-backend.js";
|
||||
import { buildAnthropicCliMigrationResult, hasClaudeCliAuth } from "./cli-migration.js";
|
||||
import { anthropicMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||
|
||||
const PROVIDER_ID = "anthropic";
|
||||
@@ -312,6 +313,59 @@ async function runAnthropicSetupTokenNonInteractive(ctx: {
|
||||
});
|
||||
}
|
||||
|
||||
async function runAnthropicCliMigration(ctx: ProviderAuthContext): Promise<ProviderAuthResult> {
|
||||
if (!hasClaudeCliAuth()) {
|
||||
throw new Error(
|
||||
[
|
||||
"Claude CLI is not authenticated on this host.",
|
||||
`Run ${formatCliCommand("claude auth login")} first, then re-run this setup.`,
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
return buildAnthropicCliMigrationResult(ctx.config);
|
||||
}
|
||||
|
||||
async function runAnthropicCliMigrationNonInteractive(ctx: {
|
||||
config: ProviderAuthContext["config"];
|
||||
runtime: ProviderAuthContext["runtime"];
|
||||
}): Promise<ProviderAuthContext["config"] | null> {
|
||||
if (!hasClaudeCliAuth()) {
|
||||
ctx.runtime.error(
|
||||
[
|
||||
'Auth choice "anthropic-cli" requires Claude CLI auth on this host.',
|
||||
`Run ${formatCliCommand("claude auth login")} first.`,
|
||||
].join("\n"),
|
||||
);
|
||||
ctx.runtime.exit(1);
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = buildAnthropicCliMigrationResult(ctx.config);
|
||||
const currentDefaults = ctx.config.agents?.defaults;
|
||||
const currentModel = currentDefaults?.model;
|
||||
const currentFallbacks =
|
||||
currentModel && typeof currentModel === "object" && "fallbacks" in currentModel
|
||||
? currentModel.fallbacks
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...ctx.config,
|
||||
...result.configPatch,
|
||||
agents: {
|
||||
...ctx.config.agents,
|
||||
...result.configPatch?.agents,
|
||||
defaults: {
|
||||
...currentDefaults,
|
||||
...result.configPatch?.agents?.defaults,
|
||||
model: {
|
||||
...(Array.isArray(currentFallbacks) ? { fallbacks: currentFallbacks } : {}),
|
||||
primary: result.defaultModel,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Anthropic Provider",
|
||||
@@ -325,6 +379,33 @@ export default definePluginEntry({
|
||||
envVars: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
|
||||
deprecatedProfileIds: [CLAUDE_CLI_PROFILE_ID],
|
||||
auth: [
|
||||
{
|
||||
id: "cli",
|
||||
label: "Claude CLI",
|
||||
hint: "Reuse a local Claude CLI login and switch model selection to claude-cli/*",
|
||||
kind: "custom",
|
||||
wizard: {
|
||||
choiceId: "anthropic-cli",
|
||||
choiceLabel: "Anthropic Claude CLI",
|
||||
choiceHint: "Reuse a local Claude CLI login on this host",
|
||||
groupId: "anthropic",
|
||||
groupLabel: "Anthropic",
|
||||
groupHint: "Claude CLI + setup-token + API key",
|
||||
modelAllowlist: {
|
||||
allowedKeys: [...ANTHROPIC_OAUTH_ALLOWLIST].map((model) =>
|
||||
model.replace(/^anthropic\//, "claude-cli/"),
|
||||
),
|
||||
initialSelections: ["claude-cli/claude-sonnet-4-6"],
|
||||
message: "Claude CLI models",
|
||||
},
|
||||
},
|
||||
run: async (ctx: ProviderAuthContext) => await runAnthropicCliMigration(ctx),
|
||||
runNonInteractive: async (ctx) =>
|
||||
await runAnthropicCliMigrationNonInteractive({
|
||||
config: ctx.config,
|
||||
runtime: ctx.runtime,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "setup-token",
|
||||
label: "setup-token (claude)",
|
||||
@@ -336,7 +417,7 @@ export default definePluginEntry({
|
||||
choiceHint: "Run `claude setup-token` elsewhere, then paste the token here",
|
||||
groupId: "anthropic",
|
||||
groupLabel: "Anthropic",
|
||||
groupHint: "setup-token + API key",
|
||||
groupHint: "Claude CLI + setup-token + API key",
|
||||
modelAllowlist: {
|
||||
allowedKeys: [...ANTHROPIC_OAUTH_ALLOWLIST],
|
||||
initialSelections: ["anthropic/claude-sonnet-4-6"],
|
||||
@@ -368,7 +449,7 @@ export default definePluginEntry({
|
||||
choiceLabel: "Anthropic API key",
|
||||
groupId: "anthropic",
|
||||
groupLabel: "Anthropic",
|
||||
groupHint: "setup-token + API key",
|
||||
groupHint: "Claude CLI + setup-token + API key",
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -7,6 +7,16 @@
|
||||
"anthropic": ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]
|
||||
},
|
||||
"providerAuthChoices": [
|
||||
{
|
||||
"provider": "anthropic",
|
||||
"method": "cli",
|
||||
"choiceId": "anthropic-cli",
|
||||
"choiceLabel": "Anthropic Claude CLI",
|
||||
"choiceHint": "Reuse a local Claude CLI login on this host",
|
||||
"groupId": "anthropic",
|
||||
"groupLabel": "Anthropic",
|
||||
"groupHint": "Claude CLI + setup-token + API key"
|
||||
},
|
||||
{
|
||||
"provider": "anthropic",
|
||||
"method": "setup-token",
|
||||
@@ -15,7 +25,7 @@
|
||||
"choiceHint": "Run `claude setup-token` elsewhere, then paste the token here",
|
||||
"groupId": "anthropic",
|
||||
"groupLabel": "Anthropic",
|
||||
"groupHint": "setup-token + API key"
|
||||
"groupHint": "Claude CLI + setup-token + API key"
|
||||
},
|
||||
{
|
||||
"provider": "anthropic",
|
||||
@@ -24,7 +34,7 @@
|
||||
"choiceLabel": "Anthropic API key",
|
||||
"groupId": "anthropic",
|
||||
"groupLabel": "Anthropic",
|
||||
"groupHint": "setup-token + API key",
|
||||
"groupHint": "Claude CLI + setup-token + API key",
|
||||
"optionKey": "anthropicApiKey",
|
||||
"cliFlag": "--anthropic-api-key",
|
||||
"cliOption": "--anthropic-api-key <key>",
|
||||
|
||||
19
src/commands/auth-choice-legacy.test.ts
Normal file
19
src/commands/auth-choice-legacy.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
formatDeprecatedNonInteractiveAuthChoiceError,
|
||||
normalizeLegacyOnboardAuthChoice,
|
||||
resolveDeprecatedAuthChoiceReplacement,
|
||||
} from "./auth-choice-legacy.js";
|
||||
|
||||
describe("auth choice legacy aliases", () => {
|
||||
it("maps claude-cli to the new anthropic cli choice", () => {
|
||||
expect(normalizeLegacyOnboardAuthChoice("claude-cli")).toBe("anthropic-cli");
|
||||
expect(resolveDeprecatedAuthChoiceReplacement("claude-cli")).toEqual({
|
||||
normalized: "anthropic-cli",
|
||||
message: 'Auth choice "claude-cli" is deprecated; using Anthropic Claude CLI setup instead.',
|
||||
});
|
||||
expect(formatDeprecatedNonInteractiveAuthChoiceError("claude-cli")).toBe(
|
||||
'Auth choice "claude-cli" is deprecated.\nUse "--auth-choice anthropic-cli".',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -10,9 +10,12 @@ export const AUTH_CHOICE_LEGACY_ALIASES_FOR_CLI: ReadonlyArray<AuthChoice> = [
|
||||
export function normalizeLegacyOnboardAuthChoice(
|
||||
authChoice: AuthChoice | undefined,
|
||||
): AuthChoice | undefined {
|
||||
if (authChoice === "oauth" || authChoice === "claude-cli") {
|
||||
if (authChoice === "oauth") {
|
||||
return "setup-token";
|
||||
}
|
||||
if (authChoice === "claude-cli") {
|
||||
return "anthropic-cli";
|
||||
}
|
||||
if (authChoice === "codex-cli") {
|
||||
return "openai-codex";
|
||||
}
|
||||
@@ -31,8 +34,8 @@ export function resolveDeprecatedAuthChoiceReplacement(authChoice: "claude-cli"
|
||||
} {
|
||||
if (authChoice === "claude-cli") {
|
||||
return {
|
||||
normalized: "setup-token",
|
||||
message: 'Auth choice "claude-cli" is deprecated; using setup-token flow instead.',
|
||||
normalized: "anthropic-cli",
|
||||
message: 'Auth choice "claude-cli" is deprecated; using Anthropic Claude CLI setup instead.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -45,8 +48,6 @@ export function formatDeprecatedNonInteractiveAuthChoiceError(
|
||||
authChoice: "claude-cli" | "codex-cli",
|
||||
): string {
|
||||
const replacement =
|
||||
authChoice === "claude-cli"
|
||||
? '"--auth-choice token" (Anthropic setup-token)'
|
||||
: '"--auth-choice openai-codex"';
|
||||
authChoice === "claude-cli" ? '"--auth-choice anthropic-cli"' : '"--auth-choice openai-codex"';
|
||||
return [`Auth choice "${authChoice}" is deprecated.`, `Use ${replacement}.`].join("\n");
|
||||
}
|
||||
|
||||
@@ -229,6 +229,52 @@ describe("modelsAuthLoginCommand", () => {
|
||||
expect(runtime.log).toHaveBeenCalledWith("Default model set to openai-codex/gpt-5.4");
|
||||
});
|
||||
|
||||
it("supports provider-owned Claude CLI migration without writing auth profiles", async () => {
|
||||
const runtime = createRuntime();
|
||||
const runClaudeCliMigration = vi.fn().mockResolvedValue({
|
||||
profiles: [],
|
||||
defaultModel: "claude-cli/claude-sonnet-4-6",
|
||||
configPatch: {
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"claude-cli/claude-sonnet-4-6": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mocks.resolvePluginProviders.mockReturnValue([
|
||||
{
|
||||
id: "anthropic",
|
||||
label: "Anthropic",
|
||||
auth: [
|
||||
{
|
||||
id: "cli",
|
||||
label: "Claude CLI",
|
||||
kind: "custom",
|
||||
run: runClaudeCliMigration,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
await modelsAuthLoginCommand(
|
||||
{ provider: "anthropic", method: "cli", setDefault: true },
|
||||
runtime,
|
||||
);
|
||||
|
||||
expect(runClaudeCliMigration).toHaveBeenCalledOnce();
|
||||
expect(mocks.upsertAuthProfile).not.toHaveBeenCalled();
|
||||
expect(lastUpdatedConfig?.agents?.defaults?.model).toEqual({
|
||||
primary: "claude-cli/claude-sonnet-4-6",
|
||||
});
|
||||
expect(lastUpdatedConfig?.agents?.defaults?.models).toEqual({
|
||||
"claude-cli/claude-sonnet-4-6": {},
|
||||
});
|
||||
expect(runtime.log).toHaveBeenCalledWith("Default model set to claude-cli/claude-sonnet-4-6");
|
||||
});
|
||||
|
||||
it("clears stale auth lockouts before attempting openai-codex login", async () => {
|
||||
const runtime = createRuntime();
|
||||
const fakeStore = {
|
||||
|
||||
@@ -9,6 +9,7 @@ export type { AuthProfileStore, OAuthCredential } from "../agents/auth-profiles/
|
||||
export { CLAUDE_CLI_PROFILE_ID, CODEX_CLI_PROFILE_ID } from "../agents/auth-profiles/constants.js";
|
||||
export { ensureAuthProfileStore } from "../agents/auth-profiles/store.js";
|
||||
export { listProfilesForProvider, upsertAuthProfile } from "../agents/auth-profiles/profiles.js";
|
||||
export { readClaudeCliCredentialsCached } from "../agents/cli-credentials.js";
|
||||
export { suggestOAuthProfileIdForLegacyDefault } from "../agents/auth-profiles/repair.js";
|
||||
export {
|
||||
MINIMAX_OAUTH_MARKER,
|
||||
|
||||
@@ -510,6 +510,7 @@ describe("plugin-sdk subpath exports", () => {
|
||||
expectSourceMentions("provider-auth", [
|
||||
"buildOauthProviderAuthResult",
|
||||
"generatePkceVerifierChallenge",
|
||||
"readClaudeCliCredentialsCached",
|
||||
"toFormUrlEncoded",
|
||||
]);
|
||||
expectSourceOmits("core", ["buildOauthProviderAuthResult"]);
|
||||
|
||||
@@ -174,6 +174,16 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
|
||||
},
|
||||
providerAuthChoices: [
|
||||
{
|
||||
provider: "anthropic",
|
||||
method: "cli",
|
||||
choiceId: "anthropic-cli",
|
||||
choiceLabel: "Anthropic Claude CLI",
|
||||
choiceHint: "Reuse a local Claude CLI login on this host",
|
||||
groupId: "anthropic",
|
||||
groupLabel: "Anthropic",
|
||||
groupHint: "Claude CLI + setup-token + API key",
|
||||
},
|
||||
{
|
||||
provider: "anthropic",
|
||||
method: "setup-token",
|
||||
@@ -182,7 +192,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
choiceHint: "Run `claude setup-token` elsewhere, then paste the token here",
|
||||
groupId: "anthropic",
|
||||
groupLabel: "Anthropic",
|
||||
groupHint: "setup-token + API key",
|
||||
groupHint: "Claude CLI + setup-token + API key",
|
||||
},
|
||||
{
|
||||
provider: "anthropic",
|
||||
@@ -191,7 +201,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
choiceLabel: "Anthropic API key",
|
||||
groupId: "anthropic",
|
||||
groupLabel: "Anthropic",
|
||||
groupHint: "setup-token + API key",
|
||||
groupHint: "Claude CLI + setup-token + API key",
|
||||
optionKey: "anthropicApiKey",
|
||||
cliFlag: "--anthropic-api-key",
|
||||
cliOption: "--anthropic-api-key <key>",
|
||||
|
||||
Reference in New Issue
Block a user