thread.ts 2.9 KB

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