Refactor authentication system to consolidate auth flow and remove provider-based commands

🤖 Generated with [OpenCode](https://opencode.ai)

Co-Authored-By: OpenCode <noreply@opencode.ai>
This commit is contained in:
Dax Raad
2025-06-11 00:21:46 -04:00
parent b8e7d06356
commit 83eb61fd5f
12 changed files with 312 additions and 239 deletions

View File

@@ -0,0 +1,144 @@
import { AuthAnthropic } from "../../auth/anthropic"
import { Auth } from "../../auth"
import { cmd } from "./cmd"
import * as prompts from "@clack/prompts"
import open from "open"
import { UI } from "../ui"
import { ModelsDev } from "../../provider/models"
export const AuthCommand = cmd({
command: "auth",
builder: (yargs) =>
yargs
.command(AuthLoginCommand)
.command(AuthLogoutCommand)
.command(AuthListCommand)
.demandCommand(),
async handler(args) {},
})
export const AuthListCommand = cmd({
command: "list",
aliases: ["ls"],
describe: "list providers",
async handler() {
UI.empty()
prompts.intro("Credentials")
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
prompts.log.info(`${name} ${Bun.color("gray", "ansi")}(${result.type})`)
}
prompts.outro(`${results.length} credentials`)
},
})
export const AuthLoginCommand = cmd({
command: "login",
describe: "login to a provider",
async handler() {
UI.empty()
prompts.intro("Add credential")
const provider = await prompts.select({
message: "Select provider",
maxItems: 2,
options: [
{
label: "Anthropic",
value: "anthropic",
},
{
label: "OpenAI",
value: "openai",
},
{
label: "Google",
value: "google",
},
],
})
if (prompts.isCancel(provider)) throw new UI.CancelledError()
if (provider === "anthropic") {
const method = await prompts.select({
message: "Login method",
options: [
{
label: "Claude Pro/Max",
value: "oauth",
},
{
label: "API Key",
value: "api",
},
],
})
if (prompts.isCancel(method)) throw new UI.CancelledError()
if (method === "oauth") {
// some weird bug where program exits without this
await new Promise((resolve) => setTimeout(resolve, 10))
const { url, verifier } = await AuthAnthropic.authorize()
prompts.note("Opening browser...")
await open(url)
prompts.log.info(url)
const code = await prompts.text({
message: "Paste the authorization code here: ",
validate: (x) => (x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(code)) throw new UI.CancelledError()
await AuthAnthropic.exchange(code, verifier)
.then(() => {
prompts.log.success("Login successful")
})
.catch(() => {
prompts.log.error("Invalid code")
})
prompts.outro("Done")
return
}
}
const key = await prompts.password({
message: "Enter your API key",
validate: (x) => (x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(key)) throw new UI.CancelledError()
await Auth.set(provider, {
type: "api",
key,
})
prompts.outro("Done")
},
})
export const AuthLogoutCommand = cmd({
command: "logout",
describe: "logout from a configured provider",
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({
message: "Select credential",
options: credentials.map(([key, value]) => ({
label: database[key]?.name || key,
value: key,
})),
})
if (prompts.isCancel(providerID)) throw new UI.CancelledError()
await Auth.remove(providerID)
prompts.outro("Logout successful")
},
})

View File

@@ -1,120 +0,0 @@
import { App } from "../../app/app"
import { AuthAnthropic } from "../../auth/anthropic"
import { AuthKeys } from "../../auth/keys"
import { cmd } from "./cmd"
import * as prompts from "@clack/prompts"
import open from "open"
import { VERSION } from "../version"
import { Provider } from "../../provider/provider"
import { UI } from "../ui"
export const ProviderCommand = cmd({
command: "provider",
builder: (yargs) =>
yargs
.command(ProviderAddCommand)
.command(ProviderListCommand)
.demandCommand(),
describe: "initialize opencode",
async handler() {},
})
export const ProviderListCommand = cmd({
command: "list",
aliases: ["ls"],
describe: "list providers",
async handler() {
await App.provide({ cwd: process.cwd(), version: VERSION }, async () => {
prompts.intro("Providers")
const providers = await Provider.list().then((x) => Object.values(x))
for (const value of providers) {
prompts.log.success(value.info.name + " (" + value.source + ")")
}
prompts.outro(`${providers.length} configured`)
})
},
})
export const ProviderAddCommand = cmd({
command: "add",
describe: "add credentials for various providers",
async handler() {
await App.provide({ cwd: process.cwd(), version: VERSION }, async () => {
const providers = await Provider.list()
prompts.intro("Add provider")
const provider = await prompts.select({
message: "Select",
maxItems: 2,
options: [
{
label: "Anthropic",
value: "anthropic",
hint: providers["anthropic"] ? "configured" : "",
},
{
label: "OpenAI",
value: "openai",
hint: providers["openai"] ? "configured" : "",
},
{
label: "Google",
value: "google",
hint: providers["google"] ? "configured" : "",
},
],
})
if (prompts.isCancel(provider)) throw new UI.CancelledError({})
if (provider === "anthropic") {
const method = await prompts.select({
message: "Login method",
options: [
{
label: "Claude Pro/Max",
value: "oauth",
},
{
label: "API Key",
value: "api",
},
],
})
if (prompts.isCancel(method)) throw new UI.CancelledError({})
if (method === "oauth") {
// some weird bug where program exits without this
await new Promise((resolve) => setTimeout(resolve, 10))
const { url, verifier } = await AuthAnthropic.authorize()
prompts.note("Opening browser...")
await open(url)
prompts.log.info(url)
const code = await prompts.text({
message: "Paste the authorization code here: ",
validate: (x) => (x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(code)) throw new UI.CancelledError({})
await AuthAnthropic.exchange(code, verifier)
.then(() => {
prompts.log.success("Login successful")
})
.catch(() => {
prompts.log.error("Invalid code")
})
prompts.outro("Done")
return
}
}
const key = await prompts.password({
message: "Enter your API key",
validate: (x) => (x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(key)) throw new UI.CancelledError({})
await AuthKeys.set(provider, key)
prompts.outro("Done")
})
},
})

View File

@@ -33,7 +33,7 @@ export const RunCommand = {
await App.provide(
{
cwd: process.cwd(),
version: "0.0.0",
version: VERSION,
},
async () => {
await Share.init()
@@ -47,7 +47,7 @@ export const RunCommand = {
UI.empty()
UI.println(
UI.Style.TEXT_INFO_BOLD +
"~ https://dev.opencode.ai/s?id=" +
"~ https://dev.opencode.ai/s/" +
session.id.slice(-8),
)
UI.empty()