thread.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { cmd } from "@/cli/cmd/cmd"
  2. import { tui } from "./app"
  3. import { Rpc } from "@/util/rpc"
  4. import { type rpc } from "./worker"
  5. import path from "path"
  6. import { UI } from "@/cli/ui"
  7. import { iife } from "@/util/iife"
  8. import { Log } from "@/util/log"
  9. import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
  10. import { Config } from "@/config/config"
  11. declare global {
  12. const OPENCODE_WORKER_PATH: string
  13. }
  14. export const TuiThreadCommand = cmd({
  15. command: "$0 [project]",
  16. describe: "start opencode tui",
  17. builder: (yargs) =>
  18. withNetworkOptions(yargs)
  19. .positional("project", {
  20. type: "string",
  21. describe: "path to start opencode in",
  22. })
  23. .option("model", {
  24. type: "string",
  25. alias: ["m"],
  26. describe: "model to use in the format of provider/model",
  27. })
  28. .option("continue", {
  29. alias: ["c"],
  30. describe: "continue the last session",
  31. type: "boolean",
  32. })
  33. .option("session", {
  34. alias: ["s"],
  35. type: "string",
  36. describe: "session id to continue",
  37. })
  38. .option("prompt", {
  39. type: "string",
  40. describe: "prompt to use",
  41. })
  42. .option("agent", {
  43. type: "string",
  44. describe: "agent to use",
  45. }),
  46. handler: async (args) => {
  47. // Resolve relative paths against PWD to preserve behavior when using --cwd flag
  48. const baseCwd = process.env.PWD ?? process.cwd()
  49. const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd()
  50. const localWorker = new URL("./worker.ts", import.meta.url)
  51. const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url)
  52. const workerPath = await iife(async () => {
  53. if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH
  54. if (await Bun.file(distWorker).exists()) return distWorker
  55. return localWorker
  56. })
  57. try {
  58. process.chdir(cwd)
  59. } catch (e) {
  60. UI.error("Failed to change directory to " + cwd)
  61. return
  62. }
  63. const worker = new Worker(workerPath, {
  64. env: Object.fromEntries(
  65. Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
  66. ),
  67. })
  68. worker.onerror = (e) => {
  69. Log.Default.error(e)
  70. }
  71. const client = Rpc.client<typeof rpc>(worker)
  72. process.on("uncaughtException", (e) => {
  73. Log.Default.error(e)
  74. })
  75. process.on("unhandledRejection", (e) => {
  76. Log.Default.error(e)
  77. })
  78. const config = await Config.get()
  79. const networkOpts = resolveNetworkOptions(args, config)
  80. const server = await client.call("server", networkOpts)
  81. const prompt = await iife(async () => {
  82. const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined
  83. if (!args.prompt) return piped
  84. return piped ? piped + "\n" + args.prompt : args.prompt
  85. })
  86. const tuiPromise = tui({
  87. url: server.url,
  88. args: {
  89. continue: args.continue,
  90. sessionID: args.session,
  91. agent: args.agent,
  92. model: args.model,
  93. prompt,
  94. },
  95. onExit: async () => {
  96. await client.call("shutdown", undefined)
  97. },
  98. })
  99. setTimeout(() => {
  100. client.call("checkUpgrade", { directory: cwd }).catch(() => {})
  101. }, 1000)
  102. await tuiPromise
  103. },
  104. })