diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 31897111..368ab0f6 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -61,7 +61,7 @@ import { DialogSelect } from "./ui/dialog-select" import { Provider } from "@/provider" import { ArgsProvider, useArgs, type Args } from "./context/args" import open from "open" -import { Process } from "@/util" +import { Process, Log } from "@/util" import { PromptRefProvider, usePromptRef } from "./context/prompt" import { TuiConfigProvider, useTuiConfig } from "./context/tui-config" import { TuiConfig } from "@/cli/cmd/tui/config/tui" @@ -70,6 +70,8 @@ import { FormatError, FormatUnknownError } from "@/cli/error" import { isPlainTerminal } from "./util/terminal" import type { EventSource } from "./context/sdk" + +const log = Log.create({ service: "tui" }) import { DialogVariant } from "./component/dialog-variant" function rendererConfig(_config: TuiConfig.Info, plainTerminal: boolean): CliRendererConfig { @@ -99,7 +101,7 @@ function rendererConfig(_config: TuiConfig.Info, plainTerminal: boolean): CliRen keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }], onCopySelection: (text) => { Clipboard.copy(text).catch((error) => { - console.error(`Failed to copy console selection to clipboard: ${error}`) + log.error("Failed to copy console selection to clipboard", { error }) }) }, }, @@ -272,7 +274,7 @@ function App(props: { onSnapshot?: () => Promise }) { config: tuiConfig, }) .catch((error) => { - console.error("Failed to load TUI plugins", error) + log.error("Failed to load TUI plugins", { error }) }) .finally(() => { setReady(true) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-image-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-image-list.tsx index b721b187..72d50603 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-image-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-image-list.tsx @@ -15,8 +15,12 @@ const IMAGE_EXT = new Set([".png", ".jpg", ".jpeg"]) const NONE_VALUE = "__mimocode_image_none__" const IMPORT_VALUE = "__mimocode_image_import__" +const log = Log.create({ service: "dialog-image-list" }) + async function listBackgrounds() { - await fs.mkdir(BG_DIR, { recursive: true }).catch(() => {}) + await fs.mkdir(BG_DIR, { recursive: true }).catch((error) => { + log.error("Failed to create backgrounds directory", { dir: BG_DIR, error }) + }) const items = await fs.readdir(BG_DIR).catch(() => [] as string[]) return items .filter((f) => IMAGE_EXT.has(path.extname(f).toLowerCase())) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx index e3e80c0f..129e4f23 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx @@ -7,6 +7,9 @@ import { useTheme } from "../context/theme" import { Keybind } from "@/util" import { TextAttributes } from "@opentui/core" import { useSDK } from "@tui/context/sdk" +import { Log } from "@/util" + +const log = Log.create({ service: "dialog-mcp" }) function Status(props: { enabled: boolean; loading: boolean }) { const { theme } = useTheme() @@ -61,10 +64,10 @@ export function DialogMcp() { if (status.data) { sync.set("mcp", status.data) } else { - console.error("Failed to refresh MCP status: no data returned") + log.error("Failed to refresh MCP status: no data returned") } } catch (error) { - console.error("Failed to toggle MCP:", error) + log.error("Failed to toggle MCP", { error }) } finally { setLoading(null) } diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-worktree.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-worktree.tsx index 8ae41e5d..3b4e41ad 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-worktree.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-worktree.tsx @@ -5,6 +5,9 @@ import { useSDK } from "../context/sdk" import { useSync } from "@tui/context/sync" import { useRoute } from "@tui/context/route" import { useToast } from "../ui/toast" +import { Log } from "@/util" + +const log = Log.create({ service: "dialog-worktree" }) import path from "path" const CREATE_SENTINEL = "__create_worktree__" @@ -53,7 +56,9 @@ export function DialogWorktree() { async function switchTo(directory: string) { setBusy("Switching to worktree...") - await sdk.client.instance.dispose().catch(() => {}) + await sdk.client.instance.dispose().catch((error) => { + log.error("Failed to dispose instance during worktree switch", { error }) + }) sdk.switchDirectory(directory) await sync.bootstrap() route.navigate({ type: "home" }) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index b1616b95..833c8534 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -3,7 +3,7 @@ import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, S import "opentui-spinner/solid" import path from "path" import { fileURLToPath } from "url" -import { Filesystem } from "@/util" +import { Filesystem, Log } from "@/util" import { useLocal } from "@tui/context/local" import { tint, useTheme } from "@tui/context/theme" import { EmptyBorder, SplitBorder } from "@tui/component/border" @@ -46,6 +46,8 @@ import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable" import { DialogAgreement, FREE_AGREEMENT_KEY, FREE_MODEL_IDS } from "../dialog-agreement" import { useArgs } from "@tui/context/args" +const log = Log.create({ service: "prompt" }) + export type PromptProps = { sessionID?: string workspaceID?: string @@ -1063,7 +1065,7 @@ export function Prompt(props: PromptProps) { const res = await sdk.client.session.create({ workspace: props.workspaceID }) if (res.error) { - console.log("Creating a session failed:", res.error) + log.error("Creating a session failed", { error: res.error }) toast.show({ message: "Creating a session failed. Open console for more details.", diff --git a/packages/opencode/src/cli/cmd/tui/context/kv.tsx b/packages/opencode/src/cli/cmd/tui/context/kv.tsx index 17cc0764..04729a26 100644 --- a/packages/opencode/src/cli/cmd/tui/context/kv.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/kv.tsx @@ -1,5 +1,5 @@ import { Global } from "@/global" -import { Filesystem } from "@/util" +import { Filesystem, Log } from "@/util" import { Flock } from "@mimo-ai/shared/util/flock" import { rename, rm } from "fs/promises" import { createSignal, type Setter } from "solid-js" @@ -10,6 +10,7 @@ import path from "path" export const { use: useKV, provider: KVProvider } = createSimpleContext({ name: "KV", init: () => { + const log = Log.create({ service: "kv" }) const [ready, setReady] = createSignal(false) const [store, setStore] = createStore>() const filePath = path.join(Global.Path.state, "kv.json") @@ -34,7 +35,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({ setStore(x) }) .catch((error) => { - console.error("Failed to read KV state", { filePath, error }) + log.error("Failed to read KV state", { filePath, error }) }) .finally(() => { setReady(true) @@ -67,7 +68,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({ write = write .then(() => Flock.withLock(lock, () => writeSnapshot(snapshot))) .catch((error) => { - console.error("Failed to write KV state", { filePath, error }) + log.error("Failed to write KV state", { filePath, error }) }) }, delete(key: string) { @@ -77,7 +78,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({ write = write .then(() => Flock.withLock(lock, () => writeSnapshot(snapshot))) .catch((error) => { - console.error("Failed to write KV state", { filePath, error }) + log.error("Failed to write KV state", { filePath, error }) }) }, } diff --git a/packages/opencode/src/cli/cmd/tui/plugin/slots.tsx b/packages/opencode/src/cli/cmd/tui/plugin/slots.tsx index 2c0159f8..07cb0642 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/slots.tsx +++ b/packages/opencode/src/cli/cmd/tui/plugin/slots.tsx @@ -1,6 +1,9 @@ import type { TuiPluginApi, TuiSlotContext, TuiSlotMap, TuiSlotProps } from "@mimo-ai/plugin/tui" import { createSlot, createSolidSlotRegistry, type JSX, type SolidPlugin } from "@opentui/solid" import { isRecord } from "@/util/record" +import { Log } from "@/util" + +const log = Log.create({ service: "tui.slot" }) type RuntimeSlotMap = TuiSlotMap> @@ -38,7 +41,7 @@ export function setupSlots(api: HostPluginApi): HostSlots { }, { onPluginError(event) { - console.error("[tui.slot] plugin error", { + log.error("plugin error", { plugin: event.pluginId, slot: event.slot, phase: event.phase, diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index f8fe065a..9f765894 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -25,6 +25,8 @@ declare global { type RpcClient = ReturnType> +const log = Log.create({ service: "tui-thread" }) + function createWorkerFetch(client: RpcClient): typeof fetch { const fn = async (input: RequestInfo | URL, init?: RequestInit): Promise => { const request = new Request(input, init) @@ -286,7 +288,9 @@ export const TuiThreadCommand = cmd({ } setTimeout(() => { - client.call("checkUpgrade", { directory: cwd }).catch(() => {}) + client.call("checkUpgrade", { directory: cwd }).catch((error) => { + log.error("Upgrade check failed", { error }) + }) }, 1000).unref?.() try { diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts index 8c535833..d0f3a72f 100644 --- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts +++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts @@ -5,6 +5,9 @@ import path from "path" import fs from "fs/promises" import * as Filesystem from "../../../../util/filesystem" import * as Process from "../../../../util/process" +import { Log } from "../../../../util/log" + +const log = Log.create({ service: "clipboard" }) // Lazy load which and clipboardy to avoid expensive execa/which/isexe chain at startup const getWhich = lazy(async () => { @@ -115,7 +118,7 @@ const getCopyMethod = lazy(async () => { const which = await getWhich() if (os === "darwin" && which("osascript")) { - console.log("clipboard: using osascript") + log.debug("using osascript") return async (text: string) => { const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"') await Process.run(["osascript", "-e", `set the clipboard to "${escaped}"`], { nothrow: true }) @@ -124,7 +127,7 @@ const getCopyMethod = lazy(async () => { if (os === "linux") { if (process.env["WAYLAND_DISPLAY"] && which("wl-copy")) { - console.log("clipboard: using wl-copy") + log.debug("using wl-copy") return async (text: string) => { const proc = Process.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" }) if (!proc.stdin) return @@ -134,7 +137,7 @@ const getCopyMethod = lazy(async () => { } } if (which("xclip")) { - console.log("clipboard: using xclip") + log.debug("using xclip") return async (text: string) => { const proc = Process.spawn(["xclip", "-selection", "clipboard"], { stdin: "pipe", @@ -148,7 +151,7 @@ const getCopyMethod = lazy(async () => { } } if (which("xsel")) { - console.log("clipboard: using xsel") + log.debug("using xsel") return async (text: string) => { const proc = Process.spawn(["xsel", "--clipboard", "--input"], { stdin: "pipe", @@ -164,7 +167,7 @@ const getCopyMethod = lazy(async () => { } if (os === "win32") { - console.log("clipboard: using powershell") + log.debug("using powershell") return async (text: string) => { // Pipe via stdin to avoid PowerShell string interpolation ($env:FOO, $(), etc.) const proc = Process.spawn( @@ -189,7 +192,7 @@ const getCopyMethod = lazy(async () => { } } - console.log("clipboard: no native support") + log.debug("no native support") return async (text: string) => { const clipboardy = await getClipboardy() await clipboardy.write(text).catch(() => {})