feat: add server link sharing

This commit is contained in:
Dax Raad 2026-07-08 18:41:08 -04:00
commit 7698a5e6ac
25 changed files with 1586 additions and 1194 deletions

View file

@ -225,6 +225,7 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
}),
],
}),
Spec.make("link", { description: "Show server connection information" }),
Spec.make("serve", {
description: "Start the v2 API server",
params: {

View file

@ -0,0 +1,43 @@
import { EOL } from "os"
import { Effect } from "effect"
import { Service } from "@opencode-ai/client/effect"
import { OpenCode } from "@opencode-ai/client/promise"
import { renderUnicodeCompact } from "uqr"
import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
import { ServiceConfig } from "../../services/service-config"
export default Runtime.handler(
Commands.commands.link,
Effect.fn("cli.link")(function* () {
const transport = yield* Service.start(yield* ServiceConfig.options())
const password = yield* ServiceConfig.password()
const server = yield* Effect.tryPromise(() =>
OpenCode.make({ baseUrl: transport.url, headers: transport.headers }).server.get(),
)
const info = { urls: server.urls, username: "opencode", password }
process.stdout.write(
[
"",
` URLs ${info.urls[0] ?? "(none)"}`,
...info.urls.slice(1).map((url) => ` ${url}`),
` Username ${info.username}`,
` Password ${info.password}`,
"",
" Scan to connect",
"",
renderUnicodeCompact(JSON.stringify(info), { border: 2 })
.split(EOL)
.map((line) => " " + line)
.join(EOL),
"",
].join(EOL) + EOL,
)
const hostname = new URL(transport.url).hostname
if (!["localhost", "127.0.0.1", "[::1]"].includes(hostname)) return
process.stderr.write(
` Run \`opencode service set hostname 0.0.0.0\` to access the service remotely.${EOL}${EOL}`,
)
}),
)

View file

@ -36,6 +36,7 @@ const Handlers = Runtime.handlers(Commands, {
migrate: () => import("./commands/handlers/migrate"),
mini: () => import("./commands/handlers/mini"),
run: () => import("./commands/handlers/run"),
link: () => import("./commands/handlers/link"),
service: {
start: () => import("./commands/handlers/service/start"),
restart: () => import("./commands/handlers/service/restart"),

View file

@ -30,6 +30,7 @@ export function runTui(
return yield* run({
client: createOpencodeClient({ ...options, directory }),
api,
link: linkCredentials(transport),
discover: discover
? async () => {
const next = await discover()
@ -64,3 +65,12 @@ export function runTui(
})
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
}
function linkCredentials(transport: Service.Transport) {
const authorization = new Headers(transport.headers).get("authorization")
if (!authorization?.startsWith("Basic ")) return { username: "opencode", password: "" }
const value = atob(authorization.slice("Basic ".length))
const separator = value.indexOf(":")
if (separator === -1) return { username: "opencode", password: "" }
return { username: value.slice(0, separator), password: value.slice(separator + 1) }
}