Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
9dbf3a2
core: rename auth command to providers for clearer credential management
thdxr Feb 28, 2026
d16e5b9
core: rename control module to account for clearer authentication man…
thdxr Feb 28, 2026
b5515dd
core: add device flow authentication commands
thdxr Feb 28, 2026
7b5b665
core: support managing multiple authenticated accounts with individua…
thdxr Feb 28, 2026
a5d727e
core: enable workspace-aware configuration and account management com…
thdxr Feb 28, 2026
a44f78c
core: maintain backward compatibility with existing account data by r…
thdxr Feb 28, 2026
902268e
core: rename workspace scope to org across account and session surfaces
kitlangton Mar 5, 2026
b19dc93
core: resolve account config env templates after loading control token
kitlangton Mar 5, 2026
e923047
core: route session sharing through org-scoped control APIs
kitlangton Mar 5, 2026
fec8d5b
core: prevent share auth headers from being sent cross-origin
kitlangton Mar 5, 2026
adc9536
core: refactor account module to Effect with repo/service split
kitlangton Mar 6, 2026
48158ce
core: simplify account repo/service after review
kitlangton Mar 6, 2026
f807875
core: effectify CLI account commands with tagged PollResult union
kitlangton Mar 6, 2026
1a2ddf9
core: tighten account service and CLI typing
kitlangton Mar 7, 2026
95279ab
core: group org lookups by account
kitlangton Mar 7, 2026
2db9d31
core: use service shape helper for account api
kitlangton Mar 7, 2026
270f44f
Merge upstream/dev into cli-auth-cloud
kitlangton Mar 7, 2026
7af0546
core: restore session workspace scope
kitlangton Mar 7, 2026
0e642ed
core: separate account repo errors
kitlangton Mar 7, 2026
e5b3407
core: remove dead auth command
kitlangton Mar 7, 2026
a581493
test: cover account service flows
kitlangton Mar 7, 2026
48cf609
core: track active account separately from org selection
kitlangton Mar 7, 2026
a2b527f
core: simplify account error payloads
kitlangton Mar 8, 2026
91b9a03
Merge upstream/dev into cli-auth-cloud
kitlangton Mar 8, 2026
49deb72
chore: bump effect beta
kitlangton Mar 8, 2026
eda8756
core: rename OPENCODE_CONTROL_TOKEN to OPENCODE_CONSOLE_TOKEN
kitlangton Mar 9, 2026
b4b24cf
core: inline helpers and extract magic number in account repo
kitlangton Mar 9, 2026
28f795a
core: fix type error in account repo list mapping
kitlangton Mar 9, 2026
30a501b
core: move org selection from account table to account_state
kitlangton Mar 9, 2026
41114b1
Merge remote-tracking branch 'upstream/dev' into cli-auth-cloud
kitlangton Mar 10, 2026
e59e104
core: improve logout UX with account picker and warn on missing refre…
kitlangton Mar 10, 2026
f003c5d
core: gracefully handle remote account config fetch failure
kitlangton Mar 10, 2026
ae821ae
core: hide account commands from help output
kitlangton Mar 10, 2026
531ba1d
test: truncate tables instead of deleting db file in account tests
kitlangton Mar 10, 2026
0a05161
core: cleanup from simplify review
kitlangton Mar 10, 2026
cf40bf0
core: rename leftover control references to console/account
kitlangton Mar 10, 2026
1568d73
core: remove default server URL, require login URL argument
kitlangton Mar 10, 2026
c632247
core: port missed upstream auth changes into providers
kitlangton Mar 10, 2026
b3bd563
test: update share-next test to match renamed error message
kitlangton Mar 10, 2026
cecfb62
fix: use relative imports in .sql.ts files for drizzle-kit compatibility
kitlangton Mar 10, 2026
3457728
chore: remove commented-out legacy unique index
kitlangton Mar 10, 2026
635d30f
core: rename login param from url to server to remove pointless alias
kitlangton Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
core: port missed upstream auth changes into providers
Restore --provider/-p and --method/-m flags, trailing slash
normalization, and Claude Max hint removal that were lost when
auth.ts was renamed to providers.ts before these features landed
on dev.
  • Loading branch information
kitlangton committed Mar 10, 2026
commit c6322476b5474875ebabbaac4c8fac3c3070f272
127 changes: 82 additions & 45 deletions packages/opencode/src/cli/cmd/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@ import { text } from "node:stream/consumers"

type PluginAuth = NonNullable<Hooks["auth"]>

async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string): Promise<boolean> {
async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string, methodName?: string): Promise<boolean> {
let index = 0
if (plugin.auth.methods.length > 1) {
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 method = await prompts.select({
message: "Login method",
options: [
Expand Down Expand Up @@ -242,18 +251,30 @@ export const ProvidersLoginCommand = 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(),
async fn() {
UI.empty()
prompts.intro("Add credential")
if (args.url) {
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json() as any)
const url = args.url.replace(/\/+$/, "")
const wellknown = await fetch(`${url}/.well-known/opencode`).then((x) => x.json() as any)
prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``)
const proc = Process.spawn(wellknown.auth.command, {
stdout: "pipe",
Expand All @@ -269,12 +290,12 @@ export const ProvidersLoginCommand = cmd({
prompts.outro("Done")
return
}
await Auth.set(args.url, {
await Auth.set(url, {
type: "wellknown",
key: wellknown.auth.env,
token: token.trim(),
})
prompts.log.success("Logged into " + args.url)
prompts.log.success("Logged into " + url)
prompts.outro("Done")
return
}
Expand Down Expand Up @@ -311,59 +332,75 @@ export const ProvidersLoginCommand = 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: "API key",
openai: "ChatGPT Plus/Pro or API key",
}[x.id],
})),
{
value: "other",
label: "Other",
},
],
})

if (prompts.isCancel(provider)) throw new UI.CancelledError()
),
...pluginProviders.map((x) => ({
label: x.name,
value: x.id,
hint: "plugin",
})),
]

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\//, "")

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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function createDialogProviderOptions() {
value: provider.id,
description: {
opencode: "(Recommended)",
anthropic: "(Claude Max or API key)",
anthropic: "(API key)",
openai: "(ChatGPT Plus/Pro or API key)",
"opencode-go": "Low cost subscription for everyone",
}[provider.id],
Expand Down
Loading