From a61e597c6ff7c51c62d8a2be3852b1a03598854c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 24 Oct 2025 19:23:06 +0200 Subject: [PATCH 01/14] feat: Port CLI options to opentui version --- packages/opencode/src/cli/cmd/tui/app.tsx | 12 ++- .../src/cli/cmd/tui/context/route.tsx | 25 +++--- packages/opencode/src/cli/cmd/tui/thread.ts | 83 ++++++++++++++----- 3 files changed, 87 insertions(+), 33 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index b861deafba..813ac9bd11 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -1,7 +1,7 @@ import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" import { Clipboard } from "@tui/util/clipboard" import { TextAttributes } from "@opentui/core" -import { RouteProvider, useRoute } from "@tui/context/route" +import { RouteProvider, useRoute, type Route } from "@tui/context/route" import { Switch, Match, createEffect, untrack, ErrorBoundary } from "solid-js" import { Installation } from "@/installation" import { Global } from "@/global" @@ -22,13 +22,19 @@ import { PromptHistoryProvider } from "./component/prompt/history" import { DialogAlert } from "./ui/dialog-alert" import { ExitProvider } from "./context/exit" -export async function tui(input: { url: string; onExit?: () => Promise }) { +export async function tui(input: { url: string; sessionID?: string; onExit?: () => Promise }) { + const routeData: Route | undefined = input.sessionID + ? { + type: "session", + sessionID: input.sessionID, + } + : undefined await render( () => { return ( Something went wrong}> - + diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx index f1408a5dc5..6a2387a59c 100644 --- a/packages/opencode/src/cli/cmd/tui/context/route.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx @@ -1,24 +1,27 @@ import { createStore } from "solid-js/store" import { createSimpleContext } from "./helper" -type Route = +export type Route = | { - type: "home" - } + type: "home" + } | { - type: "session" - sessionID: string - } + type: "session" + sessionID: string + } export const { use: useRoute, provider: RouteProvider } = createSimpleContext({ name: "Route", - init: () => { + init: (props: { data?: Route }) => { const [store, setStore] = createStore( - process.env["OPENCODE_ROUTE"] - ? JSON.parse(process.env["OPENCODE_ROUTE"]) - : { + props.data ?? + ( + process.env["OPENCODE_ROUTE"] + ? JSON.parse(process.env["OPENCODE_ROUTE"]) + : { type: "home", - }, + } + ), ) return { diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index dde93bdc5d..a6706ea57e 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -3,6 +3,10 @@ import { tui } from "./app" import { Rpc } from "@/util/rpc" import { type rpc } from "./worker" import { upgrade } from "@/cli/upgrade" +import { Session } from "@/session" +import { bootstrap } from "@/cli/bootstrap" +import path from "path" +import { UI } from "@/cli/ui" export const TuiThreadCommand = cmd({ command: "$0 [project]", @@ -13,6 +17,16 @@ export const TuiThreadCommand = cmd({ type: "string", describe: "path to start opencode in", }) + .option("continue", { + alias: ["c"], + describe: "continue the last session", + type: "boolean", + }) + .option("session", { + alias: ["s"], + describe: "session id to continue", + type: "string", + }) .option("port", { type: "number", describe: "port to listen on", @@ -25,25 +39,56 @@ export const TuiThreadCommand = cmd({ default: "127.0.0.1", }), handler: async (args) => { - upgrade() - const worker = new Worker("./src/cli/cmd/tui/worker.ts") - worker.onerror = console.error - const client = Rpc.client(worker) - process.on("uncaughtException", (e) => { - console.error(e) - }) - process.on("unhandledRejection", (e) => { - console.error(e) - }) - const server = await client.call("server", { - port: args.port, - hostname: args.hostname, - }) - await tui({ - url: server.url, - onExit: async () => { - await client.call("shutdown", undefined) - }, + const cwd = args.project ? path.resolve(args.project) : process.cwd() + try { + process.chdir(cwd) + } catch (e) { + UI.error("Failed to change directory to " + cwd) + return + } + await bootstrap(cwd, async () => { + upgrade() + + const sessionID = await (async () => { + if (args.continue) { + const it = Session.list() + try { + for await (const s of it) { + if (s.parentID === undefined) { + return s.id + } + } + return + } finally { + await it.return() + } + } + if (args.session) { + return args.session + } + return undefined + })() + + const worker = new Worker("./src/cli/cmd/tui/worker.ts") + worker.onerror = console.error + const client = Rpc.client(worker) + process.on("uncaughtException", (e) => { + console.error(e) + }) + process.on("unhandledRejection", (e) => { + console.error(e) + }) + const server = await client.call("server", { + port: args.port, + hostname: args.hostname, + }) + await tui({ + url: server.url, + sessionID, + onExit: async () => { + await client.call("shutdown", undefined) + }, + }) }) }, }) From 98d6530c092aeeeebf9344b58861aadeb0ae474e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 24 Oct 2025 19:23:36 +0200 Subject: [PATCH 02/14] chore: Mark as completed --- packages/opencode/src/cli/cmd/tui.ts | 76 +++++++++++++++------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts index b936f54e4e..f50122c753 100644 --- a/packages/opencode/src/cli/cmd/tui.ts +++ b/packages/opencode/src/cli/cmd/tui.ts @@ -12,7 +12,6 @@ import { Log } from "../../util/log" import { Ide } from "../../ide" import { Flag } from "../../flag/flag" -import { Session } from "../../session" import { $ } from "bun" import { bootstrap } from "../bootstrap" @@ -40,16 +39,17 @@ export const TuiCommand = cmd({ alias: ["m"], describe: "model to use in the format of provider/model", }) - .option("continue", { - alias: ["c"], - describe: "continue the last session", - type: "boolean", - }) - .option("session", { - alias: ["s"], - describe: "session id to continue", - type: "string", - }) + // Migrated to opentui + // .option("continue", { + // alias: ["c"], + // describe: "continue the last session", + // type: "boolean", + // }) + // .option("session", { + // alias: ["s"], + // describe: "session id to continue", + // type: "string", + // }) .option("prompt", { alias: ["p"], type: "string", @@ -73,32 +73,36 @@ export const TuiCommand = cmd({ handler: async (args) => { while (true) { const cwd = args.project ? path.resolve(args.project) : process.cwd() - try { - process.chdir(cwd) - } catch (e) { - UI.error("Failed to change directory to " + cwd) - return - } + + // MIGRATED TO OPENTUI: + // try { + // process.chdir(cwd) + // } catch (e) { + // UI.error("Failed to change directory to " + cwd) + // return + // } const result = await bootstrap(cwd, async () => { - const sessionID = await (async () => { - if (args.continue) { - const it = Session.list() - try { - for await (const s of it) { - if (s.parentID === undefined) { - return s.id - } - } - return - } finally { - await it.return() - } - } - if (args.session) { - return args.session - } - return undefined - })() + const sessionID = "make-typechecker-happy" + // MIGRATED TO OPENTUI: + // const sessionID = await (async () => { + // if (args.continue) { + // const it = Session.list() + // try { + // for await (const s of it) { + // if (s.parentID === undefined) { + // return s.id + // } + // } + // return + // } finally { + // await it.return() + // } + // } + // if (args.session) { + // return args.session + // } + // return undefined + // })() const providers = await Provider.list() if (Object.keys(providers).length === 0) { return "needs_provider" From 2cae9d1a7f40ec469ee605c7bae6ae5dc0597aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 24 Oct 2025 21:35:39 +0200 Subject: [PATCH 03/14] feat: Create Toast component --- packages/opencode/src/cli/cmd/tui/app.tsx | 17 ++-- .../opencode/src/cli/cmd/tui/routes/home.tsx | 2 + .../src/cli/cmd/tui/routes/session/index.tsx | 1 + .../opencode/src/cli/cmd/tui/ui/toast.tsx | 81 +++++++++++++++++++ 4 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/ui/toast.tsx diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 813ac9bd11..f1294c2dee 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -20,6 +20,7 @@ import { Home } from "@tui/routes/home" import { Session } from "@tui/routes/session" import { PromptHistoryProvider } from "./component/prompt/history" import { DialogAlert } from "./ui/dialog-alert" +import { ToastProvider } from "./ui/toast" import { ExitProvider } from "./context/exit" export async function tui(input: { url: string; sessionID?: string; onExit?: () => Promise }) { @@ -39,13 +40,15 @@ export async function tui(input: { url: string; sessionID?: string; onExit?: () - - - - - - - + + + + + + + + + diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index 78e310e7c2..5ff1348cc9 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -6,6 +6,7 @@ import type { KeybindsConfig } from "@opencode-ai/sdk" import { Logo } from "../component/logo" import { Locale } from "@/util/locale" import { useSync } from "../context/sync" +import { Toast } from "../ui/toast" export function Home() { const sync = useSync() @@ -44,6 +45,7 @@ export function Home() { + ) } diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index b768adde1c..2448abf0c9 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -500,6 +500,7 @@ export function Session() { /> + diff --git a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx new file mode 100644 index 0000000000..9406ed8f0d --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx @@ -0,0 +1,81 @@ +import { createContext, useContext, type ParentProps, Show } from "solid-js" +import { createStore } from "solid-js/store" +import { Theme } from "@tui/context/theme" +import { SplitBorder } from "../component/border" + +export interface ToastOptions { + message: string | null + duration: number + type: "info" | "success" | "warning" | "error" +} + +export function Toast() { + const toast = useToast() + + return ( + + {(current) => ( + + {current().message} + + )} + + ) +} + +function init() { + const [store, setStore] = createStore({ + currentToast: null as ToastOptions | null, + }) + + let timeoutHandle: NodeJS.Timeout | null = null + + return { + show(options: ToastOptions) { + const { duration, ...currentToast } = options + setStore("currentToast", currentToast) + if (timeoutHandle) clearTimeout(timeoutHandle) + timeoutHandle = setTimeout(() => { + setStore("currentToast", null) + }, duration).unref() + }, + get currentToast(): ToastOptions | null { + return store.currentToast + }, + } +} + +export type ToastContext = ReturnType + +const ctx = createContext() + +export function ToastProvider(props: ParentProps) { + const value = init() + return ( + + {props.children} + + ) +} + +export function useToast() { + const value = useContext(ctx) + if (!value) { + throw new Error("useToast must be used within a ToastProvider") + } + return value +} \ No newline at end of file From 20a9ec3589bbe5164f75cf74c2382f7d5f9ad188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 24 Oct 2025 21:37:27 +0200 Subject: [PATCH 04/14] fix: Implement /share copy to clipboard (#3372) --- .../src/cli/cmd/tui/routes/session/index.tsx | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 2448abf0c9..5be666884b 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -48,8 +48,9 @@ import { DialogConfirm } from "@tui/ui/dialog-confirm" import { DialogTimeline } from "./dialog-timeline" import { Sidebar } from "./sidebar" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" - import parsers from "../../../../../../parsers-config.json" +import { Clipboard } from "../../util/clipboard" +import { Toast, useToast } from "../../ui/toast" addDefaultParsers(parsers.parsers) @@ -83,6 +84,8 @@ export function Session() { createEffect(() => sync.session.sync(route.sessionID)) + const toast = useToast() + const sdk = useSDK() let scroll: ScrollBoxRenderable @@ -173,12 +176,20 @@ export function Session() { keybind: "session_share", disabled: !!session()?.share?.url, category: "Session", - onSelect: (dialog) => { - sdk.client.session.share({ - path: { - id: route.sessionID, - }, - }) + onSelect: async (dialog) => { + try { + const response = await sdk.client.session.share({ + path: { + id: route.sessionID, + }, + }) + Clipboard.copy(response.data!.share!.url).catch(() => + toast.show({ message: "Failed to copy URL to clipboard", type: "error", duration: 3000 }) + ) + toast.show({ message: "Share URL copied to clipboard!", type: "success", duration: 3000 }) + } catch { + toast.show({ message: "Failed to share session", type: "error", duration: 3000 }) + } dialog.clear() }, }, From 1a99d3e176b035d41daf0ac104c1940f4cd6a77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 24 Oct 2025 21:48:10 +0200 Subject: [PATCH 05/14] refactor: Replace try-catch with then-catch --- .../src/cli/cmd/tui/routes/session/index.tsx | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 5be666884b..b122595fdb 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -177,19 +177,20 @@ export function Session() { disabled: !!session()?.share?.url, category: "Session", onSelect: async (dialog) => { - try { - const response = await sdk.client.session.share({ - path: { - id: route.sessionID, - }, - }) - Clipboard.copy(response.data!.share!.url).catch(() => - toast.show({ message: "Failed to copy URL to clipboard", type: "error", duration: 3000 }) + await sdk.client.session.share({ + path: { + id: route.sessionID, + }, + }) + .then((res) => + Clipboard.copy(res.data!.share!.url).catch(() => + toast.show({ message: "Failed to copy URL to clipboard", type: "error", duration: 3000 }) + ) ) - toast.show({ message: "Share URL copied to clipboard!", type: "success", duration: 3000 }) - } catch { - toast.show({ message: "Failed to share session", type: "error", duration: 3000 }) - } + .then(() => + toast.show({ message: "Share URL copied to clipboard!", type: "success", duration: 3000 }) + ) + .catch(() => toast.show({ message: "Failed to share session", type: "error", duration: 3000 })) dialog.clear() }, }, From d24ce8d60e719edce7cba0e73bd30961f3e09629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 24 Oct 2025 23:38:20 +0200 Subject: [PATCH 06/14] fix: Handle invalid sessionID errors --- packages/opencode/src/cli/cmd/tui/app.tsx | 29 ++++++++++++++++--- .../src/cli/cmd/tui/context/route.tsx | 18 +++++++----- .../opencode/src/cli/cmd/tui/context/sync.tsx | 2 +- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index f1294c2dee..0a8fa17a40 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -2,12 +2,12 @@ import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentu import { Clipboard } from "@tui/util/clipboard" import { TextAttributes } from "@opentui/core" import { RouteProvider, useRoute, type Route } from "@tui/context/route" -import { Switch, Match, createEffect, untrack, ErrorBoundary } from "solid-js" +import { Switch, Match, createEffect, untrack, ErrorBoundary, createMemo, createSignal } from "solid-js" import { Installation } from "@/installation" import { Global } from "@/global" import { DialogProvider, useDialog } from "@tui/ui/dialog" import { SDKProvider, useSDK } from "@tui/context/sdk" -import { SyncProvider } from "@tui/context/sync" +import { SyncProvider, useSync } from "@tui/context/sync" import { LocalProvider, useLocal } from "@tui/context/local" import { DialogModel } from "@tui/component/dialog-model" import { DialogStatus } from "@tui/component/dialog-status" @@ -20,8 +20,9 @@ import { Home } from "@tui/routes/home" import { Session } from "@tui/routes/session" import { PromptHistoryProvider } from "./component/prompt/history" import { DialogAlert } from "./ui/dialog-alert" -import { ToastProvider } from "./ui/toast" +import { ToastProvider, useToast } from "./ui/toast" import { ExitProvider } from "./context/exit" +import type { SessionRoute } from "./context/route" export async function tui(input: { url: string; sessionID?: string; onExit?: () => Promise }) { const routeData: Route | undefined = input.sessionID @@ -76,6 +77,9 @@ function App() { const local = useLocal() const command = useCommandDialog() const { event } = useSDK() + const sync = useSync() + const toast = useToast() + const [sessionExists, setSessionExists] = createSignal(false) useKeyboard(async (evt) => { if (evt.meta && evt.name === "t") { @@ -89,6 +93,23 @@ function App() { } }) + // Make sure session is valid, otherwise redirect to home + createEffect(async () => { + if (route.data.type === "session") { + const data = route.data as SessionRoute + await sync.session.sync(data.sessionID) + .catch((err) => { + toast.show({ + message: `Failed to load session ${data.sessionID}: ${err.data?.message ?? err.error?.[0]?.message ?? "Unknown error"}`, + type: "error", + duration: 5000 + }) + return route.navigate({ type: "home" }) + }) + setSessionExists(true) + } + }) + createEffect(() => { console.log(JSON.stringify(route.data)) }) @@ -204,7 +225,7 @@ function App() { - + diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx index 6a2387a59c..ef230dc988 100644 --- a/packages/opencode/src/cli/cmd/tui/context/route.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx @@ -1,14 +1,16 @@ import { createStore } from "solid-js/store" import { createSimpleContext } from "./helper" -export type Route = - | { - type: "home" - } - | { - type: "session" - sessionID: string - } +export type HomeRoute = { + type: "home" +} + +export type SessionRoute = { + type: "session" + sessionID: string +} + +export type Route = HomeRoute | SessionRoute export const { use: useRoute, provider: RouteProvider } = createSimpleContext({ name: "Route", diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index e123529c5a..765fb61961 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -246,7 +246,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ }, async sync(sessionID: string) { const [session, messages, todo] = await Promise.all([ - sdk.client.session.get({ path: { id: sessionID } }), + sdk.client.session.get({ path: { id: sessionID }, throwOnError: true }), sdk.client.session.messages({ path: { id: sessionID } }), sdk.client.session.todo({ path: { id: sessionID } }), ]) From f667bc24645587c8a0ba4e819bb31397f17879ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 24 Oct 2025 23:40:35 +0200 Subject: [PATCH 07/14] feat: Standardize toast duration to 5s --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 6 +++--- packages/opencode/src/cli/cmd/tui/ui/toast.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index b122595fdb..2540b2699c 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -184,13 +184,13 @@ export function Session() { }) .then((res) => Clipboard.copy(res.data!.share!.url).catch(() => - toast.show({ message: "Failed to copy URL to clipboard", type: "error", duration: 3000 }) + toast.show({ message: "Failed to copy URL to clipboard", type: "error" }) ) ) .then(() => - toast.show({ message: "Share URL copied to clipboard!", type: "success", duration: 3000 }) + toast.show({ message: "Share URL copied to clipboard!", type: "success" }) ) - .catch(() => toast.show({ message: "Failed to share session", type: "error", duration: 3000 })) + .catch(() => toast.show({ message: "Failed to share session", type: "error" })) dialog.clear() }, }, diff --git a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx index 9406ed8f0d..4d7599189c 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx @@ -5,7 +5,7 @@ import { SplitBorder } from "../component/border" export interface ToastOptions { message: string | null - duration: number + duration?: number type: "info" | "success" | "warning" | "error" } @@ -51,7 +51,7 @@ function init() { if (timeoutHandle) clearTimeout(timeoutHandle) timeoutHandle = setTimeout(() => { setStore("currentToast", null) - }, duration).unref() + }, duration ?? 5000).unref() }, get currentToast(): ToastOptions | null { return store.currentToast From 3ba1c93d46b69d88e936ee29ecf90e26350fbda8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 24 Oct 2025 23:44:15 +0200 Subject: [PATCH 08/14] feat: Change session not found error toast to match dev --- packages/opencode/src/cli/cmd/tui/app.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 0a8fa17a40..8d23ad28c3 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -98,11 +98,10 @@ function App() { if (route.data.type === "session") { const data = route.data as SessionRoute await sync.session.sync(data.sessionID) - .catch((err) => { + .catch(() => { toast.show({ - message: `Failed to load session ${data.sessionID}: ${err.data?.message ?? err.error?.[0]?.message ?? "Unknown error"}`, + message: `Session not found: ${data.sessionID}`, type: "error", - duration: 5000 }) return route.navigate({ type: "home" }) }) From 418098f70d581133de694bd95d1ff0e49b02d405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Fri, 24 Oct 2025 23:47:39 +0200 Subject: [PATCH 09/14] Revert "chore: Mark as completed" This reverts commit 98d6530c092aeeeebf9344b58861aadeb0ae474e. --- packages/opencode/src/cli/cmd/tui.ts | 76 +++++++++++++--------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts index f50122c753..b936f54e4e 100644 --- a/packages/opencode/src/cli/cmd/tui.ts +++ b/packages/opencode/src/cli/cmd/tui.ts @@ -12,6 +12,7 @@ import { Log } from "../../util/log" import { Ide } from "../../ide" import { Flag } from "../../flag/flag" +import { Session } from "../../session" import { $ } from "bun" import { bootstrap } from "../bootstrap" @@ -39,17 +40,16 @@ export const TuiCommand = cmd({ alias: ["m"], describe: "model to use in the format of provider/model", }) - // Migrated to opentui - // .option("continue", { - // alias: ["c"], - // describe: "continue the last session", - // type: "boolean", - // }) - // .option("session", { - // alias: ["s"], - // describe: "session id to continue", - // type: "string", - // }) + .option("continue", { + alias: ["c"], + describe: "continue the last session", + type: "boolean", + }) + .option("session", { + alias: ["s"], + describe: "session id to continue", + type: "string", + }) .option("prompt", { alias: ["p"], type: "string", @@ -73,36 +73,32 @@ export const TuiCommand = cmd({ handler: async (args) => { while (true) { const cwd = args.project ? path.resolve(args.project) : process.cwd() - - // MIGRATED TO OPENTUI: - // try { - // process.chdir(cwd) - // } catch (e) { - // UI.error("Failed to change directory to " + cwd) - // return - // } + try { + process.chdir(cwd) + } catch (e) { + UI.error("Failed to change directory to " + cwd) + return + } const result = await bootstrap(cwd, async () => { - const sessionID = "make-typechecker-happy" - // MIGRATED TO OPENTUI: - // const sessionID = await (async () => { - // if (args.continue) { - // const it = Session.list() - // try { - // for await (const s of it) { - // if (s.parentID === undefined) { - // return s.id - // } - // } - // return - // } finally { - // await it.return() - // } - // } - // if (args.session) { - // return args.session - // } - // return undefined - // })() + const sessionID = await (async () => { + if (args.continue) { + const it = Session.list() + try { + for await (const s of it) { + if (s.parentID === undefined) { + return s.id + } + } + return + } finally { + await it.return() + } + } + if (args.session) { + return args.session + } + return undefined + })() const providers = await Provider.list() if (Object.keys(providers).length === 0) { return "needs_provider" From f7c5832b279a52741609602dc43e303a772fcb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Sat, 25 Oct 2025 12:14:30 +0200 Subject: [PATCH 10/14] refactor: local.tsx: Rename variables for clarity --- .../src/cli/cmd/tui/context/local.tsx | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index 61557c31bb..5345e17566 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -15,7 +15,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const agent = iife(() => { const agents = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent")) - const [store, setStore] = createStore<{ + const [agentStore, setAgentStore] = createStore<{ current: string }>({ current: agents()[0].name, @@ -25,17 +25,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ return agents() }, current() { - return agents().find((x) => x.name === store.current)! + return agents().find((x) => x.name === agentStore.current)! }, set(name: string) { - setStore("current", name) + setAgentStore("current", name) }, move(direction: 1 | -1) { - let next = agents().findIndex((x) => x.name === store.current) + direction + let next = agents().findIndex((x) => x.name === agentStore.current) + direction if (next < 0) next = agents().length - 1 if (next >= agents().length) next = 0 const value = agents()[next] - setStore("current", value.name) + setAgentStore("current", value.name) if (value.model) model.set({ providerID: value.model.providerID, @@ -51,7 +51,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ }) const model = iife(() => { - const [store, setStore] = createStore<{ + const [modelStore, setModelStore] = createStore<{ ready: boolean model: Record< string, @@ -75,23 +75,23 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ file .json() .then((x) => { - setStore("recent", x.recent) + setModelStore("recent", x.recent) }) .catch(() => {}) .finally(() => { - setStore("ready", true) + setModelStore("ready", true) }) createEffect(() => { Bun.write( file, JSON.stringify({ - recent: store.recent, + recent: modelStore.recent, }), ) }) - const fallback = createMemo(() => { + const fallbackModel = createMemo(() => { function isValid(providerID: string, modelID: string) { const provider = sync.data.provider.find((x) => x.id === providerID) if (!provider) return false @@ -110,7 +110,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } } - for (const item of store.recent) { + for (const item of modelStore.recent) { if (isValid(item.providerID, item.modelID)) { return item } @@ -123,21 +123,21 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } }) - const current = createMemo(() => { + const currentModel = createMemo(() => { const a = agent.current() - return store.model[agent.current().name] ?? (a.model ? a.model : fallback()) + return modelStore.model[agent.current().name] ?? (a.model ? a.model : fallbackModel()) }) return { - current, + current: currentModel, get ready() { - return store.ready + return modelStore.ready }, recent() { - return store.recent + return modelStore.recent }, parsed: createMemo(() => { - const value = current() + const value = currentModel() const provider = sync.data.provider.find((x) => x.id === value.providerID)! const model = provider.models[value.modelID] return { @@ -147,11 +147,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ }), set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) { batch(() => { - setStore("model", agent.current().name, model) + setModelStore("model", agent.current().name, model) if (options?.recent) { - const uniq = uniqueBy([model, ...store.recent], (x) => x.providerID + x.modelID) + const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID) if (uniq.length > 5) uniq.pop() - setStore("recent", uniq) + setModelStore("recent", uniq) } }) }, @@ -160,7 +160,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const kv = iife(() => { const [ready, setReady] = createSignal(false) - const [store, setStore] = createStore({ + const [kvStore, setKvStore] = createStore({ openrouter_warning: false, }) const file = Bun.file(path.join(Global.Path.state, "kv.json")) @@ -168,7 +168,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ file .json() .then((x) => { - setStore(x) + setKvStore(x) }) .catch(() => {}) .finally(() => { @@ -177,13 +177,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ return { get data() { - return store + return kvStore }, get ready() { return ready() }, set(key: string, value: any) { - setStore(key as any, value) + setKvStore(key as any, value) Bun.write( file, JSON.stringify({ From af69b1cb58efdb57c3f3411a482ae580b9e03d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Sat, 25 Oct 2025 12:42:23 +0200 Subject: [PATCH 11/14] feat: Add selected model validation --- packages/opencode/src/cli/cmd/tui/app.tsx | 24 +++++----- .../src/cli/cmd/tui/context/local.tsx | 45 ++++++++++++++++--- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 8d23ad28c3..b777d51c95 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -36,12 +36,12 @@ export async function tui(input: { url: string; sessionID?: string; onExit?: () return ( Something went wrong}> - - - - - - + + + + + + @@ -49,12 +49,12 @@ export async function tui(input: { url: string; sessionID?: string; onExit?: () - - - - - - + + + + + + ) diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index 5345e17566..1f144754a8 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -7,11 +7,14 @@ import path from "path" import { Global } from "@/global" import { iife } from "@/util/iife" import { createSimpleContext } from "./helper" +import { useToast } from "../ui/toast" +import type { Provider } from "@opencode-ai/sdk" export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ name: "Local", init: () => { const sync = useSync() + const toast = useToast() const agent = iife(() => { const agents = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent")) @@ -36,11 +39,15 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ if (next >= agents().length) next = 0 const value = agents()[next] setAgentStore("current", value.name) - if (value.model) - model.set({ - providerID: value.model.providerID, - modelID: value.model.modelID, - }) + if (value.model) { + if (isModelValid(sync.data.provider, value.model)) + model.set({ + providerID: value.model.providerID, + modelID: value.model.modelID, + }) + else + toast.show({ message: `Agent's configured model ${value.model.providerID}/${value.model.modelID} is not valid`, type: "warning", duration: 2000 }) + } }, color(name: string) { const index = agents().findIndex((x) => x.name === name) @@ -77,7 +84,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ .then((x) => { setModelStore("recent", x.recent) }) - .catch(() => {}) + .catch(() => { }) .finally(() => { setModelStore("ready", true) }) @@ -125,7 +132,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const currentModel = createMemo(() => { const a = agent.current() - return modelStore.model[agent.current().name] ?? (a.model ? a.model : fallbackModel()) + return getFirstValidModel( + sync.data.provider, + () => modelStore.model[a.name], + () => a.model, + fallbackModel, + )! }) return { @@ -147,6 +159,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ }), set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) { batch(() => { + if (!isModelValid(sync.data.provider, model)) { + toast.show({ message: `Model ${model.providerID}/${model.modelID} is not valid`, type: "warning", duration: 2000 }) + return + } + setModelStore("model", agent.current().name, model) if (options?.recent) { const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID) @@ -205,3 +222,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ return result }, }) + +export function isModelValid(providers: Provider[], model: { providerID: string, modelID: string }) { + const provider = providers.find((x) => x.id === model.providerID) + return !!provider?.models[model.modelID] +} + +export function getFirstValidModel(providers: Provider[], ...modelFns: (() => { providerID: string, modelID: string } | undefined)[]) { + for (const modelFn of modelFns) { + const model = modelFn() + if (!model) continue + if (isModelValid(providers, model)) + return model + } +} \ No newline at end of file From 35c2bc05862e1923d2e589736d7b54515083966d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Sat, 25 Oct 2025 13:01:09 +0200 Subject: [PATCH 12/14] feat: Implement --model option --- packages/opencode/src/cli/cmd/tui/app.tsx | 4 +-- .../src/cli/cmd/tui/context/local.tsx | 32 ++++++++++++++++--- packages/opencode/src/cli/cmd/tui/thread.ts | 6 ++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index b777d51c95..5939a2badc 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -24,7 +24,7 @@ import { ToastProvider, useToast } from "./ui/toast" import { ExitProvider } from "./context/exit" import type { SessionRoute } from "./context/route" -export async function tui(input: { url: string; sessionID?: string; onExit?: () => Promise }) { +export async function tui(input: { url: string; sessionID?: string; model?: string; onExit?: () => Promise }) { const routeData: Route | undefined = input.sessionID ? { type: "session", @@ -40,7 +40,7 @@ export async function tui(input: { url: string; sessionID?: string; onExit?: () - + diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index 1f144754a8..fd49860cd0 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -1,5 +1,5 @@ import { createStore } from "solid-js/store" -import { batch, createEffect, createMemo, createSignal } from "solid-js" +import { batch, createEffect, createMemo, createSignal, onMount } from "solid-js" import { useSync } from "@tui/context/sync" import { Theme } from "@tui/context/theme" import { uniqueBy } from "remeda" @@ -12,10 +12,24 @@ import type { Provider } from "@opencode-ai/sdk" export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ name: "Local", - init: () => { + init: (props: { initialModel?: string }) => { const sync = useSync() const toast = useToast() + // Set initial model if provided + onMount(() => { + if (props.initialModel) { + const [providerID, modelID] = props.initialModel.split("/") + if (!providerID || !modelID) + return toast.show({ + type: "warning", + message: `Invalid model format: ${props.initialModel}`, + duration: 2000, + }) + model.set({ providerID, modelID }, { recent: true }) + } + }) + const agent = iife(() => { const agents = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent")) const [agentStore, setAgentStore] = createStore<{ @@ -46,7 +60,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ modelID: value.model.modelID, }) else - toast.show({ message: `Agent's configured model ${value.model.providerID}/${value.model.modelID} is not valid`, type: "warning", duration: 2000 }) + toast.show({ + type: "warning", + message: `Agent's configured model ${value.model.providerID}/${value.model.modelID} is not valid`, + duration: 2000, + }) } }, color(name: string) { @@ -160,7 +178,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) { batch(() => { if (!isModelValid(sync.data.provider, model)) { - toast.show({ message: `Model ${model.providerID}/${model.modelID} is not valid`, type: "warning", duration: 2000 }) + toast.show({ + message: `Model ${model.providerID}/${model.modelID} is not valid`, + type: "warning", + duration: 2000, + }) return } @@ -233,6 +255,6 @@ export function getFirstValidModel(providers: Provider[], ...modelFns: (() => { const model = modelFn() if (!model) continue if (isModelValid(providers, model)) - return model + return model } } \ No newline at end of file diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index a6706ea57e..ec1f578166 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -17,6 +17,11 @@ export const TuiThreadCommand = cmd({ type: "string", describe: "path to start opencode in", }) + .option("model", { + type: "string", + alias: ["m"], + describe: "model to use in the format of provider/model", + }) .option("continue", { alias: ["c"], describe: "continue the last session", @@ -85,6 +90,7 @@ export const TuiThreadCommand = cmd({ await tui({ url: server.url, sessionID, + model: args.model, onExit: async () => { await client.call("shutdown", undefined) }, From d62ea3d883aae8abe8c45bd57252e4e6c351312e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Sat, 25 Oct 2025 13:12:02 +0200 Subject: [PATCH 13/14] feat: Improve warning toast --- .../src/cli/cmd/tui/context/local.tsx | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index fd49860cd0..e7fbcba3e5 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -48,24 +48,26 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ setAgentStore("current", name) }, move(direction: 1 | -1) { - let next = agents().findIndex((x) => x.name === agentStore.current) + direction - if (next < 0) next = agents().length - 1 - if (next >= agents().length) next = 0 - const value = agents()[next] - setAgentStore("current", value.name) - if (value.model) { - if (isModelValid(sync.data.provider, value.model)) - model.set({ - providerID: value.model.providerID, - modelID: value.model.modelID, - }) - else - toast.show({ - type: "warning", - message: `Agent's configured model ${value.model.providerID}/${value.model.modelID} is not valid`, - duration: 2000, - }) - } + batch(() => { + let next = agents().findIndex((x) => x.name === agentStore.current) + direction + if (next < 0) next = agents().length - 1 + if (next >= agents().length) next = 0 + const value = agents()[next] + setAgentStore("current", value.name) + if (value.model) { + if (isModelValid(sync.data.provider, value.model)) + model.set({ + providerID: value.model.providerID, + modelID: value.model.modelID, + }) + else + toast.show({ + type: "warning", + message: `Agent ${value.name}'s configured model ${value.model.providerID}/${value.model.modelID} is not valid`, + duration: 2000, + }) + } + }) }, color(name: string) { const index = agents().findIndex((x) => x.name === name) From 1ba5a2ece1e2723d049cd49b9fe002c6bb653563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haris=20Gu=C5=A1i=C4=87?= Date: Sat, 25 Oct 2025 13:21:23 +0200 Subject: [PATCH 14/14] feat: Implement --agent option --- packages/opencode/src/cli/cmd/tui/app.tsx | 4 +- .../src/cli/cmd/tui/context/local.tsx | 37 ++++++++++++------- packages/opencode/src/cli/cmd/tui/thread.ts | 5 +++ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 5939a2badc..4db4a4aa2c 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -24,7 +24,7 @@ import { ToastProvider, useToast } from "./ui/toast" import { ExitProvider } from "./context/exit" import type { SessionRoute } from "./context/route" -export async function tui(input: { url: string; sessionID?: string; model?: string; onExit?: () => Promise }) { +export async function tui(input: { url: string; sessionID?: string; model?: string; agent?: string; onExit?: () => Promise }) { const routeData: Route | undefined = input.sessionID ? { type: "session", @@ -40,7 +40,7 @@ export async function tui(input: { url: string; sessionID?: string; model?: stri - + diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index e7fbcba3e5..4d78fd7584 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -12,22 +12,27 @@ import type { Provider } from "@opencode-ai/sdk" export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ name: "Local", - init: (props: { initialModel?: string }) => { + init: (props: { initialModel?: string; initialAgent?: string }) => { const sync = useSync() const toast = useToast() // Set initial model if provided onMount(() => { - if (props.initialModel) { - const [providerID, modelID] = props.initialModel.split("/") - if (!providerID || !modelID) - return toast.show({ - type: "warning", - message: `Invalid model format: ${props.initialModel}`, - duration: 2000, - }) - model.set({ providerID, modelID }, { recent: true }) - } + batch(() => { + if (props.initialAgent) { + agent.set(props.initialAgent) + } + if (props.initialModel) { + const [providerID, modelID] = props.initialModel.split("/") + if (!providerID || !modelID) + return toast.show({ + type: "warning", + message: `Invalid model format: ${props.initialModel}`, + duration: 3000, + }) + model.set({ providerID, modelID }, { recent: true }) + } + }) }) const agent = iife(() => { @@ -45,6 +50,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ return agents().find((x) => x.name === agentStore.current)! }, set(name: string) { + if (!agents().some((x) => x.name === name)) + return toast.show({ + type: "warning", + message: `Agent not found: ${name}`, + duration: 3000, + }) setAgentStore("current", name) }, move(direction: 1 | -1) { @@ -64,7 +75,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ toast.show({ type: "warning", message: `Agent ${value.name}'s configured model ${value.model.providerID}/${value.model.modelID} is not valid`, - duration: 2000, + duration: 3000, }) } }) @@ -183,7 +194,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ toast.show({ message: `Model ${model.providerID}/${model.modelID} is not valid`, type: "warning", - duration: 2000, + duration: 3000, }) return } diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index ec1f578166..cadeac17e1 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -32,6 +32,10 @@ export const TuiThreadCommand = cmd({ describe: "session id to continue", type: "string", }) + .option("agent", { + type: "string", + describe: "agent to use", + }) .option("port", { type: "number", describe: "port to listen on", @@ -91,6 +95,7 @@ export const TuiThreadCommand = cmd({ url: server.url, sessionID, model: args.model, + agent: args.agent, onExit: async () => { await client.call("shutdown", undefined) },