import { cmd } from "@/cli/cmd/cmd" import { tui } from "./app" import { Rpc } from "@/util/rpc" import { type rpc } from "./worker" import path from "path" import { UI } from "@/cli/ui" import { iife } from "@/util/iife" import { Log } from "@/util/log" import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network" import { Config } from "@/config/config" declare global { const OPENCODE_WORKER_PATH: string } export const TuiThreadCommand = cmd({ command: "$0 [project]", describe: "start opencode tui", builder: (yargs) => withNetworkOptions(yargs) .positional("project", { type: "string", describe: "path to start opencode in", }) .option("model", { type: "string", alias: ["m"], describe: "model to use in the format of provider/model", }) .option("continue", { alias: ["c"], describe: "continue the last session", type: "boolean", }) .option("session", { alias: ["s"], type: "string", describe: "session id to continue", }) .option("prompt", { type: "string", describe: "prompt to use", }) .option("agent", { type: "string", describe: "agent to use", }), handler: async (args) => { // Resolve relative paths against PWD to preserve behavior when using --cwd flag const baseCwd = process.env.PWD ?? process.cwd() const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd() const localWorker = new URL("./worker.ts", import.meta.url) const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url) const workerPath = await iife(async () => { if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH if (await Bun.file(distWorker).exists()) return distWorker return localWorker }) try { process.chdir(cwd) } catch (e) { UI.error("Failed to change directory to " + cwd) return } const worker = new Worker(workerPath, { env: Object.fromEntries( Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined), ), }) worker.onerror = (e) => { Log.Default.error(e) } const client = Rpc.client(worker) process.on("uncaughtException", (e) => { Log.Default.error(e) }) process.on("unhandledRejection", (e) => { Log.Default.error(e) }) const config = await Config.get() const networkOpts = resolveNetworkOptions(args, config) const server = await client.call("server", networkOpts) const prompt = await iife(async () => { const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined if (!args.prompt) return piped return piped ? piped + "\n" + args.prompt : args.prompt }) const tuiPromise = tui({ url: server.url, args: { continue: args.continue, sessionID: args.session, agent: args.agent, model: args.model, prompt, }, onExit: async () => { await client.call("shutdown", undefined) }, }) setTimeout(() => { client.call("checkUpgrade", { directory: cwd }).catch(() => {}) }, 1000) await tuiPromise }, })