diff --git a/packages/opencode/src/cli/cmd/opentui/component/prompt.tsx b/packages/opencode/src/cli/cmd/opentui/component/prompt.tsx
index f25b6bbbd..2a558a337 100644
--- a/packages/opencode/src/cli/cmd/opentui/component/prompt.tsx
+++ b/packages/opencode/src/cli/cmd/opentui/component/prompt.tsx
@@ -11,7 +11,6 @@ import { useSync } from "../context/sync"
import { Identifier } from "../../../../id/id"
import { createStore, produce } from "solid-js/store"
import type { FilePart } from "@opencode-ai/sdk"
-import { Instance } from "../../../../project/instance"
import fuzzysort from "fuzzysort"
export type PromptProps = {
@@ -295,7 +294,7 @@ function Autocomplete(props: {
type: "file",
mime: "text/plain",
filename: file,
- url: `file://${Instance.directory}/${file}`,
+ url: `file://${process.cwd()}/${file}`,
source: {
type: "file",
text: {
diff --git a/packages/opencode/src/cli/cmd/opentui/context/sync.tsx b/packages/opencode/src/cli/cmd/opentui/context/sync.tsx
index 9705b6fc4..2c71a52c8 100644
--- a/packages/opencode/src/cli/cmd/opentui/context/sync.tsx
+++ b/packages/opencode/src/cli/cmd/opentui/context/sync.tsx
@@ -1,7 +1,7 @@
import type { Message, Agent, Provider, Session, Part, Config, Todo } from "@opencode-ai/sdk"
import { createStore, produce, reconcile } from "solid-js/store"
import { useSDK } from "./sdk"
-import { createContext, Show, useContext, type ParentProps } from "solid-js"
+import { createContext, onCleanup, Show, useContext, type ParentProps } from "solid-js"
import { Binary } from "../../../../util/binary"
function init() {
diff --git a/packages/opencode/src/cli/cmd/opentui/opentui.tsx b/packages/opencode/src/cli/cmd/opentui/opentui.tsx
index ef3db6881..9155fe664 100644
--- a/packages/opencode/src/cli/cmd/opentui/opentui.tsx
+++ b/packages/opencode/src/cli/cmd/opentui/opentui.tsx
@@ -1,5 +1,5 @@
import { cmd } from "../cmd"
-import { render, useKeyHandler, useRenderer, useTerminalDimensions } from "@opentui/solid"
+import { render, useKeyboard, useKeyHandler, useRenderer, useTerminalDimensions } from "@opentui/solid"
import { TextAttributes } from "@opentui/core"
import { RouteProvider, useRoute } from "./context/route"
import { Home } from "./home"
@@ -8,21 +8,31 @@ import { Theme } from "./context/theme"
import { Installation } from "../../../installation"
import { Global } from "../../../global"
import { DialogProvider, useDialog } from "./ui/dialog"
-import { bootstrap } from "../../bootstrap"
import { SDKProvider } from "./context/sdk"
import { SyncProvider } from "./context/sync"
import { LocalProvider, useLocal } from "./context/local"
import { DialogModel } from "./component/dialog-model"
import { DialogCommand } from "./component/dialog-command"
import { Session } from "./session"
+import { Instance } from "../../../project/instance"
+import { EventLoop } from "../../../util/eventloop"
export const OpentuiCommand = cmd({
command: "opentui",
describe: "print hello",
handler: async () => {
- await bootstrap(process.cwd(), async () => {
- await render(
- () => (
+ await render(
+ () => {
+ const renderer = useRenderer()
+ useKeyboard(async (evt) => {
+ if (evt.name === "c" && evt.ctrl) {
+ await Instance.disposeAll()
+ renderer.destroy()
+ await EventLoop.wait()
+ process.exit(0)
+ }
+ })
+ return (
@@ -34,15 +44,14 @@ export const OpentuiCommand = cmd({
- ),
- {
- targetFps: 60,
- gatherStats: false,
- useAlternateScreen: false,
- },
- )
- console.log("done")
- })
+ )
+ },
+ {
+ targetFps: 60,
+ gatherStats: false,
+ exitOnCtrlC: false,
+ },
+ )
},
})
diff --git a/packages/opencode/src/cli/cmd/opentui/session.tsx b/packages/opencode/src/cli/cmd/opentui/session.tsx
index a7f686864..c2f614ded 100644
--- a/packages/opencode/src/cli/cmd/opentui/session.tsx
+++ b/packages/opencode/src/cli/cmd/opentui/session.tsx
@@ -16,7 +16,6 @@ import type { ReadTool } from "../../../tool/read"
import type { WriteTool } from "../../../tool/write"
import { BashTool } from "../../../tool/bash"
import type { GlobTool } from "../../../tool/glob"
-import { Instance } from "../../../project/instance"
import { TodoWriteTool } from "../../../tool/todo"
import type { GrepTool } from "../../../tool/grep"
import type { ListTool } from "../../../tool/ls"
@@ -71,7 +70,7 @@ export function Session() {
(scroll = r)}
+ ref={(r) => (scroll = r)}
scrollbarOptions={{ visible: false }}
stickyScroll={true}
stickyStart="bottom"
@@ -525,7 +524,7 @@ ToolRegistry.register({
function normalizePath(input: string) {
if (path.isAbsolute(input)) {
- return path.relative(Instance.directory, input) || "."
+ return path.relative(process.cwd(), input) || "."
}
return input
}
diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts
index 01ea87a3c..5a3adbd4d 100644
--- a/packages/opencode/src/project/instance.ts
+++ b/packages/opencode/src/project/instance.ts
@@ -44,4 +44,12 @@ export const Instance = {
async dispose() {
await State.dispose(Instance.directory)
},
+ async disposeAll() {
+ for (const [key, value] of cache) {
+ context.provide(value, async () => {
+ process.stdout.write(`disposing ${key}...`)
+ await Instance.dispose()
+ })
+ }
+ },
}
diff --git a/packages/opencode/src/util/eventloop.ts b/packages/opencode/src/util/eventloop.ts
new file mode 100644
index 000000000..87f6eef41
--- /dev/null
+++ b/packages/opencode/src/util/eventloop.ts
@@ -0,0 +1,20 @@
+import { Log } from "./log"
+
+export namespace EventLoop {
+ export async function wait() {
+ return new Promise((resolve) => {
+ const check = () => {
+ const active = [...(process as any)._getActiveHandles(), ...(process as any)._getActiveRequests()]
+ Log.Default.info("eventloop", {
+ active,
+ })
+ if ((process as any)._getActiveHandles().length === 0 && (process as any)._getActiveRequests().length === 0) {
+ resolve()
+ } else {
+ setImmediate(check)
+ }
+ }
+ check()
+ })
+ }
+}
diff --git a/packages/opencode/src/util/signal.ts b/packages/opencode/src/util/signal.ts
new file mode 100644
index 000000000..bc633ecc6
--- /dev/null
+++ b/packages/opencode/src/util/signal.ts
@@ -0,0 +1,12 @@
+export function signal() {
+ let resolve: any
+ const promise = new Promise((r) => (resolve = r))
+ return {
+ trigger() {
+ return resolve()
+ },
+ wait() {
+ return promise
+ },
+ }
+}