diff --git a/packages/opencode/src/cli/cmd/tui/attach.ts b/packages/opencode/src/cli/cmd/tui/attach.ts index e892f9922d1b..e7e3a5b99c7e 100644 --- a/packages/opencode/src/cli/cmd/tui/attach.ts +++ b/packages/opencode/src/cli/cmd/tui/attach.ts @@ -5,6 +5,7 @@ import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" import { TuiConfig } from "@/config/tui" import { Instance } from "@/project/instance" import { existsSync } from "fs" +import { getAuthorizationHeader } from "@/flag/auth" export const AttachCommand = cmd({ command: "attach ", @@ -38,6 +39,11 @@ export const AttachCommand = cmd({ alias: ["p"], type: "string", describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)", + }) + .option("username", { + alias: ["u"], + type: "string", + describe: "basic auth username (defaults to OPENCODE_SERVER_USERNAME or 'opencode')", }), handler: async (args) => { const unguard = win32InstallCtrlCGuard() @@ -61,10 +67,9 @@ export const AttachCommand = cmd({ } })() const headers = (() => { - const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD - if (!password) return undefined - const auth = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}` - return { Authorization: auth } + const Authorization = getAuthorizationHeader({ passwordFromCli: args.password, usernameFromCli: args.username }) + if (!Authorization) return undefined + return { Authorization } })() const config = await Instance.provide({ directory: directory && existsSync(directory) ? directory : process.cwd(), diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 14a9c88731f2..bfbc2d29c95b 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -14,6 +14,7 @@ import type { EventSource } from "./context/sdk" import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" import { TuiConfig } from "@/config/tui" import { Instance } from "@/project/instance" +import { getAuthorizationHeader } from "@/flag/auth" declare global { const OPENCODE_WORKER_PATH: string @@ -25,6 +26,7 @@ function createWorkerFetch(client: RpcClient): typeof fetch { const fn = async (input: RequestInfo | URL, init?: RequestInit): Promise => { const request = new Request(input, init) const body = request.body ? await request.text() : undefined + const result = await client.call("fetch", { url: request.url, method: request.method, @@ -182,7 +184,16 @@ export const TuiThreadCommand = cmd({ const transport = external ? { url: (await client.call("server", network)).url, - fetch: undefined, + fetch: (() => { + const authHeader = getAuthorizationHeader() + if (!authHeader) return undefined + + return (async (input: RequestInfo | URL, init?: RequestInit): Promise => { + const request = new Request(input, init) + request.headers.set("Authorization", authHeader) + return fetch(request) + }) as typeof fetch + })(), events: undefined, } : { diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 4452d6d764aa..27b8c912bba1 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -9,7 +9,7 @@ import { Config } from "@/config/config" import { GlobalBus } from "@/bus/global" import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2" import type { BunWebSocketData } from "hono/bun" -import { Flag } from "@/flag/flag" +import { getAuthorizationHeader } from "@/flag/auth" import { setTimeout as sleep } from "node:timers/promises" await Log.init({ @@ -144,10 +144,3 @@ export const rpc = { } Rpc.listen(rpc) - -function getAuthorizationHeader(): string | undefined { - const password = Flag.OPENCODE_SERVER_PASSWORD - if (!password) return undefined - const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode" - return `Basic ${btoa(`${username}:${password}`)}` -} diff --git a/packages/opencode/src/flag/auth.ts b/packages/opencode/src/flag/auth.ts new file mode 100644 index 000000000000..01477877ced1 --- /dev/null +++ b/packages/opencode/src/flag/auth.ts @@ -0,0 +1,11 @@ +import { Flag } from "@/flag/flag" + +export function getAuthorizationHeader(options?: { + passwordFromCli?: string + usernameFromCli?: string +}): string | undefined { + const password = options?.passwordFromCli ?? Flag.OPENCODE_SERVER_PASSWORD + if (!password) return undefined + const username = options?.usernameFromCli ?? Flag.OPENCODE_SERVER_USERNAME ?? "opencode" + return `Basic ${btoa(`${username}:${password}`)}` +} diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index e65d21bfd607..d42a45d26b25 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -7,6 +7,7 @@ import { Server } from "../server/server" import { BunProc } from "../bun" import { Instance } from "../project/instance" import { Flag } from "../flag/flag" +import { getAuthorizationHeader } from "../flag/auth" import { CodexAuthPlugin } from "./codex" import { Session } from "../session" import { NamedError } from "@opencode-ai/util/error" @@ -22,11 +23,16 @@ export namespace Plugin { const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin, GitlabAuthPlugin] const state = Instance.state(async () => { + const authHeader = getAuthorizationHeader() + const client = createOpencodeClient({ baseUrl: "http://localhost:4096", directory: Instance.directory, - // @ts-ignore - fetch type incompatibility - fetch: async (...args) => Server.App().fetch(...args), + fetch: (async (input, init) => { + const request = new Request(input, init) + if (authHeader) request.headers.set("Authorization", authHeader) + return Server.App().fetch(request) + }) as typeof fetch, }) const config = await Config.get() const hooks: Hooks[] = []