2025-06-11 04:21:46 +00:00
import { Auth } from "../../auth"
import { cmd } from "./cmd"
2026-05-03 20:41:10 +00:00
import { CliError , effectCmd , fail } from "../effect-cmd"
2025-06-11 04:21:46 +00:00
import { UI } from "../ui"
2026-05-03 20:41:10 +00:00
import * as Prompt from "../effect/prompt"
2026-05-14 00:58:24 +00:00
import { ModelsDev } from "@opencode-ai/core/models"
2026-05-02 19:11:01 +00:00
2025-06-16 19:02:25 +00:00
import { map , pipe , sortBy , values } from "remeda"
2025-06-25 02:24:55 +00:00
import path from "path"
import os from "os"
2026-04-27 18:33:33 +00:00
import { Config } from "@/config/config"
2026-04-25 17:51:37 +00:00
import { Global } from "@opencode-ai/core/global"
2025-08-14 20:24:46 +00:00
import { Plugin } from "../../plugin"
2025-12-10 22:23:12 +00:00
import type { Hooks } from "@opencode-ai/plugin"
2026-04-27 18:33:33 +00:00
import { Process } from "@/util/process"
2026-05-03 20:41:10 +00:00
import { errorMessage } from "@/util/error"
2026-02-25 04:04:15 +00:00
import { text } from "node:stream/consumers"
2026-05-03 20:41:10 +00:00
import { Effect , Option } from "effect"
2025-12-10 22:23:12 +00:00
type PluginAuth = NonNullable < Hooks [ " auth " ] >
2026-05-03 20:41:10 +00:00
const promptValue = < Value > ( value : Option.Option < Value > ) = > {
if ( Option . isNone ( value ) ) return Effect . die ( new UI . CancelledError ( ) )
return Effect . succeed ( value . value )
}
const put = Effect . fn ( "Cli.providers.put" ) ( function * ( key : string , info : Auth.Info ) {
const auth = yield * Auth . Service
yield * Effect . orDie ( auth . set ( key , info ) )
} )
const cliTry = < Value > ( message : string , fn : ( ) = > PromiseLike < Value > ) = >
Effect . tryPromise ( {
try : fn ,
catch : ( error ) = > new CliError ( { message : message + errorMessage ( error ) } ) ,
} )
const handlePluginAuth = Effect . fn ( "Cli.providers.pluginAuth" ) ( function * (
plugin : { auth : PluginAuth } ,
provider : string ,
methodName? : string ,
) {
const index = yield * Effect . gen ( function * ( ) {
if ( ! methodName ) {
if ( plugin . auth . methods . length <= 1 ) return 0
return yield * promptValue (
yield * Prompt . select ( {
message : "Login method" ,
options : plugin.auth.methods.map ( ( x , index ) = > ( {
label : x.label ,
value : index ,
} ) ) ,
} ) ,
)
}
2026-03-05 06:16:53 +00:00
const match = plugin . auth . methods . findIndex ( ( x ) = > x . label . toLowerCase ( ) === methodName . toLowerCase ( ) )
if ( match === - 1 ) {
2026-05-03 20:41:10 +00:00
return yield * fail (
2026-03-05 06:16:53 +00:00
` Unknown method " ${ methodName } " for ${ provider } . Available: ${ plugin . auth . methods . map ( ( x ) = > x . label ) . join ( ", " ) } ` ,
)
}
2026-05-03 20:41:10 +00:00
return match
} )
2025-12-10 22:23:12 +00:00
const method = plugin . auth . methods [ index ]
2026-05-03 20:41:10 +00:00
yield * Effect . sleep ( "10 millis" )
2025-12-10 22:23:12 +00:00
const inputs : Record < string , string > = { }
if ( method . prompts ) {
for ( const prompt of method . prompts ) {
2026-03-18 16:36:19 +00:00
if ( prompt . when ) {
const value = inputs [ prompt . when . key ]
if ( value === undefined ) continue
const matches = prompt . when . op === "eq" ? value === prompt.when.value : value !== prompt . when . value
if ( ! matches ) continue
2025-12-10 22:23:12 +00:00
}
2026-03-18 16:36:19 +00:00
if ( prompt . condition && ! prompt . condition ( inputs ) ) continue
2025-12-10 22:23:12 +00:00
if ( prompt . type === "select" ) {
2026-05-03 20:41:10 +00:00
const value = yield * Prompt . select ( {
2025-12-10 22:23:12 +00:00
message : prompt.message ,
options : prompt.options ,
} )
2026-05-03 20:41:10 +00:00
inputs [ prompt . key ] = yield * promptValue ( value )
continue
2025-12-10 22:23:12 +00:00
}
2026-05-03 20:41:10 +00:00
const value = yield * Prompt . text ( {
message : prompt.message ,
placeholder : prompt.placeholder ,
validate : prompt.validate ? ( v ) = > prompt . validate ! ( v ? ? "" ) : undefined ,
} )
inputs [ prompt . key ] = yield * promptValue ( value )
2025-12-10 22:23:12 +00:00
}
}
if ( method . type === "oauth" ) {
2026-05-03 20:41:10 +00:00
const authorize = yield * cliTry ( "Failed to authorize: " , ( ) = > method . authorize ( inputs ) )
2025-12-10 22:23:12 +00:00
if ( authorize . url ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . info ( "Go to: " + authorize . url )
2025-12-10 22:23:12 +00:00
}
if ( authorize . method === "auto" ) {
if ( authorize . instructions ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . info ( authorize . instructions )
2025-12-10 22:23:12 +00:00
}
2026-05-03 20:41:10 +00:00
const spinner = Prompt . spinner ( )
yield * spinner . start ( "Waiting for authorization..." )
const result = yield * cliTry ( "Failed to authorize: " , ( ) = > authorize . callback ( ) )
2025-12-10 22:23:12 +00:00
if ( result . type === "failed" ) {
2026-05-03 20:41:10 +00:00
yield * spinner . stop ( "Failed to authorize" , 1 )
2025-12-10 22:23:12 +00:00
}
if ( result . type === "success" ) {
const saveProvider = result . provider ? ? provider
if ( "refresh" in result ) {
const { type : _ , provider : __ , refresh , access , expires , . . . extraFields } = result
2026-05-03 20:41:10 +00:00
yield * put ( saveProvider , {
2025-12-10 22:23:12 +00:00
type : "oauth" ,
refresh ,
access ,
expires ,
. . . extraFields ,
} )
}
if ( "key" in result ) {
2026-05-03 20:41:10 +00:00
yield * put ( saveProvider , {
2025-12-10 22:23:12 +00:00
type : "api" ,
key : result.key ,
2026-05-12 21:22:40 +00:00
. . . ( result . metadata ? { metadata : result.metadata } : { } ) ,
2025-12-10 22:23:12 +00:00
} )
}
2026-05-03 20:41:10 +00:00
yield * spinner . stop ( "Login successful" )
2025-12-10 22:23:12 +00:00
}
}
if ( authorize . method === "code" ) {
2026-05-03 20:41:10 +00:00
const code = yield * Prompt . text ( {
2025-12-10 22:23:12 +00:00
message : "Paste the authorization code here: " ,
validate : ( x ) = > ( x && x . length > 0 ? undefined : "Required" ) ,
} )
2026-05-03 20:41:10 +00:00
const authorizationCode = yield * promptValue ( code )
const result = yield * cliTry ( "Failed to authorize: " , ( ) = > authorize . callback ( authorizationCode ) )
2025-12-10 22:23:12 +00:00
if ( result . type === "failed" ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . error ( "Failed to authorize" )
2025-12-10 22:23:12 +00:00
}
if ( result . type === "success" ) {
const saveProvider = result . provider ? ? provider
if ( "refresh" in result ) {
const { type : _ , provider : __ , refresh , access , expires , . . . extraFields } = result
2026-05-03 20:41:10 +00:00
yield * put ( saveProvider , {
2025-12-10 22:23:12 +00:00
type : "oauth" ,
refresh ,
access ,
expires ,
. . . extraFields ,
} )
}
if ( "key" in result ) {
2026-05-03 20:41:10 +00:00
yield * put ( saveProvider , {
2025-12-10 22:23:12 +00:00
type : "api" ,
key : result.key ,
2026-05-12 21:22:40 +00:00
. . . ( result . metadata ? { metadata : result.metadata } : { } ) ,
2025-12-10 22:23:12 +00:00
} )
}
2026-05-03 20:41:10 +00:00
yield * Prompt . log . success ( "Login successful" )
2025-12-10 22:23:12 +00:00
}
}
2026-05-03 20:41:10 +00:00
yield * Prompt . outro ( "Done" )
2025-12-10 22:23:12 +00:00
return true
}
if ( method . type === "api" ) {
2026-05-03 20:41:10 +00:00
const key = yield * Prompt . password ( {
2026-04-30 04:35:59 +00:00
message : "Enter your API key" ,
validate : ( x ) = > ( x && x . length > 0 ? undefined : "Required" ) ,
} )
2026-05-03 20:41:10 +00:00
const apiKey = yield * promptValue ( key )
2026-04-30 04:35:59 +00:00
const metadata = Object . keys ( inputs ) . length ? { metadata : inputs } : { }
2026-05-03 20:41:10 +00:00
const authorizeApi = method . authorize
if ( ! authorizeApi ) {
yield * put ( provider , {
2026-04-30 04:35:59 +00:00
type : "api" ,
2026-05-03 20:41:10 +00:00
key : apiKey ,
2026-04-30 04:35:59 +00:00
. . . metadata ,
2026-04-14 17:53:00 +00:00
} )
2026-05-03 20:41:10 +00:00
yield * Prompt . outro ( "Done" )
2025-12-10 22:23:12 +00:00
return true
}
2026-04-30 04:35:59 +00:00
2026-05-03 20:41:10 +00:00
const result = yield * cliTry ( "Failed to authorize: " , ( ) = > authorizeApi ( inputs ) )
2026-04-30 04:35:59 +00:00
if ( result . type === "failed" ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . error ( "Failed to authorize" )
2026-04-30 04:35:59 +00:00
}
if ( result . type === "success" ) {
const saveProvider = result . provider ? ? provider
2026-05-12 21:22:40 +00:00
const merged = { . . . ( metadata . metadata ? ? { } ) , . . . ( result . metadata ? ? { } ) }
2026-05-03 20:41:10 +00:00
yield * put ( saveProvider , {
2026-04-30 04:35:59 +00:00
type : "api" ,
2026-05-03 20:41:10 +00:00
key : result.key ? ? apiKey ,
2026-05-12 21:22:40 +00:00
. . . ( Object . keys ( merged ) . length ? { metadata : merged } : { } ) ,
2026-04-30 04:35:59 +00:00
} )
2026-05-03 20:41:10 +00:00
yield * Prompt . log . success ( "Login successful" )
2026-04-30 04:35:59 +00:00
}
2026-05-03 20:41:10 +00:00
yield * Prompt . outro ( "Done" )
2026-04-30 04:35:59 +00:00
return true
2025-12-10 22:23:12 +00:00
}
return false
2026-05-03 20:41:10 +00:00
} )
2025-06-11 04:21:46 +00:00
2026-02-17 21:21:49 +00:00
export function resolvePluginProviders ( input : {
hooks : Hooks [ ]
existingProviders : Record < string , unknown >
disabled : Set < string >
enabled? : Set < string >
providerNames : Record < string , string | undefined >
} ) : Array < { id : string ; name : string } > {
const seen = new Set < string > ( )
const result : Array < { id : string ; name : string } > = [ ]
for ( const hook of input . hooks ) {
if ( ! hook . auth ) continue
const id = hook . auth . provider
if ( seen . has ( id ) ) continue
seen . add ( id )
if ( Object . hasOwn ( input . existingProviders , id ) ) continue
if ( input . disabled . has ( id ) ) continue
if ( input . enabled && ! input . enabled . has ( id ) ) continue
result . push ( {
id ,
name : input.providerNames [ id ] ? ? id ,
} )
}
return result
}
2026-03-10 16:53:47 +00:00
export const ProvidersCommand = cmd ( {
command : "providers" ,
aliases : [ "auth" ] ,
describe : "manage AI providers and credentials" ,
2025-06-11 04:21:46 +00:00
builder : ( yargs ) = >
2026-03-10 16:53:47 +00:00
yargs . command ( ProvidersListCommand ) . command ( ProvidersLoginCommand ) . command ( ProvidersLogoutCommand ) . demandCommand ( ) ,
2025-07-29 23:30:24 +00:00
async handler() { } ,
2025-06-11 04:21:46 +00:00
} )
2026-05-03 03:22:44 +00:00
export const ProvidersListCommand = effectCmd ( {
2025-06-11 04:21:46 +00:00
command : "list" ,
aliases : [ "ls" ] ,
2026-03-10 16:53:47 +00:00
describe : "list providers and credentials" ,
2026-05-03 03:22:44 +00:00
// Lists global credentials + provider env vars; no project instance needed.
instance : false ,
handler : Effect.fn ( "Cli.providers.list" ) ( function * ( _args ) {
2026-05-03 03:42:40 +00:00
const authSvc = yield * Auth . Service
const modelsDev = yield * ModelsDev . Service
2025-06-11 04:21:46 +00:00
2026-05-03 15:42:57 +00:00
UI . empty ( )
const authPath = path . join ( Global . Path . data , "auth.json" )
const homedir = os . homedir ( )
const displayPath = authPath . startsWith ( homedir ) ? authPath . replace ( homedir , "~" ) : authPath
2026-05-03 20:41:10 +00:00
yield * Prompt . intro ( ` Credentials ${ UI . Style . TEXT_DIM } ${ displayPath } ` )
2026-05-03 15:42:57 +00:00
const results = Object . entries ( yield * Effect . orDie ( authSvc . all ( ) ) )
const database = yield * modelsDev . get ( )
for ( const [ providerID , result ] of results ) {
const name = database [ providerID ] ? . name || providerID
2026-05-03 20:41:10 +00:00
yield * Prompt . log . info ( ` ${ name } ${ UI . Style . TEXT_DIM } ${ result . type } ` )
2026-05-03 15:42:57 +00:00
}
2026-05-03 20:41:10 +00:00
yield * Prompt . outro ( ` ${ results . length } credentials ` )
2025-06-25 02:24:55 +00:00
2026-05-03 15:42:57 +00:00
const activeEnvVars : Array < { provider : string ; envVar : string } > = [ ]
2025-06-27 02:30:44 +00:00
2026-05-03 15:42:57 +00:00
for ( const [ providerID , provider ] of Object . entries ( database ) ) {
for ( const envVar of provider . env ) {
if ( process . env [ envVar ] ) {
activeEnvVars . push ( {
provider : provider.name || providerID ,
envVar ,
} )
2025-06-25 02:24:55 +00:00
}
}
2026-05-03 15:42:57 +00:00
}
2025-06-25 02:24:55 +00:00
2026-05-03 15:42:57 +00:00
if ( activeEnvVars . length > 0 ) {
UI . empty ( )
2026-05-03 20:41:10 +00:00
yield * Prompt . intro ( "Environment" )
2025-06-27 02:30:44 +00:00
2026-05-03 15:42:57 +00:00
for ( const { provider , envVar } of activeEnvVars ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . info ( ` ${ provider } ${ UI . Style . TEXT_DIM } ${ envVar } ` )
2026-05-03 03:23:47 +00:00
}
2026-05-03 15:42:57 +00:00
2026-05-03 20:41:10 +00:00
yield * Prompt . outro ( ` ${ activeEnvVars . length } environment variable ` + ( activeEnvVars . length === 1 ? "" : "s" ) )
2026-05-03 15:42:57 +00:00
}
2026-05-03 03:22:44 +00:00
} ) ,
2025-06-11 04:21:46 +00:00
} )
2026-05-03 03:22:44 +00:00
export const ProvidersLoginCommand = effectCmd ( {
2025-07-29 23:30:24 +00:00
command : "login [url]" ,
2025-06-23 17:00:21 +00:00
describe : "log in to a provider" ,
2025-07-29 23:30:24 +00:00
builder : ( yargs ) = >
2026-03-05 06:16:53 +00:00
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" ,
} ) ,
2026-05-03 03:22:44 +00:00
handler : Effect.fn ( "Cli.providers.login" ) ( function * ( args ) {
2026-05-03 15:42:57 +00:00
const authSvc = yield * Auth . Service
UI . empty ( )
2026-05-03 20:41:10 +00:00
yield * Prompt . intro ( "Add credential" )
2026-05-03 15:42:57 +00:00
if ( args . url ) {
const url = args . url . replace ( /\/+$/ , "" )
2026-05-03 20:41:10 +00:00
const wellknown = ( yield * cliTry ( ` Failed to load auth provider metadata from ${ url } : ` , ( ) = >
fetch ( ` ${ url } /.well-known/opencode ` ) . then ( ( x ) = > x . json ( ) ) ,
) ) as {
2026-05-03 15:44:02 +00:00
auth : { command : string [ ] ; env : string }
}
2026-05-03 20:41:10 +00:00
yield * Prompt . log . info ( ` Running \` ${ wellknown . auth . command . join ( " " ) } \` ` )
const abort = new AbortController ( )
const proc = Process . spawn ( wellknown . auth . command , { stdout : "pipe" , stderr : "inherit" , abort : abort.signal } )
2026-05-03 15:42:57 +00:00
if ( ! proc . stdout ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . error ( "Failed" )
yield * Prompt . outro ( "Done" )
2026-05-03 03:23:47 +00:00
return
}
2026-05-03 20:41:10 +00:00
const [ exit , token ] = yield * cliTry ( "Failed to run auth provider command: " , ( ) = >
Promise . all ( [ proc . exited , text ( proc . stdout ! ) ] ) ,
) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > abort . abort ( ) ) ) )
2026-05-03 15:42:57 +00:00
if ( exit !== 0 ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . error ( "Failed" )
yield * Prompt . outro ( "Done" )
2026-05-03 15:42:57 +00:00
return
}
yield * Effect . orDie ( authSvc . set ( url , { type : "wellknown" , key : wellknown.auth.env , token : token.trim ( ) } ) )
2026-05-03 20:41:10 +00:00
yield * Prompt . log . success ( "Logged into " + url )
yield * Prompt . outro ( "Done" )
2026-05-03 15:42:57 +00:00
return
}
2026-05-03 20:41:10 +00:00
const cfgSvc = yield * Config . Service
const pluginSvc = yield * Plugin . Service
const modelsDev = yield * ModelsDev . Service
2026-05-03 15:42:57 +00:00
yield * Effect . ignore ( modelsDev . refresh ( true ) )
2025-12-01 23:01:10 +00:00
2026-05-03 15:42:57 +00:00
const config = yield * cfgSvc . get ( )
2025-12-01 23:01:10 +00:00
2026-05-03 15:42:57 +00:00
const disabled = new Set ( config . disabled_providers ? ? [ ] )
const enabled = config . enabled_providers ? new Set ( config . enabled_providers ) : undefined
2025-12-01 23:01:10 +00:00
2026-05-03 15:42:57 +00:00
const allProviders = yield * modelsDev . get ( )
const providers : Record < string , ( typeof allProviders ) [ string ] > = { }
for ( const [ key , value ] of Object . entries ( allProviders ) ) {
if ( ( enabled ? enabled . has ( key ) : true ) && ! disabled . has ( key ) ) providers [ key ] = value
}
const hooks = yield * pluginSvc . list ( )
const priority : Record < string , number > = {
opencode : 0 ,
openai : 1 ,
"github-copilot" : 2 ,
google : 3 ,
anthropic : 4 ,
openrouter : 5 ,
vercel : 6 ,
}
const pluginProviders = resolvePluginProviders ( {
hooks ,
existingProviders : providers ,
disabled ,
enabled ,
providerNames : Object.fromEntries ( Object . entries ( config . provider ? ? { } ) . map ( ( [ id , p ] ) = > [ id , p . name ] ) ) ,
} )
const options = [
. . . pipe (
providers ,
values ( ) ,
sortBy (
( x ) = > priority [ x . id ] ? ? 99 ,
( x ) = > x . name ? ? x . id ,
2026-05-03 03:23:47 +00:00
) ,
2026-05-03 15:42:57 +00:00
map ( ( x ) = > ( {
2026-05-03 03:23:47 +00:00
label : x.name ,
value : x.id ,
2026-05-03 15:42:57 +00:00
hint : {
opencode : "recommended" ,
openai : "ChatGPT Plus/Pro or API key" ,
} [ x . id ] ,
2026-05-03 03:23:47 +00:00
} ) ) ,
2026-05-03 15:42:57 +00:00
) ,
. . . 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 ) {
2026-05-03 20:41:10 +00:00
return yield * fail ( ` Unknown provider " ${ input } " ` )
2026-05-03 15:42:57 +00:00
}
provider = match . value
} else {
2026-05-03 20:41:10 +00:00
provider = yield * promptValue (
yield * Prompt . autocomplete ( {
2026-05-03 03:23:47 +00:00
message : "Select provider" ,
maxItems : 8 ,
2026-05-03 15:42:57 +00:00
options : [ . . . options , { value : "other" , label : "Other" } ] ,
} ) ,
)
}
2025-06-22 23:11:37 +00:00
2026-05-03 15:42:57 +00:00
const plugin = hooks . findLast ( ( x ) = > x . auth ? . provider === provider )
if ( plugin && plugin . auth ) {
2026-05-03 20:41:10 +00:00
const handled = yield * handlePluginAuth ( { auth : plugin.auth ! } , provider , args . method )
2026-05-03 15:42:57 +00:00
if ( handled ) return
}
2025-12-10 22:23:12 +00:00
2026-05-03 15:42:57 +00:00
if ( provider === "other" ) {
2026-05-03 20:41:10 +00:00
provider = ( yield * promptValue (
yield * Prompt . text ( {
2026-05-03 03:23:47 +00:00
message : "Enter provider id" ,
validate : ( x ) = > ( x && x . match ( /^[0-9a-z-]+$/ ) ? undefined : "a-z, 0-9 and hyphens only" ) ,
2026-05-03 15:42:57 +00:00
} ) ,
2026-05-03 20:41:10 +00:00
) ) . replace ( /^@ai-sdk\// , "" )
2025-11-06 18:03:02 +00:00
2026-05-03 15:42:57 +00:00
const customPlugin = hooks . findLast ( ( x ) = > x . auth ? . provider === provider )
if ( customPlugin && customPlugin . auth ) {
2026-05-03 20:41:10 +00:00
const handled = yield * handlePluginAuth ( { auth : customPlugin.auth ! } , provider , args . method )
2026-05-03 15:42:57 +00:00
if ( handled ) return
2026-05-03 03:23:47 +00:00
}
2025-10-04 05:10:38 +00:00
2026-05-03 20:41:10 +00:00
yield * Prompt . log . warn (
2026-05-03 15:42:57 +00:00
` This only stores a credential for ${ provider } - you will need configure it in opencode.json, check the docs for examples. ` ,
)
}
2025-11-06 18:03:02 +00:00
2026-05-03 15:42:57 +00:00
if ( provider === "amazon-bedrock" ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . info (
2026-05-03 15:42:57 +00:00
"Amazon Bedrock authentication priority:\n" +
" 1. Bearer token (AWS_BEARER_TOKEN_BEDROCK or /connect)\n" +
" 2. AWS credential chain (profile, access keys, IAM roles, EKS IRSA)\n\n" +
"Configure via opencode.json options (profile, region, endpoint) or\n" +
"AWS environment variables (AWS_PROFILE, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_WEB_IDENTITY_TOKEN_FILE)." ,
)
}
2025-06-22 23:11:37 +00:00
2026-05-03 15:42:57 +00:00
if ( provider === "opencode" ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . info ( "Create an api key at https://opencode.ai/auth" )
2026-05-03 15:42:57 +00:00
}
2025-12-30 15:45:09 +00:00
2026-05-03 15:42:57 +00:00
if ( provider === "vercel" ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . info ( "You can create an api key at https://vercel.link/ai-gateway-token" )
2026-05-03 15:42:57 +00:00
}
2025-07-19 14:08:24 +00:00
2026-05-03 15:42:57 +00:00
if ( [ "cloudflare" , "cloudflare-ai-gateway" ] . includes ( provider ) ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . info (
2026-05-03 15:42:57 +00:00
"Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Read more: https://opencode.ai/docs/providers/#cloudflare-ai-gateway" ,
)
}
2026-05-03 20:41:10 +00:00
const key = yield * Prompt . password ( {
message : "Enter your API key" ,
validate : ( x ) = > ( x && x . length > 0 ? undefined : "Required" ) ,
} )
const apiKey = yield * promptValue ( key )
yield * Effect . orDie ( authSvc . set ( provider , { type : "api" , key : apiKey } ) )
2026-05-03 03:23:47 +00:00
2026-05-03 20:41:10 +00:00
yield * Prompt . outro ( "Done" )
2026-05-03 03:22:44 +00:00
} ) ,
2025-06-11 04:21:46 +00:00
} )
2026-05-03 03:22:44 +00:00
export const ProvidersLogoutCommand = effectCmd ( {
2025-06-11 04:21:46 +00:00
command : "logout" ,
2025-06-23 17:00:21 +00:00
describe : "log out from a configured provider" ,
2026-05-03 03:22:44 +00:00
// Removes a global auth credential; no project instance needed.
instance : false ,
handler : Effect.fn ( "Cli.providers.logout" ) ( function * ( _args ) {
2026-05-03 03:42:40 +00:00
const authSvc = yield * Auth . Service
const modelsDev = yield * ModelsDev . Service
2026-05-03 15:42:57 +00:00
UI . empty ( )
const credentials : Array < [ string , Auth . Info ] > = Object . entries ( yield * Effect . orDie ( authSvc . all ( ) ) )
2026-05-03 20:41:10 +00:00
yield * Prompt . intro ( "Remove credential" )
2026-05-03 15:42:57 +00:00
if ( credentials . length === 0 ) {
2026-05-03 20:41:10 +00:00
yield * Prompt . log . error ( "No credentials found" )
2026-05-03 15:42:57 +00:00
return
}
const database = yield * modelsDev . get ( )
2026-05-03 20:41:10 +00:00
const selected = yield * Prompt . select ( {
message : "Select provider" ,
options : credentials.map ( ( [ key , value ] ) = > ( {
label : ( database [ key ] ? . name || key ) + UI . Style . TEXT_DIM + " (" + value . type + ")" ,
value : key ,
} ) ) ,
} )
yield * Effect . orDie ( authSvc . remove ( yield * promptValue ( selected ) ) )
yield * Prompt . outro ( "Logout successful" )
2026-05-03 03:22:44 +00:00
} ) ,
2025-06-11 04:21:46 +00:00
} )