summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-06-10 18:58:47 -0400
committerDax Raad <[email protected]>2025-06-10 18:58:47 -0400
commit28f5cbbfe957bcd7e49dc7e318100388c5b5afcf (patch)
tree06c8b1840a2af2e178eac8da65918ce9f0ea01e1
parentca3c22dc12e7b0f29c0aa9eabe9d67d42c87c521 (diff)
downloadopencode-28f5cbbfe957bcd7e49dc7e318100388c5b5afcf.tar.gz
opencode-28f5cbbfe957bcd7e49dc7e318100388c5b5afcf.zip
Fix shutdown handling, error management, and process lifecycle issues
🤖 Generated with [OpenCode](https://opencode.ai) Co-Authored-By: OpenCode <[email protected]>
-rw-r--r--packages/opencode/src/app/app.ts7
-rw-r--r--packages/opencode/src/cli/cmd/provider.ts10
-rw-r--r--packages/opencode/src/cli/ui.ts8
-rw-r--r--packages/opencode/src/index.ts124
-rw-r--r--packages/opencode/src/lsp/client.ts2
-rw-r--r--packages/opencode/src/lsp/server.ts2
-rw-r--r--packages/opencode/src/util/log.ts2
7 files changed, 89 insertions, 66 deletions
diff --git a/packages/opencode/src/app/app.ts b/packages/opencode/src/app/app.ts
index 9d7920870..7e8afc825 100644
--- a/packages/opencode/src/app/app.ts
+++ b/packages/opencode/src/app/app.ts
@@ -97,7 +97,7 @@ export namespace App {
log.info("registering service", { name: key })
services.set(key, {
state: init(app.info),
- shutdown: shutdown,
+ shutdown,
})
}
return services.get(key)?.state as State
@@ -108,14 +108,15 @@ export namespace App {
return ctx.use().info
}
- export async function provide<T extends (app: Info) => any>(
+ export async function provide<T>(
input: { cwd: string; version: string },
- cb: T,
+ cb: (app: Info) => Promise<T>,
) {
const app = await create(input)
return ctx.provide(app, async () => {
const result = await cb(app.info)
for (const [key, entry] of app.services.entries()) {
+ if (!entry.shutdown) continue
log.info("shutdown", { name: key })
await entry.shutdown?.(await entry.state)
}
diff --git a/packages/opencode/src/cli/cmd/provider.ts b/packages/opencode/src/cli/cmd/provider.ts
index 49b10e85d..23011c9ae 100644
--- a/packages/opencode/src/cli/cmd/provider.ts
+++ b/packages/opencode/src/cli/cmd/provider.ts
@@ -6,6 +6,7 @@ import * as prompts from "@clack/prompts"
import open from "open"
import { VERSION } from "../version"
import { Provider } from "../../provider/provider"
+import { UI } from "../ui"
export const ProviderCommand = cmd({
command: "provider",
@@ -62,7 +63,7 @@ export const ProviderAddCommand = cmd({
},
],
})
- if (prompts.isCancel(provider)) return
+ if (prompts.isCancel(provider)) throw new UI.CancelledError({})
if (provider === "anthropic") {
const method = await prompts.select({
@@ -78,7 +79,7 @@ export const ProviderAddCommand = cmd({
},
],
})
- if (prompts.isCancel(method)) return
+ if (prompts.isCancel(method)) throw new UI.CancelledError({})
if (method === "oauth") {
// some weird bug where program exits without this
@@ -92,7 +93,8 @@ export const ProviderAddCommand = cmd({
message: "Paste the authorization code here: ",
validate: (x) => (x.length > 0 ? undefined : "Required"),
})
- if (prompts.isCancel(code)) return
+ if (prompts.isCancel(code)) throw new UI.CancelledError({})
+
await AuthAnthropic.exchange(code, verifier)
.then(() => {
prompts.log.success("Login successful")
@@ -109,7 +111,7 @@ export const ProviderAddCommand = cmd({
message: "Enter your API key",
validate: (x) => (x.length > 0 ? undefined : "Required"),
})
- if (prompts.isCancel(key)) return
+ if (prompts.isCancel(key)) throw new UI.CancelledError({})
await AuthKeys.set(provider, key)
prompts.outro("Done")
diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts
index ade61fa30..fad8214ba 100644
--- a/packages/opencode/src/cli/ui.ts
+++ b/packages/opencode/src/cli/ui.ts
@@ -1,4 +1,5 @@
-import { VERSION } from "./version"
+import { z } from "zod"
+import { NamedError } from "../util/error"
export namespace UI {
const LOGO = [
@@ -7,6 +8,11 @@ export namespace UI {
`▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀`,
]
+ export const CancelledError = NamedError.create(
+ "UICancelledError",
+ z.object({}),
+ )
+
export const Style = {
TEXT_HIGHLIGHT: "\x1b[96m",
TEXT_HIGHLIGHT_BOLD: "\x1b[96m\x1b[1m",
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index 279251ab9..07ee6633c 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -21,62 +21,76 @@ import { UI } from "./cli/ui"
await Log.init({ print: process.argv.includes("--print-logs") })
-yargs(hideBin(process.argv))
- .scriptName("opencode")
- .version(VERSION)
- .command({
- command: "$0",
- describe: "Start OpenCode TUI",
- builder: (yargs) =>
- yargs.option("print-logs", {
- type: "boolean",
- }),
- handler: async (args) => {
- UI.logo()
- await App.provide({ cwd: process.cwd(), version: VERSION }, async () => {
- const providers = await Provider.list()
- if (Object.keys(providers).length === 0) {
- await ProviderAddCommand.handler(args)
- return
- }
+try {
+ await yargs(hideBin(process.argv))
+ .scriptName("opencode")
+ .version(VERSION)
+ .command({
+ command: "$0",
+ describe: "Start OpenCode TUI",
+ handler: async (args) => {
+ while (true) {
+ const result = await App.provide(
+ { cwd: process.cwd(), version: VERSION },
+ async () => {
+ const providers = await Provider.list()
+ if (Object.keys(providers).length === 0) {
+ return "needs_provider"
+ }
+
+ await Share.init()
+ const server = Server.listen()
- await Share.init()
- const server = Server.listen()
+ let cmd = ["go", "run", "./main.go"]
+ let cwd = new URL("../../tui/cmd/opencode", import.meta.url)
+ .pathname
+ if (Bun.embeddedFiles.length > 0) {
+ const blob = Bun.embeddedFiles[0] as File
+ const binary = path.join(Global.Path.cache, "tui", blob.name)
+ const file = Bun.file(binary)
+ if (!(await file.exists())) {
+ await Bun.write(file, blob, { mode: 0o755 })
+ await fs.chmod(binary, 0o755)
+ }
+ cwd = process.cwd()
+ cmd = [binary]
+ }
+ const proc = Bun.spawn({
+ cmd,
+ cwd,
+ stdout: "inherit",
+ stderr: "inherit",
+ stdin: "inherit",
+ env: {
+ ...process.env,
+ OPENCODE_SERVER: server.url.toString(),
+ },
+ onExit: () => {
+ server.stop()
+ },
+ })
+ await proc.exited
+ await server.stop()
- let cmd = ["go", "run", "./main.go"]
- let cwd = new URL("../../tui/cmd/opencode", import.meta.url).pathname
- if (Bun.embeddedFiles.length > 0) {
- const blob = Bun.embeddedFiles[0] as File
- const binary = path.join(Global.Path.cache, "tui", blob.name)
- const file = Bun.file(binary)
- if (!(await file.exists())) {
- await Bun.write(file, blob, { mode: 0o755 })
- await fs.chmod(binary, 0o755)
+ return "done"
+ },
+ )
+ if (result === "done") break
+ if (result === "needs_provider") {
+ UI.logo()
+ await ProviderAddCommand.handler(args)
}
- cwd = process.cwd()
- cmd = [binary]
}
- const proc = Bun.spawn({
- cmd,
- cwd,
- stdout: "inherit",
- stderr: "inherit",
- stdin: "inherit",
- env: {
- ...process.env,
- OPENCODE_SERVER: server.url.toString(),
- },
- onExit: () => {
- server.stop()
- },
- })
- await proc.exited
- await server.stop()
- })
- },
- })
- .command(RunCommand)
- .command(GenerateCommand)
- .command(ScrapCommand)
- .command(ProviderCommand)
- .parse()
+ },
+ })
+ .command(RunCommand)
+ .command(GenerateCommand)
+ .command(ScrapCommand)
+ .command(ProviderCommand)
+ .fail((msg, err) => {
+ Log.Default.error(msg)
+ })
+ .parse()
+} catch (e) {
+ Log.Default.error(e)
+}
diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts
index d71954332..6ad951fe7 100644
--- a/packages/opencode/src/lsp/client.ts
+++ b/packages/opencode/src/lsp/client.ts
@@ -169,7 +169,7 @@ export namespace LSPClient {
log.info("shutting down")
connection.end()
connection.dispose()
- server.process.kill()
+ server.process.kill("SIGKILL")
},
}
diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts
index a549a0761..8b53d76b2 100644
--- a/packages/opencode/src/lsp/server.ts
+++ b/packages/opencode/src/lsp/server.ts
@@ -32,7 +32,7 @@ export namespace LSPServer {
".cts",
],
async spawn(app) {
- const tsserver = Bun.resolve(
+ const tsserver = await Bun.resolve(
"typescript/lib/tsserver.js",
app.path.cwd,
).catch(() => {})
diff --git a/packages/opencode/src/util/log.ts b/packages/opencode/src/util/log.ts
index 04323240c..07ecd4b63 100644
--- a/packages/opencode/src/util/log.ts
+++ b/packages/opencode/src/util/log.ts
@@ -2,7 +2,7 @@ import path from "path"
import fs from "fs/promises"
import { Global } from "../global"
export namespace Log {
- const Default = create()
+ export const Default = create()
export interface Options {
print: boolean