diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index 76afa038..ace51b26 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -16,7 +16,13 @@ export namespace Auth { key: z.string(), }) - export const Info = z.discriminatedUnion("type", [Oauth, Api]) + export const WellKnown = z.object({ + type: z.literal("wellknown"), + key: z.string(), + token: z.string(), + }) + + export const Info = z.discriminatedUnion("type", [Oauth, Api, WellKnown]) export type Info = z.infer const filepath = path.join(Global.Path.data, "auth.json") diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts index 854a8c06..a9201a88 100644 --- a/packages/opencode/src/cli/cmd/auth.ts +++ b/packages/opencode/src/cli/cmd/auth.ts @@ -16,7 +16,7 @@ export const AuthCommand = cmd({ describe: "manage credentials", builder: (yargs) => yargs.command(AuthLoginCommand).command(AuthLogoutCommand).command(AuthListCommand).demandCommand(), - async handler() { }, + async handler() {}, }) export const AuthListCommand = cmd({ @@ -61,20 +61,45 @@ export const AuthListCommand = cmd({ prompts.log.info(`${provider} ${UI.Style.TEXT_DIM}${envVar}`) } - prompts.outro( - `${activeEnvVars.length} environment variable` - + (activeEnvVars.length === 1 ? "" : "s") - ) + prompts.outro(`${activeEnvVars.length} environment variable` + (activeEnvVars.length === 1 ? "" : "s")) } }, }) export const AuthLoginCommand = cmd({ - command: "login", + command: "login [url]", describe: "log in to a provider", - async handler() { - UI.empty() + builder: (yargs) => + yargs.positional("url", { + describe: "opencode auth provider", + type: "string", + }), + async handler(args) { prompts.intro("Add credential") + if (args.url) { + const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json()) + 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) + prompts.outro("Done") + return + } + UI.empty() const providers = await ModelsDev.get() const priority: Record = { anthropic: 0, diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 2d896a53..24903da5 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -11,11 +11,13 @@ import { lazy } from "../util/lazy" import { NamedError } from "../util/error" import matter from "gray-matter" import { Flag } from "../flag/flag" +import { Auth } from "../auth" export namespace Config { const log = Log.create({ service: "config" }) export const state = App.state("config", async (app) => { + const auth = await Auth.all() let result = await global() for (const file of ["opencode.jsonc", "opencode.json"]) { const found = await Filesystem.findUp(file, app.path.cwd, app.path.root) @@ -30,6 +32,14 @@ export namespace Config { log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG }) } + for (const [key, value] of Object.entries(auth)) { + if (value.type === "wellknown") { + process.env[value.key] = value.token + const wellknown = await fetch(`${key}/.well-known/opencode`).then((x) => x.json()) + result = mergeDeep(result, await loadRaw(JSON.stringify(wellknown.config ?? {}), process.cwd())) + } + } + result.agent = result.agent || {} const markdownAgents = [ ...(await Filesystem.globUp("agent/*.md", Global.Path.config, Global.Path.config)), @@ -307,7 +317,10 @@ export namespace Config { throw new JsonError({ path: configPath }, { cause: err }) }) if (!text) return {} + return loadRaw(text, configPath) + } + async function loadRaw(text: string, configPath: string) { text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => { return process.env[varName] || "" })