core: rename auth command to providers for clearer credential management

The auth command has been renamed to providers to better reflect its purpose of managing AI provider credentials. This makes it easier for users to discover and use the credential management features when configuring different AI providers.
This commit is contained in:
Dax Raad 2026-02-28 13:48:58 -05:00
commit 9dbf3a2042
5 changed files with 19 additions and 26 deletions

View file

@ -111,3 +111,7 @@ const table = sqliteTable("session", {
- Avoid mocks as much as possible - Avoid mocks as much as possible
- Test actual implementation, do not duplicate logic into tests - Test actual implementation, do not duplicate logic into tests
- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`. - Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.
## Type Checking
- Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly.

View file

@ -16,10 +16,6 @@ import { text } from "node:stream/consumers"
type PluginAuth = NonNullable<Hooks["auth"]> type PluginAuth = NonNullable<Hooks["auth"]>
/**
* 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<boolean> { async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string): Promise<boolean> {
let index = 0 let index = 0
if (plugin.auth.methods.length > 1) { if (plugin.auth.methods.length > 1) {
@ -37,7 +33,6 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string):
} }
const method = plugin.auth.methods[index] const method = plugin.auth.methods[index]
// Handle prompts for all auth types
await Bun.sleep(10) await Bun.sleep(10)
const inputs: Record<string, string> = {} const inputs: Record<string, string> = {}
if (method.prompts) { if (method.prompts) {
@ -161,11 +156,6 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string):
return false return false
} }
/**
* Build a deduplicated list of plugin-registered auth providers that are not
* already present in models.dev, respecting enabled/disabled provider lists.
* Pure function with no side effects; safe to test without mocking.
*/
export function resolvePluginProviders(input: { export function resolvePluginProviders(input: {
hooks: Hooks[] hooks: Hooks[]
existingProviders: Record<string, unknown> existingProviders: Record<string, unknown>
@ -193,19 +183,20 @@ export function resolvePluginProviders(input: {
return result return result
} }
export const AuthCommand = cmd({ export const ProvidersCommand = cmd({
command: "auth", command: "providers",
describe: "manage credentials", aliases: ["auth"],
describe: "manage AI providers and credentials",
builder: (yargs) => builder: (yargs) =>
yargs.command(AuthLoginCommand).command(AuthLogoutCommand).command(AuthListCommand).demandCommand(), yargs.command(ProvidersListCommand).command(ProvidersLoginCommand).command(ProvidersLogoutCommand).demandCommand(),
async handler() {}, async handler() {},
}) })
export const AuthListCommand = cmd({ export const ProvidersListCommand = cmd({
command: "list", command: "list",
aliases: ["ls"], aliases: ["ls"],
describe: "list providers", describe: "list providers and credentials",
async handler() { async handler(_args) {
UI.empty() UI.empty()
const authPath = path.join(Global.Path.data, "auth.json") const authPath = path.join(Global.Path.data, "auth.json")
const homedir = os.homedir() const homedir = os.homedir()
@ -221,7 +212,6 @@ export const AuthListCommand = cmd({
prompts.outro(`${results.length} credentials`) prompts.outro(`${results.length} credentials`)
// Environment variables section
const activeEnvVars: Array<{ provider: string; envVar: string }> = [] const activeEnvVars: Array<{ provider: string; envVar: string }> = []
for (const [providerID, provider] of Object.entries(database)) { for (const [providerID, provider] of Object.entries(database)) {
@ -248,7 +238,7 @@ export const AuthListCommand = cmd({
}, },
}) })
export const AuthLoginCommand = cmd({ export const ProvidersLoginCommand = cmd({
command: "login [url]", command: "login [url]",
describe: "log in to a provider", describe: "log in to a provider",
builder: (yargs) => builder: (yargs) =>
@ -371,7 +361,6 @@ export const AuthLoginCommand = cmd({
provider = provider.replace(/^@ai-sdk\//, "") provider = provider.replace(/^@ai-sdk\//, "")
if (prompts.isCancel(provider)) throw new UI.CancelledError() if (prompts.isCancel(provider)) throw new UI.CancelledError()
// Check if a plugin provides auth for this custom provider
const customPlugin = await Plugin.list().then((x) => x.findLast((x) => x.auth?.provider === provider)) const customPlugin = await Plugin.list().then((x) => x.findLast((x) => x.auth?.provider === provider))
if (customPlugin && customPlugin.auth) { if (customPlugin && customPlugin.auth) {
const handled = await handlePluginAuth({ auth: customPlugin.auth }, provider) const handled = await handlePluginAuth({ auth: customPlugin.auth }, provider)
@ -423,10 +412,10 @@ export const AuthLoginCommand = cmd({
}, },
}) })
export const AuthLogoutCommand = cmd({ export const ProvidersLogoutCommand = cmd({
command: "logout", command: "logout",
describe: "log out from a configured provider", describe: "log out from a configured provider",
async handler() { async handler(_args) {
UI.empty() UI.empty()
const credentials = await Auth.all().then((x) => Object.entries(x)) const credentials = await Auth.all().then((x) => Object.entries(x))
prompts.intro("Remove credential") prompts.intro("Remove credential")

View file

@ -3,7 +3,7 @@ import { hideBin } from "yargs/helpers"
import { RunCommand } from "./cli/cmd/run" import { RunCommand } from "./cli/cmd/run"
import { GenerateCommand } from "./cli/cmd/generate" import { GenerateCommand } from "./cli/cmd/generate"
import { Log } from "./util/log" import { Log } from "./util/log"
import { AuthCommand } from "./cli/cmd/auth" import { ProvidersCommand } from "./cli/cmd/providers"
import { AgentCommand } from "./cli/cmd/agent" import { AgentCommand } from "./cli/cmd/agent"
import { UpgradeCommand } from "./cli/cmd/upgrade" import { UpgradeCommand } from "./cli/cmd/upgrade"
import { UninstallCommand } from "./cli/cmd/uninstall" import { UninstallCommand } from "./cli/cmd/uninstall"
@ -129,7 +129,7 @@ let cli = yargs(hideBin(process.argv))
.command(RunCommand) .command(RunCommand)
.command(GenerateCommand) .command(GenerateCommand)
.command(DebugCommand) .command(DebugCommand)
.command(AuthCommand) .command(ProvidersCommand)
.command(AgentCommand) .command(AgentCommand)
.command(UpgradeCommand) .command(UpgradeCommand)
.command(UninstallCommand) .command(UninstallCommand)

View file

@ -1,5 +1,5 @@
import { test, expect, describe } from "bun:test" import { test, expect, describe } from "bun:test"
import { resolvePluginProviders } from "../../src/cli/cmd/auth" import { resolvePluginProviders } from "../../src/cli/cmd/providers"
import type { Hooks } from "@opencode-ai/plugin" import type { Hooks } from "@opencode-ai/plugin"
function hookWithAuth(provider: string): Hooks { function hookWithAuth(provider: string): Hooks {

View file

@ -4,7 +4,7 @@
"globalPassThroughEnv": ["CI", "OPENCODE_DISABLE_SHARE"], "globalPassThroughEnv": ["CI", "OPENCODE_DISABLE_SHARE"],
"tasks": { "tasks": {
"typecheck": { "typecheck": {
"dependsOn": ["^build"] "dependsOn": []
}, },
"build": { "build": {
"dependsOn": ["^build"], "dependsOn": ["^build"],