2025-06-11 04:21:46 +00:00
import { Auth } from "../../auth"
import { cmd } from "./cmd"
import * as prompts from "@clack/prompts"
import { UI } from "../ui"
import { ModelsDev } from "../../provider/models"
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"
import { Global } from "../../global"
2025-08-14 20:24:46 +00:00
import { Plugin } from "../../plugin"
2025-09-01 21:15:49 +00:00
import { Instance } from "../../project/instance"
2025-06-11 04:21:46 +00:00
export const AuthCommand = cmd ( {
command : "auth" ,
2025-06-23 17:00:21 +00:00
describe : "manage credentials" ,
2025-06-11 04:21:46 +00:00
builder : ( yargs ) = >
2025-07-07 19:53:43 +00:00
yargs . command ( AuthLoginCommand ) . command ( AuthLogoutCommand ) . command ( AuthListCommand ) . demandCommand ( ) ,
2025-07-29 23:30:24 +00:00
async handler() { } ,
2025-06-11 04:21:46 +00:00
} )
export const AuthListCommand = cmd ( {
command : "list" ,
aliases : [ "ls" ] ,
describe : "list providers" ,
async handler() {
UI . empty ( )
2025-06-25 02:24:55 +00:00
const authPath = path . join ( Global . Path . data , "auth.json" )
const homedir = os . homedir ( )
2025-07-07 19:53:43 +00:00
const displayPath = authPath . startsWith ( homedir ) ? authPath . replace ( homedir , "~" ) : authPath
2025-06-25 02:24:55 +00:00
prompts . intro ( ` Credentials ${ UI . Style . TEXT_DIM } ${ displayPath } ` )
2025-06-11 04:21:46 +00:00
const results = await Auth . all ( ) . then ( ( x ) = > Object . entries ( x ) )
const database = await ModelsDev . get ( )
for ( const [ providerID , result ] of results ) {
const name = database [ providerID ] ? . name || providerID
2025-06-25 02:24:55 +00:00
prompts . log . info ( ` ${ name } ${ UI . Style . TEXT_DIM } ${ result . type } ` )
2025-06-11 04:21:46 +00:00
}
prompts . outro ( ` ${ results . length } credentials ` )
2025-06-25 02:24:55 +00:00
// Environment variables section
2025-06-27 02:30:44 +00:00
const activeEnvVars : Array < { provider : string ; envVar : string } > = [ ]
2025-06-25 02:24:55 +00:00
for ( const [ providerID , provider ] of Object . entries ( database ) ) {
for ( const envVar of provider . env ) {
if ( process . env [ envVar ] ) {
2025-06-27 02:30:44 +00:00
activeEnvVars . push ( {
provider : provider.name || providerID ,
envVar ,
2025-06-25 02:24:55 +00:00
} )
}
}
}
if ( activeEnvVars . length > 0 ) {
UI . empty ( )
prompts . intro ( "Environment" )
2025-06-27 02:30:44 +00:00
2025-06-25 02:24:55 +00:00
for ( const { provider , envVar } of activeEnvVars ) {
prompts . log . info ( ` ${ provider } ${ UI . Style . TEXT_DIM } ${ envVar } ` )
}
2025-06-27 02:30:44 +00:00
2025-07-29 23:30:24 +00:00
prompts . outro ( ` ${ activeEnvVars . length } environment variable ` + ( activeEnvVars . length === 1 ? "" : "s" ) )
2025-06-25 02:24:55 +00:00
}
2025-06-11 04:21:46 +00:00
} ,
} )
export const AuthLoginCommand = cmd ( {
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 ) = >
yargs . positional ( "url" , {
describe : "opencode auth provider" ,
type : "string" ,
} ) ,
async handler ( args ) {
2025-09-19 09:11:29 +00:00
await Instance . provide ( {
directory : process.cwd ( ) ,
async fn() {
UI . empty ( )
prompts . intro ( "Add credential" )
if ( args . url ) {
2025-10-31 19:07:36 +00:00
const wellknown = await fetch ( ` ${ args . url } /.well-known/opencode ` ) . then ( ( x ) = > x . json ( ) as any )
2025-09-19 09:11:29 +00:00
prompts . log . info ( ` Running \` ${ wellknown . auth . command . join ( " " ) } \` ` )
const proc = Bun . spawn ( {
cmd : wellknown.auth.command ,
stdout : "pipe" ,
} )
const exit = await proc . exited
if ( exit !== 0 ) {
prompts . log . error ( "Failed" )
prompts . outro ( "Done" )
return
}
const token = await new Response ( proc . stdout ) . text ( )
await Auth . set ( args . url , {
type : "wellknown" ,
key : wellknown.auth.env ,
token : token.trim ( ) ,
} )
prompts . log . success ( "Logged into " + args . url )
2025-08-14 20:24:46 +00:00
prompts . outro ( "Done" )
return
}
2025-09-19 09:11:29 +00:00
await ModelsDev . refresh ( ) . catch ( ( ) = > { } )
const providers = await ModelsDev . get ( )
const priority : Record < string , number > = {
opencode : 0 ,
anthropic : 1 ,
"github-copilot" : 2 ,
openai : 3 ,
google : 4 ,
openrouter : 5 ,
vercel : 6 ,
}
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 : priority [ x . id ] <= 1 ? "recommended" : undefined ,
} ) ) ,
2025-08-14 20:24:46 +00:00
) ,
2025-09-19 09:11:29 +00:00
{
value : "other" ,
label : "Other" ,
} ,
] ,
} )
2025-07-14 21:55:06 +00:00
2025-09-19 09:11:29 +00:00
if ( prompts . isCancel ( provider ) ) throw new UI . CancelledError ( )
2025-07-14 21:55:06 +00:00
2025-09-19 09:11:29 +00:00
const plugin = await Plugin . list ( ) . then ( ( x ) = > x . find ( ( x ) = > x . auth ? . provider === provider ) )
if ( plugin && plugin . auth ) {
let index = 0
if ( plugin . auth . methods . length > 1 ) {
const method = await prompts . select ( {
message : "Login method" ,
options : [
. . . plugin . auth . methods . map ( ( x , index ) = > ( {
label : x.label ,
value : index.toString ( ) ,
} ) ) ,
] ,
} )
if ( prompts . isCancel ( method ) ) throw new UI . CancelledError ( )
index = parseInt ( method )
2025-08-14 20:24:46 +00:00
}
2025-09-19 09:11:29 +00:00
const method = plugin . auth . methods [ index ]
if ( method . type === "oauth" ) {
await new Promise ( ( resolve ) = > setTimeout ( resolve , 10 ) )
const authorize = await method . authorize ( )
2025-06-11 04:21:46 +00:00
2025-09-19 09:11:29 +00:00
if ( authorize . url ) {
prompts . log . info ( "Go to: " + authorize . url )
2025-08-14 20:24:46 +00:00
}
2025-09-19 09:11:29 +00:00
if ( authorize . method === "auto" ) {
if ( authorize . instructions ) {
prompts . log . info ( authorize . instructions )
}
const spinner = prompts . spinner ( )
spinner . start ( "Waiting for authorization..." )
const result = await authorize . callback ( )
if ( result . type === "failed" ) {
spinner . stop ( "Failed to authorize" , 1 )
2025-08-18 21:12:21 +00:00
}
2025-09-19 09:11:29 +00:00
if ( result . type === "success" ) {
if ( "refresh" in result ) {
await Auth . set ( provider , {
type : "oauth" ,
refresh : result.refresh ,
access : result.access ,
expires : result.expires ,
} )
}
if ( "key" in result ) {
await Auth . set ( provider , {
type : "api" ,
key : result.key ,
} )
}
spinner . stop ( "Login successful" )
2025-08-18 21:12:21 +00:00
}
2025-08-14 20:24:46 +00:00
}
2025-07-14 21:55:06 +00:00
2025-09-19 09:11:29 +00:00
if ( authorize . method === "code" ) {
const code = await prompts . text ( {
message : "Paste the authorization code here: " ,
validate : ( x ) = > ( x && x . length > 0 ? undefined : "Required" ) ,
} )
if ( prompts . isCancel ( code ) ) throw new UI . CancelledError ( )
const result = await authorize . callback ( code )
if ( result . type === "failed" ) {
prompts . log . error ( "Failed to authorize" )
2025-08-18 21:12:21 +00:00
}
2025-09-19 09:11:29 +00:00
if ( result . type === "success" ) {
if ( "refresh" in result ) {
await Auth . set ( provider , {
type : "oauth" ,
refresh : result.refresh ,
access : result.access ,
expires : result.expires ,
} )
}
if ( "key" in result ) {
await Auth . set ( provider , {
type : "api" ,
key : result.key ,
} )
}
prompts . log . success ( "Login successful" )
2025-08-18 21:12:21 +00:00
}
2025-08-14 20:24:46 +00:00
}
2025-09-19 09:11:29 +00:00
prompts . outro ( "Done" )
return
2025-08-14 20:24:46 +00:00
}
2025-07-14 21:55:06 +00:00
}
2025-06-22 23:11:37 +00:00
2025-09-19 09:11:29 +00:00
if ( provider === "other" ) {
provider = 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 ( )
prompts . log . warn (
` This only stores a credential for ${ provider } - you will need configure it in opencode.json, check the docs for examples. ` ,
)
}
2025-06-22 23:11:37 +00:00
2025-09-19 09:11:29 +00:00
if ( provider === "amazon-bedrock" ) {
prompts . log . info (
"Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID" ,
)
prompts . outro ( "Done" )
return
}
2025-06-22 23:11:37 +00:00
2025-10-04 05:10:38 +00:00
if ( provider === "google-vertex" ) {
prompts . log . info (
"Google Cloud Vertex AI uses Application Default Credentials. Set GOOGLE_APPLICATION_CREDENTIALS or run 'gcloud auth application-default login'. Optionally set GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION (or VERTEX_LOCATION)" ,
)
prompts . outro ( "Done" )
return
}
2025-09-19 09:11:29 +00:00
if ( provider === "opencode" ) {
prompts . log . info ( "Create an api key at https://opencode.ai/auth" )
}
2025-09-03 18:43:50 +00:00
2025-09-19 09:11:29 +00:00
if ( provider === "vercel" ) {
prompts . log . info ( "You can create an api key at https://vercel.link/ai-gateway-token" )
}
2025-06-22 23:11:37 +00:00
2025-09-19 09:11:29 +00:00
const key = await prompts . password ( {
message : "Enter your API key" ,
validate : ( x ) = > ( x && x . length > 0 ? undefined : "Required" ) ,
} )
if ( prompts . isCancel ( key ) ) throw new UI . CancelledError ( )
await Auth . set ( provider , {
type : "api" ,
key ,
} )
2025-07-19 14:08:24 +00:00
2025-09-19 09:11:29 +00:00
prompts . outro ( "Done" )
} ,
2025-06-11 04:21:46 +00:00
} )
} ,
} )
export const AuthLogoutCommand = cmd ( {
command : "logout" ,
2025-06-23 17:00:21 +00:00
describe : "log out from a configured provider" ,
2025-06-11 04:21:46 +00:00
async handler() {
UI . empty ( )
const credentials = await Auth . all ( ) . then ( ( x ) = > Object . entries ( x ) )
prompts . intro ( "Remove credential" )
if ( credentials . length === 0 ) {
prompts . log . error ( "No credentials found" )
return
}
const database = await ModelsDev . get ( )
const providerID = await prompts . select ( {
2025-06-11 04:27:46 +00:00
message : "Select provider" ,
2025-06-11 04:21:46 +00:00
options : credentials.map ( ( [ key , value ] ) = > ( {
2025-07-07 19:53:43 +00:00
label : ( database [ key ] ? . name || key ) + UI . Style . TEXT_DIM + " (" + value . type + ")" ,
2025-06-11 04:21:46 +00:00
value : key ,
} ) ) ,
} )
if ( prompts . isCancel ( providerID ) ) throw new UI . CancelledError ( )
await Auth . remove ( providerID )
prompts . outro ( "Logout successful" )
} ,
} )