index.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import "zod-openapi/extend"
  2. import { App } from "./app/app"
  3. import { Server } from "./server/server"
  4. import fs from "fs/promises"
  5. import path from "path"
  6. import { Share } from "./share/share"
  7. import { Global } from "./global"
  8. import yargs from "yargs"
  9. import { hideBin } from "yargs/helpers"
  10. import { RunCommand } from "./cli/cmd/run"
  11. import { GenerateCommand } from "./cli/cmd/generate"
  12. import { ScrapCommand } from "./cli/cmd/scrap"
  13. import { Log } from "./util/log"
  14. import { AuthCommand, AuthLoginCommand } from "./cli/cmd/auth"
  15. import { UpgradeCommand } from "./cli/cmd/upgrade"
  16. import { Provider } from "./provider/provider"
  17. import { UI } from "./cli/ui"
  18. import { Installation } from "./installation"
  19. import { Bus } from "./bus"
  20. import { Config } from "./config/config"
  21. import { NamedError } from "./util/error"
  22. import { FormatError } from "./cli/error"
  23. const cancel = new AbortController()
  24. const cli = yargs(hideBin(process.argv))
  25. .scriptName("opencode")
  26. .version(Installation.VERSION)
  27. .option("print-logs", {
  28. describe: "Print logs to stderr",
  29. type: "boolean",
  30. })
  31. .middleware(async () => {
  32. await Log.init({ print: process.argv.includes("--print-logs") })
  33. Log.Default.info("opencode", {
  34. version: Installation.VERSION,
  35. args: process.argv.slice(2),
  36. })
  37. })
  38. .usage("\n" + UI.logo())
  39. .command({
  40. command: "$0 [project]",
  41. describe: "start opencode TUI",
  42. builder: (yargs) =>
  43. yargs.positional("project", {
  44. type: "string",
  45. describe: "path to start opencode in",
  46. }),
  47. handler: async (args) => {
  48. while (true) {
  49. const cwd = args.project ? path.resolve(args.project) : process.cwd()
  50. process.chdir(cwd)
  51. const result = await App.provide({ cwd }, async (app) => {
  52. const providers = await Provider.list()
  53. if (Object.keys(providers).length === 0) {
  54. return "needs_provider"
  55. }
  56. await Share.init()
  57. const server = Server.listen()
  58. let cmd = ["go", "run", "./main.go"]
  59. let cwd = new URL("../../tui/cmd/opencode", import.meta.url).pathname
  60. if (Bun.embeddedFiles.length > 0) {
  61. const blob = Bun.embeddedFiles[0] as File
  62. const binary = path.join(Global.Path.cache, "tui", blob.name)
  63. const file = Bun.file(binary)
  64. if (!(await file.exists())) {
  65. await Bun.write(file, blob, { mode: 0o755 })
  66. await fs.chmod(binary, 0o755)
  67. }
  68. cwd = process.cwd()
  69. cmd = [binary]
  70. }
  71. const proc = Bun.spawn({
  72. cmd: [...cmd, ...process.argv.slice(2)],
  73. signal: cancel.signal,
  74. cwd,
  75. stdout: "inherit",
  76. stderr: "inherit",
  77. stdin: "inherit",
  78. env: {
  79. ...process.env,
  80. OPENCODE_SERVER: server.url.toString(),
  81. OPENCODE_APP_INFO: JSON.stringify(app),
  82. },
  83. onExit: () => {
  84. server.stop()
  85. },
  86. })
  87. ;(async () => {
  88. if (Installation.VERSION === "dev") return
  89. if (Installation.isSnapshot()) return
  90. const config = await Config.global()
  91. if (config.autoupdate === false) return
  92. const latest = await Installation.latest()
  93. if (Installation.VERSION === latest) return
  94. const method = await Installation.method()
  95. if (method === "unknown") return
  96. await Installation.upgrade(method, latest)
  97. .then(() => {
  98. Bus.publish(Installation.Event.Updated, { version: latest })
  99. })
  100. .catch(() => {})
  101. })()
  102. await proc.exited
  103. server.stop()
  104. return "done"
  105. })
  106. if (result === "done") break
  107. if (result === "needs_provider") {
  108. UI.empty()
  109. UI.println(UI.logo(" "))
  110. UI.empty()
  111. await AuthLoginCommand.handler(args)
  112. }
  113. }
  114. },
  115. })
  116. .command(RunCommand)
  117. .command(GenerateCommand)
  118. .command(ScrapCommand)
  119. .command(AuthCommand)
  120. .command(UpgradeCommand)
  121. .fail((msg) => {
  122. if (
  123. msg.startsWith("Unknown argument") ||
  124. msg.startsWith("Not enough non-option arguments")
  125. ) {
  126. cli.showHelp("log")
  127. }
  128. })
  129. .strict()
  130. try {
  131. await cli.parse()
  132. } catch (e) {
  133. const data: Record<string, any> = {}
  134. if (e instanceof NamedError) {
  135. const obj = e.toObject()
  136. Object.assign(data, {
  137. ...obj.data,
  138. })
  139. }
  140. if (e instanceof Error) {
  141. Object.assign(data, {
  142. name: e.name,
  143. message: e.message,
  144. cause: e.cause?.toString(),
  145. })
  146. }
  147. Log.Default.error("fatal", data)
  148. const formatted = FormatError(e)
  149. if (formatted) UI.error(formatted)
  150. if (!formatted)
  151. UI.error(
  152. "Unexpected error, check log file at " + Log.file() + " for more details",
  153. )
  154. }
  155. cancel.abort()