diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts index 4afe7a822..a7300e029 100644 --- a/packages/opencode/src/cli/cmd/auth.ts +++ b/packages/opencode/src/cli/cmd/auth.ts @@ -20,10 +20,19 @@ type PluginAuth = NonNullable * Handle plugin-based authentication flow. * Returns true if auth was handled, false if it should fall through to default handling. */ -async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string): Promise { +async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string, methodName?: string): Promise { let index = 0 - if (plugin.auth.methods.length > 1) { - const method = await prompts.select({ + if (methodName) { + const match = plugin.auth.methods.findIndex((x) => x.label.toLowerCase() === methodName.toLowerCase()) + if (match === -1) { + prompts.log.error( + `Unknown method "${methodName}" for ${provider}. Available: ${plugin.auth.methods.map((x) => x.label).join(", ")}`, + ) + process.exit(1) + } + index = match + } else if (plugin.auth.methods.length > 1) { + const selected = await prompts.select({ message: "Login method", options: [ ...plugin.auth.methods.map((x, index) => ({ @@ -32,8 +41,8 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string): })), ], }) - if (prompts.isCancel(method)) throw new UI.CancelledError() - index = parseInt(method) + if (prompts.isCancel(selected)) throw new UI.CancelledError() + index = parseInt(selected) } const method = plugin.auth.methods[index] @@ -252,10 +261,21 @@ export const AuthLoginCommand = cmd({ command: "login [url]", describe: "log in to a provider", builder: (yargs) => - yargs.positional("url", { - describe: "opencode auth provider", - type: "string", - }), + yargs + .positional("url", { + describe: "opencode auth provider", + type: "string", + }) + .option("provider", { + alias: ["p"], + describe: "provider id or name to log in to (skips provider selection)", + type: "string", + }) + .option("method", { + alias: ["m"], + describe: "login method label (skips method selection)", + type: "string", + }), async handler(args) { await Instance.provide({ directory: process.cwd(), @@ -322,60 +342,76 @@ export const AuthLoginCommand = cmd({ enabled, providerNames: Object.fromEntries(Object.entries(config.provider ?? {}).map(([id, p]) => [id, p.name])), }) - let provider = await prompts.autocomplete({ - message: "Select provider", - maxItems: 8, - options: [ - ...pipe( - providers, - values(), - sortBy( - (x) => priority[x.id] ?? 99, - (x) => x.name ?? x.id, - ), - map((x) => ({ - label: x.name, - value: x.id, - hint: { - opencode: "recommended", - anthropic: "Claude Max or API key", - openai: "ChatGPT Plus/Pro or API key", - }[x.id], - })), + const options = [ + ...pipe( + providers, + values(), + sortBy( + (x) => priority[x.id] ?? 99, + (x) => x.name ?? x.id, ), - ...pluginProviders.map((x) => ({ + map((x) => ({ label: x.name, value: x.id, - hint: "plugin", + hint: { + opencode: "recommended", + anthropic: "Claude Max or API key", + openai: "ChatGPT Plus/Pro or API key", + }[x.id], })), - { - value: "other", - label: "Other", - }, - ], - }) + ), + ...pluginProviders.map((x) => ({ + label: x.name, + value: x.id, + hint: "plugin", + })), + ] - if (prompts.isCancel(provider)) throw new UI.CancelledError() + let provider: string + if (args.provider) { + const input = args.provider + const byID = options.find((x) => x.value === input) + const byName = options.find((x) => x.label.toLowerCase() === input.toLowerCase()) + const match = byID ?? byName + if (!match) { + prompts.log.error(`Unknown provider "${input}"`) + process.exit(1) + } + provider = match.value + } else { + const selected = await prompts.autocomplete({ + message: "Select provider", + maxItems: 8, + options: [ + ...options, + { + value: "other", + label: "Other", + }, + ], + }) + if (prompts.isCancel(selected)) throw new UI.CancelledError() + provider = selected as string + } const plugin = await Plugin.list().then((x) => x.findLast((x) => x.auth?.provider === provider)) if (plugin && plugin.auth) { - const handled = await handlePluginAuth({ auth: plugin.auth }, provider) + const handled = await handlePluginAuth({ auth: plugin.auth }, provider, args.method) if (handled) return } if (provider === "other") { - provider = await prompts.text({ + const custom = await prompts.text({ message: "Enter provider id", validate: (x) => (x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"), }) - if (prompts.isCancel(provider)) throw new UI.CancelledError() - provider = provider.replace(/^@ai-sdk\//, "") - if (prompts.isCancel(provider)) throw new UI.CancelledError() + if (prompts.isCancel(custom)) throw new UI.CancelledError() + provider = custom.replace(/^@ai-sdk\//, "") // Check if a plugin provides auth for this custom provider const customPlugin = await Plugin.list().then((x) => x.findLast((x) => x.auth?.provider === provider)) if (customPlugin && customPlugin.auth) { - const handled = await handlePluginAuth({ auth: customPlugin.auth }, provider) + const handled = await handlePluginAuth({ auth: customPlugin.auth }, provider, args.method) if (handled) return }