diff --git a/packages/cli/src/mini/runtime.boot.ts b/packages/cli/src/mini/runtime.boot.ts index b5fca98dc0..5739bc7e9a 100644 --- a/packages/cli/src/mini/runtime.boot.ts +++ b/packages/cli/src/mini/runtime.boot.ts @@ -6,7 +6,7 @@ // history ring. All are async because they read config or hit the SDK, but // none block each other. import { Context, Effect, Layer } from "effect" -import { resolve } from "@opencode-ai/tui/config" +import { resolve } from "@opencode-ai/tui/config/v1" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { makeGlobalNode } from "@opencode-ai/core/effect/app-node" import { makeRuntime } from "@opencode-ai/core/effect/runtime" diff --git a/packages/cli/src/mini/types.ts b/packages/cli/src/mini/types.ts index 0e3b4bea3b..caa90fb512 100644 --- a/packages/cli/src/mini/types.ts +++ b/packages/cli/src/mini/types.ts @@ -13,7 +13,7 @@ // → OpenTUI split-footer renderer writes to terminal import type { OpenCodeClient, ReferenceListOutput } from "@opencode-ai/client/promise" import type { FilePart, PermissionRequest, QuestionRequest, ToolPart } from "@opencode-ai/sdk/v2" -import type { TuiConfig } from "@opencode-ai/tui/config" +import type { TuiConfig } from "@opencode-ai/tui/config/v1" export type RunFilePart = { type: "file" diff --git a/packages/cli/src/tui-config.ts b/packages/cli/src/tui-config.ts index 48c43bf1d3..1b838917bb 100644 --- a/packages/cli/src/tui-config.ts +++ b/packages/cli/src/tui-config.ts @@ -1,7 +1,7 @@ export * as TuiConfig from "./tui-config" import { Global } from "@opencode-ai/core/global" -import { TuiConfig } from "@opencode-ai/tui/config" +import { TuiConfig } from "@opencode-ai/tui/config/v1" import { Effect, FileSystem, Option, Schema } from "effect" import { parse, type ParseError } from "jsonc-parser" import path from "path" diff --git a/packages/cli/test/footer-keymap.test.tsx b/packages/cli/test/footer-keymap.test.tsx index d1f7fa45b3..1aa0e3270e 100644 --- a/packages/cli/test/footer-keymap.test.tsx +++ b/packages/cli/test/footer-keymap.test.tsx @@ -2,7 +2,7 @@ import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui" import { testRender, useRenderer } from "@opentui/solid" import { OpencodeKeymapProvider, registerOpencodeKeymap } from "@opencode-ai/tui/keymap" -import { resolve } from "@opencode-ai/tui/config" +import { resolve } from "@opencode-ai/tui/config/v1" import { expect, test } from "bun:test" import { createComponent, createSignal } from "solid-js" import { RunFooterView } from "../src/mini/footer.view" diff --git a/packages/opencode/script/schema.ts b/packages/opencode/script/schema.ts index 0aa4e2068e..769d5c6c59 100755 --- a/packages/opencode/script/schema.ts +++ b/packages/opencode/script/schema.ts @@ -2,7 +2,7 @@ import { Config } from "@/config/config" import { ConfigV1 } from "@opencode-ai/core/v1/config/config" -import { TuiConfig } from "@opencode-ai/tui/config" +import { TuiConfig } from "@opencode-ai/tui/config/v1" import { Schema } from "effect" type JsonSchema = Record diff --git a/packages/opencode/src/config/tui-host-attention.ts b/packages/opencode/src/config/tui-host-attention.ts index d74ce888b9..9ecb59e66a 100644 --- a/packages/opencode/src/config/tui-host-attention.ts +++ b/packages/opencode/src/config/tui-host-attention.ts @@ -1,4 +1,4 @@ -import { TuiConfig } from "@opencode-ai/tui/config" +import { TuiConfig } from "@opencode-ai/tui/config/v1" import { isRecord } from "@opencode-ai/tui/util/record" import { Filesystem } from "@/util/filesystem" import { Schema } from "effect" diff --git a/packages/opencode/src/config/tui-migrate.ts b/packages/opencode/src/config/tui-migrate.ts index 6ca254311e..63dd36ec62 100644 --- a/packages/opencode/src/config/tui-migrate.ts +++ b/packages/opencode/src/config/tui-migrate.ts @@ -2,7 +2,7 @@ import path from "path" import { type ParseError as JsoncParseError, applyEdits, modify, parse as parseJsonc } from "jsonc-parser" import { unique } from "remeda" import { Option, Schema } from "effect" -import { TuiConfig } from "@opencode-ai/tui/config" +import { TuiConfig } from "@opencode-ai/tui/config/v1" import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util/filesystem" diff --git a/packages/opencode/src/config/tui.ts b/packages/opencode/src/config/tui.ts index 8f503751f3..2c69c91bb8 100644 --- a/packages/opencode/src/config/tui.ts +++ b/packages/opencode/src/config/tui.ts @@ -15,14 +15,14 @@ import { Global } from "@opencode-ai/core/global" import { FSUtil } from "@opencode-ai/core/fs-util" import { CurrentWorkingDirectory } from "./tui-cwd" import { ConfigPlugin } from "@/config/plugin" -import { TuiKeybind } from "@opencode-ai/tui/config/keybind" +import { TuiKeybind } from "@opencode-ai/tui/config/v1/keybind" import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version" import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { Filesystem } from "@/util/filesystem" import { ConfigVariable } from "@/config/variable" import { Npm } from "@opencode-ai/core/npm" import { FormatError, FormatUnknownError } from "@/cli/error" -import { TuiConfig } from "@opencode-ai/tui/config" +import { TuiConfig } from "@opencode-ai/tui/config/v1" export const Info = TuiConfig.Info export type Info = TuiConfig.Info diff --git a/packages/opencode/test/cli/cmd/tui/attention.test.ts b/packages/opencode/test/cli/cmd/tui/attention.test.ts index c8644385ab..cdad38505b 100644 --- a/packages/opencode/test/cli/cmd/tui/attention.test.ts +++ b/packages/opencode/test/cli/cmd/tui/attention.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test" import type { AudioPlayOptions, AudioSound } from "@opentui/core" import { createTuiAttention } from "@opencode-ai/tui/attention" -import type { TuiConfig } from "@opencode-ai/tui/config" +import type { TuiConfig } from "@opencode-ai/tui/config/v1" type FocusEvent = "focus" | "blur" diff --git a/packages/opencode/test/cli/run/runtime.boot.test.ts b/packages/opencode/test/cli/run/runtime.boot.test.ts index f642c47c08..d1eb963b42 100644 --- a/packages/opencode/test/cli/run/runtime.boot.test.ts +++ b/packages/opencode/test/cli/run/runtime.boot.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test" import { OpenCode } from "@opencode-ai/client/promise" -import type { Resolved } from "@opencode-ai/tui/config" +import type { Resolved } from "@opencode-ai/tui/config/v1" import { resolveDiffStyle, resolveModelInfo, resolveRunTuiConfig } from "@opencode-ai/cli/mini/runtime.boot" import { createTuiResolvedConfig } from "../../fixture/tui-runtime" diff --git a/packages/opencode/test/fixture/tui-runtime.ts b/packages/opencode/test/fixture/tui-runtime.ts index 4ff9bbb943..eb8181070f 100644 --- a/packages/opencode/test/fixture/tui-runtime.ts +++ b/packages/opencode/test/fixture/tui-runtime.ts @@ -1,8 +1,8 @@ import { spyOn } from "bun:test" import path from "path" -import { resolve, type Info, type Resolved } from "@opencode-ai/tui/config" +import { resolve, type Info, type Resolved } from "@opencode-ai/tui/config/v1" import { TuiConfig } from "../../src/config/tui" -import { TuiKeybind } from "@opencode-ai/tui/config/keybind" +import { TuiKeybind } from "@opencode-ai/tui/config/v1/keybind" type PluginSpec = string | [string, Record] type PluginOrigin = { diff --git a/packages/tui/package.json b/packages/tui/package.json index 40f1fab7ce..9c16e0a731 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -12,7 +12,10 @@ "exports": { ".": "./src/index.tsx", "./builtins": "./src/feature-plugins/builtins.ts", - "./config": "./src/config/index.tsx", + "./config/v1": "./src/config/v1/index.tsx", + "./config/v1/keybind": "./src/config/v1/keybind.ts", + "./config/v2": "./src/config/v2/index.ts", + "./config/v2/keybind": "./src/config/v2/keybind.ts", "./context/args": "./src/context/args.tsx", "./context/epilogue": "./src/context/epilogue.tsx", "./context/exit": "./src/context/exit.tsx", @@ -30,7 +33,6 @@ "./editor-zed": "./src/editor-zed.ts", "./runtime": "./src/runtime.tsx", "./terminal-win32": "./src/terminal-win32.ts", - "./config/keybind": "./src/config/keybind.ts", "./keymap": "./src/keymap.tsx", "./prompt/content": "./src/prompt/content.ts", "./prompt/display": "./src/prompt/display.ts", diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index bd7d33c2c3..edd96f7a9b 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -68,7 +68,7 @@ import * as Model from "./util/model" import { ArgsProvider, useArgs, type Args } from "./context/args" import open from "open" import { PromptRefProvider, usePromptRef } from "./context/prompt" -import { TuiConfigProvider, useTuiConfig, type TuiConfig } from "./config" +import { TuiConfigProvider, useTuiConfig, type TuiConfig } from "./config/v1" import { createTuiApiAdapters } from "./plugin/adapters" import { createTuiApi } from "./plugin/api" import { createPluginRuntime, PluginRuntimeProvider, usePluginRuntime, type TuiPluginHost } from "./plugin/runtime" diff --git a/packages/tui/src/attention.ts b/packages/tui/src/attention.ts index b309edcd9d..3fea75e5ff 100644 --- a/packages/tui/src/attention.ts +++ b/packages/tui/src/attention.ts @@ -10,7 +10,7 @@ import type { TuiAttentionSoundPack, TuiAttentionSoundPackInfo, } from "@opencode-ai/plugin/tui" -import { AttentionSoundName, type TuiConfig } from "./config" +import { AttentionSoundName, type TuiConfig } from "./config/v1" import { Schema } from "effect" import stripAnsi from "strip-ansi" import * as TuiAudio from "./audio" diff --git a/packages/tui/src/component/command-palette.tsx b/packages/tui/src/component/command-palette.tsx index 3dd6829c54..a113419653 100644 --- a/packages/tui/src/component/command-palette.tsx +++ b/packages/tui/src/component/command-palette.tsx @@ -8,7 +8,7 @@ import { useKeymapSelector, useOpencodeKeymap, } from "../keymap" -import { useTuiConfig } from "../config" +import { useTuiConfig } from "../config/v1" type PaletteCommandEntry = ReturnType[number] diff --git a/packages/tui/src/component/dialog-mcp.tsx b/packages/tui/src/component/dialog-mcp.tsx index 0ef74bd551..34afc6fa48 100644 --- a/packages/tui/src/component/dialog-mcp.tsx +++ b/packages/tui/src/component/dialog-mcp.tsx @@ -9,7 +9,7 @@ import type { McpServer } from "@opencode-ai/client" import { useClipboard } from "../context/clipboard" import { useToast } from "../ui/toast" import { useKeyboard, useTerminalDimensions } from "@opentui/solid" -import { useTuiConfig } from "../config" +import { useTuiConfig } from "../config/v1" import { getScrollAcceleration } from "../util/scroll" import { useBindings } from "../keymap" diff --git a/packages/tui/src/component/dialog-project-copy-name.tsx b/packages/tui/src/component/dialog-project-copy-name.tsx index 7ac357e1c8..59dcc4a481 100644 --- a/packages/tui/src/component/dialog-project-copy-name.tsx +++ b/packages/tui/src/component/dialog-project-copy-name.tsx @@ -1,7 +1,7 @@ import { InputRenderable, TextAttributes } from "@opentui/core" import { Slug } from "@opencode-ai/core/util/slug" import { createSignal, onMount } from "solid-js" -import { useTuiConfig } from "../config" +import { useTuiConfig } from "../config/v1" import { useTheme } from "../context/theme" import { useBindings, useCommandShortcut } from "../keymap" import { useDialog, type DialogContext } from "../ui/dialog" diff --git a/packages/tui/src/component/dialog-workspace-file-changes.tsx b/packages/tui/src/component/dialog-workspace-file-changes.tsx index bca389c368..231f6c4648 100644 --- a/packages/tui/src/component/dialog-workspace-file-changes.tsx +++ b/packages/tui/src/component/dialog-workspace-file-changes.tsx @@ -5,7 +5,7 @@ import { createMemo, For } from "solid-js" import { createStore } from "solid-js/store" import { FilePath } from "../ui/file-path" import { useTheme } from "../context/theme" -import { useTuiConfig } from "../config" +import { useTuiConfig } from "../config/v1" import { useDialog, type DialogContext } from "../ui/dialog" import { getScrollAcceleration } from "../util/scroll" diff --git a/packages/tui/src/component/prompt/autocomplete.tsx b/packages/tui/src/component/prompt/autocomplete.tsx index 91b4bbdb42..0a58892aef 100644 --- a/packages/tui/src/component/prompt/autocomplete.tsx +++ b/packages/tui/src/component/prompt/autocomplete.tsx @@ -11,7 +11,7 @@ import { useSDK } from "../../context/sdk" import { useData } from "../../context/data" import { getScrollAcceleration } from "../../util/scroll" import { useTuiPaths } from "../../context/runtime" -import { useTuiConfig } from "../../config" +import { useTuiConfig } from "../../config/v1" import { useLocation } from "../../context/location" import { useTheme, selectedForeground } from "../../context/theme" import { SplitBorder } from "../../ui/border" diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index 54a9d1b70c..d235d8cf42 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -48,7 +48,7 @@ import { createFadeIn } from "../../util/signal" import { DialogSkill } from "../dialog-skill" import { useArgs } from "../../context/args" import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut, useLeaderActive, useOpencodeKeymap } from "../../keymap" -import { useTuiConfig } from "../../config" +import { useTuiConfig } from "../../config/v1" import { usePromptMove } from "./move" import { readLocalAttachment } from "./local-attachment" import { useData } from "../../context/data" diff --git a/packages/tui/src/config/index.tsx b/packages/tui/src/config/v1/index.tsx similarity index 100% rename from packages/tui/src/config/index.tsx rename to packages/tui/src/config/v1/index.tsx diff --git a/packages/tui/src/config/keybind.ts b/packages/tui/src/config/v1/keybind.ts similarity index 98% rename from packages/tui/src/config/keybind.ts rename to packages/tui/src/config/v1/keybind.ts index 7cab6b8c5b..9dd03c4a2a 100644 --- a/packages/tui/src/config/keybind.ts +++ b/packages/tui/src/config/v1/keybind.ts @@ -97,8 +97,6 @@ export const Definitions = { session_interrupt: keybind("escape", "Interrupt current session"), session_background: keybind("ctrl+b", "Background blocking session tools"), session_compact: keybind("c", "Compact the session"), - session_toggle_timestamps: keybind("none", "Toggle message timestamps"), - session_toggle_generic_tool_output: keybind("none", "Toggle generic tool output"), session_queued_prompts: keybind("q", "Manage queued prompts"), session_child_first: keybind("down,down", "Toggle subagent picker"), session_child_cycle: keybind("right", "Go to next child session"), @@ -146,7 +144,6 @@ export const Definitions = { messages_undo: keybind("u", "Undo message"), messages_redo: keybind("r", "Redo message"), messages_toggle_conceal: keybind("h", "Toggle code block concealment in messages"), - tool_details: keybind("none", "Toggle tool details visibility"), display_thinking: keybind("none", "Toggle thinking blocks visibility"), prompt_submit: keybind("none", "Submit prompt"), @@ -303,8 +300,6 @@ export const CommandMap = { session_interrupt: "session.interrupt", session_background: "session.background", session_compact: "session.compact", - session_toggle_timestamps: "session.toggle.timestamps", - session_toggle_generic_tool_output: "session.toggle.generic_tool_output", session_queued_prompts: "session.queued_prompts", session_child_first: "session.child.first", session_child_cycle: "session.child.next", @@ -350,7 +345,6 @@ export const CommandMap = { messages_undo: "session.undo", messages_redo: "session.redo", messages_toggle_conceal: "session.toggle.conceal", - tool_details: "session.toggle.actions", display_thinking: "session.toggle.thinking", prompt_submit: "prompt.submit", prompt_editor_context_clear: "prompt.editor_context.clear", diff --git a/packages/tui/src/config/v2/index.ts b/packages/tui/src/config/v2/index.ts new file mode 100644 index 0000000000..23ebc01e56 --- /dev/null +++ b/packages/tui/src/config/v2/index.ts @@ -0,0 +1,97 @@ +export * as TuiConfigV2 from "." + +import { Schema } from "effect" +import { TuiKeybind } from "./keybind" + +export const Plugin = Schema.Union([ + Schema.String, + Schema.Struct({ + package: Schema.String, + options: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + }), +]) + +export const Info = Schema.Struct({ + theme: Schema.optional( + Schema.Struct({ + name: Schema.optional(Schema.String), + mode: Schema.optional(Schema.Literals(["system", "dark", "light"])), + }), + ), + keybinds: Schema.optional(TuiKeybind.KeybindOverrides), + plugins: Schema.optional(Schema.Array(Plugin)), + leader: Schema.optional( + Schema.Struct({ + timeout: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))), + }), + ), + scroll: Schema.optional( + Schema.Struct({ + speed: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0.001))), + acceleration: Schema.optional(Schema.Boolean), + }), + ), + attention: Schema.optional( + Schema.Struct({ + enabled: Schema.optional(Schema.Boolean), + notifications: Schema.optional(Schema.Boolean), + sound: Schema.optional(Schema.Boolean), + volume: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1))), + sound_pack: Schema.optional(Schema.String), + sounds: Schema.optional( + Schema.Record( + Schema.Literals(["default", "question", "permission", "error", "done", "subagent_done"]), + Schema.optionalKey(Schema.String), + ), + ), + }), + ), + diffs: Schema.optional( + Schema.Struct({ + wrap: Schema.optional(Schema.Literals(["word", "none"])), + tree: Schema.optional(Schema.Boolean), + single: Schema.optional(Schema.Boolean), + view: Schema.optional(Schema.Literals(["auto", "split", "unified"])), + }), + ), + terminal: Schema.optional( + Schema.Struct({ + title: Schema.optional(Schema.Boolean), + }), + ), + composer: Schema.optional( + Schema.Struct({ + file_context: Schema.optional(Schema.Boolean), + paste_summary: Schema.optional(Schema.Boolean), + }), + ), + session: Schema.optional( + Schema.Struct({ + sidebar: Schema.optional(Schema.Literals(["auto", "hide"])), + scrollbar: Schema.optional(Schema.Boolean), + thinking: Schema.optional(Schema.Literals(["show", "hide"])), + group_exploration: Schema.optional(Schema.Boolean), + directory_filter: Schema.optional(Schema.Boolean), + }), + ), + which_key: Schema.optional( + Schema.Struct({ + layout: Schema.optional(Schema.Literals(["dock", "overlay"])), + pending_preview: Schema.optional(Schema.Boolean), + }), + ), + hints: Schema.optional( + Schema.Struct({ + tips: Schema.optional(Schema.Boolean), + getting_started: Schema.optional(Schema.Boolean), + }), + ), + updates: Schema.optional( + Schema.Struct({ + skipped: Schema.optional(Schema.String), + }), + ), + animations: Schema.optional(Schema.Boolean), + mouse: Schema.optional(Schema.Boolean), +}) +export type Info = Schema.Schema.Type diff --git a/packages/tui/src/config/v2/keybind.ts b/packages/tui/src/config/v2/keybind.ts new file mode 100644 index 0000000000..5dcd6d4f67 --- /dev/null +++ b/packages/tui/src/config/v2/keybind.ts @@ -0,0 +1,6 @@ +export * as TuiKeybind from "./keybind" + +import { Schema } from "effect" + +export const KeybindOverrides = Schema.Struct({}) +export type KeybindOverrides = Schema.Schema.Type diff --git a/packages/tui/src/context/theme.tsx b/packages/tui/src/context/theme.tsx index 909dd69ac2..79456b8498 100644 --- a/packages/tui/src/context/theme.tsx +++ b/packages/tui/src/context/theme.tsx @@ -23,7 +23,7 @@ import { createEffect, createMemo, onCleanup, onMount } from "solid-js" import { createStore, produce } from "solid-js/store" import { createSimpleContext } from "./helper" import { useKV } from "./kv" -import { useTuiConfig } from "../config" +import { useTuiConfig } from "../config/v1" import { Global } from "@opencode-ai/core/global" import { Glob } from "@opencode-ai/core/util/glob" import { readFile } from "node:fs/promises" diff --git a/packages/tui/src/keymap.tsx b/packages/tui/src/keymap.tsx index 213a32104d..e4dc694935 100644 --- a/packages/tui/src/keymap.tsx +++ b/packages/tui/src/keymap.tsx @@ -14,8 +14,8 @@ import { } from "@opentui/keymap/extras" import { KeymapProvider, useKeymap, useKeymapSelector, useBindings } from "@opentui/keymap/solid" import { createMemo, type Accessor } from "solid-js" -import { useTuiConfig } from "./config" -import { TuiKeybind } from "./config/keybind" +import { useTuiConfig } from "./config/v1" +import { TuiKeybind } from "./config/v1/keybind" export const LEADER_TOKEN = "leader" export const OPENCODE_BASE_MODE = "base" diff --git a/packages/tui/src/plugin/adapters.tsx b/packages/tui/src/plugin/adapters.tsx index b97f7b9b73..088557e931 100644 --- a/packages/tui/src/plugin/adapters.tsx +++ b/packages/tui/src/plugin/adapters.tsx @@ -1,5 +1,5 @@ import type { TuiDialogSelectOption, TuiPluginApi, TuiSlotProps } from "@opencode-ai/plugin/tui" -import type { TuiConfig } from "../config" +import type { TuiConfig } from "../config/v1" import type { useEvent } from "../context/event" import type { useRoute } from "../context/route" import type { useSDK } from "../context/sdk" diff --git a/packages/tui/src/plugin/command-shim.ts b/packages/tui/src/plugin/command-shim.ts index 61eb833fe7..d909f328cb 100644 --- a/packages/tui/src/plugin/command-shim.ts +++ b/packages/tui/src/plugin/command-shim.ts @@ -1,6 +1,6 @@ // Legacy `api.command` bridge for v1 plugins; remove in v2. import type { TuiCommand, TuiPluginApi } from "@opencode-ai/plugin/tui" -import { TuiKeybind } from "../config/keybind" +import { TuiKeybind } from "../config/v1/keybind" import type { DialogContext } from "../ui/dialog" const COMMAND_PALETTE_SHOW = "command.palette.show" diff --git a/packages/tui/src/plugin/runtime.tsx b/packages/tui/src/plugin/runtime.tsx index 4130ac9be7..ea2d88d84b 100644 --- a/packages/tui/src/plugin/runtime.tsx +++ b/packages/tui/src/plugin/runtime.tsx @@ -4,7 +4,7 @@ import type { TuiPluginInstallResult, TuiPluginStatus, } from "@opencode-ai/plugin/tui" -import type { TuiConfig } from "../config" +import type { TuiConfig } from "../config/v1" import { createContext, createSignal, useContext, type JSX, type ParentProps } from "solid-js" import { createPluginRoutes } from "./api" import { createSlots, type HostSlots } from "./slots" diff --git a/packages/tui/src/routes/home.tsx b/packages/tui/src/routes/home.tsx index 31570a2550..e6fc75dfb3 100644 --- a/packages/tui/src/routes/home.tsx +++ b/packages/tui/src/routes/home.tsx @@ -9,7 +9,7 @@ import { useLocal } from "../context/local" import { usePluginRuntime } from "../plugin/runtime" import { useEditorContext } from "../context/editor" import { useTerminalDimensions } from "@opentui/solid" -import { useTuiConfig } from "../config" +import { useTuiConfig } from "../config/v1" import { HomeSessionDestinationProvider } from "./home/session-destination" import { useData } from "../context/data" import { LocationProvider } from "../context/location" diff --git a/packages/tui/src/routes/session/form.tsx b/packages/tui/src/routes/session/form.tsx index cde48466a9..90f8a75d6e 100644 --- a/packages/tui/src/routes/session/form.tsx +++ b/packages/tui/src/routes/session/form.tsx @@ -10,7 +10,7 @@ import { useSDK } from "../../context/sdk" import { useClipboard } from "../../context/clipboard" import { SplitBorder } from "../../ui/border" import { useToast } from "../../ui/toast" -import { useTuiConfig } from "../../config" +import { useTuiConfig } from "../../config/v1" import { useBindings, useOpencodeModeStack } from "../../keymap" const FORM_MODE = "form" diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index bce2a7b33c..b24526a8aa 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -64,7 +64,7 @@ import { FormPrompt } from "./form" import { DialogExportOptions } from "../../ui/dialog-export-options" import { DialogExportResult } from "../../ui/dialog-export-result" import { sessionEpilogue } from "../../util/presentation" -import { useTuiConfig } from "../../config" +import { useTuiConfig } from "../../config/v1" import { useClipboard } from "../../context/clipboard" import { nextThinkingMode, reasoningSummary, useThinkingMode, type ThinkingMode } from "../../context/thinking" import { getScrollAcceleration } from "../../util/scroll" @@ -89,11 +89,8 @@ const sessionBindingCommands = [ "session.redo", "session.sidebar.toggle", "session.toggle.conceal", - "session.toggle.timestamps", "session.toggle.thinking", - "session.toggle.actions", "session.toggle.scrollbar", - "session.toggle.generic_tool_output", "session.toggle.exploration_grouping", "session.first", "session.last", @@ -127,9 +124,6 @@ const context = createContext<{ conceal: () => boolean thinkingMode: () => ThinkingMode showThinking: () => boolean - showTimestamps: () => boolean - showDetails: () => boolean - showGenericToolOutput: () => boolean groupExploration: () => boolean diffWrapMode: () => "word" | "none" models: () => ModelInfo[] @@ -208,13 +202,9 @@ export function Session() { const thinking = useThinkingMode() const thinkingMode = thinking.mode const showThinking = createMemo(() => true) - const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide") - const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true) - const [showAssistantMetadata, _setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true) const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false) const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word") const [_animationsEnabled, _setAnimationsEnabled] = kv.signal("animations_enabled", true) - const [showGenericToolOutput, setShowGenericToolOutput] = kv.signal("generic_tool_output_visibility", false) const [groupExploration, setGroupExploration] = kv.signal("exploration_grouping", true) const wide = createMemo(() => dimensions().width > 120) @@ -224,7 +214,6 @@ export function Session() { if (sidebar() === "auto" && wide()) return true return false }) - const showTimestamps = createMemo(() => timestamps() === "show") const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4) const models = createMemo(() => data.location.model.list(location()) ?? []) @@ -491,19 +480,6 @@ export function Session() { dialog.clear() }, }, - { - title: showTimestamps() ? "Hide timestamps" : "Show timestamps", - value: "session.toggle.timestamps", - category: "Session", - slash: { - name: "timestamps", - aliases: ["toggle-timestamps"], - }, - run: () => { - setTimestamps((prev) => (prev === "show" ? "hide" : "show")) - dialog.clear() - }, - }, { title: (() => { const next = nextThinkingMode(thinkingMode()) @@ -521,15 +497,6 @@ export function Session() { dialog.clear() }, }, - { - title: showDetails() ? "Hide tool details" : "Show tool details", - value: "session.toggle.actions", - category: "Session", - run: () => { - setShowDetails((prev) => !prev) - dialog.clear() - }, - }, { title: "Toggle session scrollbar", value: "session.toggle.scrollbar", @@ -539,15 +506,6 @@ export function Session() { dialog.clear() }, }, - { - title: showGenericToolOutput() ? "Hide generic tool output" : "Show generic tool output", - value: "session.toggle.generic_tool_output", - category: "Session", - run: () => { - setShowGenericToolOutput((prev) => !prev) - dialog.clear() - }, - }, { title: groupExploration() ? "Show exploration tools individually" : "Group exploration tools", value: "session.toggle.exploration_grouping", @@ -727,13 +685,7 @@ export function Session() { try { const sessionData = session() if (!sessionData) return - const transcript = formatSessionTranscript( - sessionData, - messages(), - showThinking(), - showDetails(), - showAssistantMetadata(), - ) + const transcript = formatSessionTranscript(sessionData, messages(), showThinking()) await clipboard.write?.(transcript) toast.show({ message: "Session transcript copied to clipboard!", variant: "success" }) } catch { @@ -754,19 +706,13 @@ export function Session() { const sessionData = session() if (!sessionData) return - const options = await DialogExportOptions.show(dialog, showThinking(), showDetails(), showAssistantMetadata()) + const options = await DialogExportOptions.show(dialog, showThinking()) if (options === null) return const content = options.format === "markdown" - ? formatSessionTranscript( - sessionData, - messages(), - options.thinking, - options.toolDetails, - options.assistantMetadata, - ) + ? formatSessionTranscript(sessionData, messages(), options.thinking) : await (async () => { if (options.debug) { const events: { readonly created: number }[] = [] @@ -922,9 +868,6 @@ export function Session() { conceal, thinkingMode, showThinking, - showTimestamps, - showDetails, - showGenericToolOutput, groupExploration, diffWrapMode, models, @@ -1534,13 +1477,7 @@ function UserMessage(props: { message: SessionMessageUser }) { > {props.message.text} - + {(file) => { const label = file.mime === "application/x-directory" ? "dir" : "file" @@ -1557,11 +1494,6 @@ function UserMessage(props: { message: SessionMessageUser }) { - - - {Locale.todayTimeOrDateTime(props.message.time.created)} - - @@ -1865,31 +1797,7 @@ function TextPart(props: { last: boolean; part: SessionMessageAssistantText }) { // Pending messages moved to individual tool pending functions function ToolPart(props: { part: SessionMessageAssistantTool }) { - const ctx = use() - const data = useData() const display = createMemo(() => toolDisplay(props.part.name)) - const activeBackgroundWork = createMemo(() => { - if (props.part.state.status === "streaming") return false - if (display() === "shell") { - const shellID = stringValue(props.part.state.structured.shellID) - return Boolean(shellID && data.shell.get(shellID)) - } - if (display() === "subagent") { - const sessionID = - stringValue(props.part.state.structured.sessionID) ?? stringValue(props.part.state.structured.sessionId) - return Boolean(sessionID && data.session.status(sessionID) === "running") - } - return false - }) - - // Hide tool if showDetails is false and tool completed successfully - const shouldHide = createMemo(() => { - if (ctx.showDetails()) return false - if (activeBackgroundWork()) return false - if (props.part.state.status !== "completed") return false - if (display() === "shell") return false - return true - }) const toolprops = { get metadata() { @@ -1913,52 +1821,50 @@ function ToolPart(props: { part: SessionMessageAssistantTool }) { } return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } @@ -1970,40 +1876,55 @@ type ToolProps = { part: SessionMessageAssistantTool } function GenericTool(props: ToolProps) { - const { theme } = useTheme() - const ctx = use() + const { theme, syntax } = useTheme() const output = createMemo(() => props.output?.trim() ?? "") + const args = createMemo(() => JSON.stringify(props.input, null, 2)) const [expanded, setExpanded] = createSignal(false) - const maxLines = 3 - const maxChars = createMemo(() => maxLines * Math.max(20, ctx.width - 6)) - const collapsed = createMemo(() => collapseToolOutput(output(), maxLines, maxChars())) - const limited = createMemo(() => { - if (expanded() || !collapsed().overflow) return output() - return collapsed().output - }) + const expandable = createMemo(() => Object.keys(props.input).length > 0 || output().length > 0) return ( - - {props.tool} {input(props.input)} - - } + setExpanded((value) => !value) : undefined} > - setExpanded((prev) => !prev) : undefined} - > - - {limited()} - - {expanded() ? "Click to collapse" : "Click to expand"} + + + 0}> + + + Input + + + + + + + + {(value) => ( + + + Output + + + + {value()} + + + + )} - - + + ) } @@ -2106,12 +2027,7 @@ export function InlineToolRow(props: { - {props.pending} - } - when={props.complete || props.failed} - > + {props.pending}} when={props.complete || props.failed}> ( - {title()} - - } + fallback={{title()}} > {title().replace(/^# /, "")} @@ -2676,11 +2588,7 @@ function ApplyPatch(props: ToolProps) { : undefined } title={ - targets().length === 1 - ? undefined - : props.part.state.status === "error" - ? "# Patch failed" - : "Patching" + targets().length === 1 ? undefined : props.part.state.status === "error" ? "# Patch failed" : "Patching" } part={props.part} spinner={props.part.state.status === "streaming" || props.part.state.status === "running"} @@ -2806,13 +2714,7 @@ function recordValue(value: unknown): Record | undefined { return value as Record } -function formatSessionTranscript( - session: SessionInfo, - messages: SessionMessageInfo[], - thinking: boolean, - toolDetails: boolean, - assistantMetadata: boolean, -) { +function formatSessionTranscript(session: SessionInfo, messages: SessionMessageInfo[], thinking: boolean) { const body = messages.flatMap((message) => { if (message.type === "user") return [`## User\n\n${message.text}`] if (message.type === "shell") @@ -2821,7 +2723,6 @@ function formatSessionTranscript( const content = message.content.flatMap((item) => { if (item.type === "text") return [item.text] if (item.type === "reasoning") return thinking ? [`_Thinking:_\n\n${item.text}`] : [] - if (!toolDetails) return [`**Tool: ${item.name}**`] const input = typeof item.state.input === "string" ? item.state.input : JSON.stringify(item.state.input, null, 2) const output = item.state.status === "error" @@ -2833,13 +2734,7 @@ function formatSessionTranscript( .join("\n") return [`**Tool: ${item.name}**\n\n**Input:**\n\`\`\`json\n${input}\n\`\`\`\n\n${output}`] }) - const duration = message.time.completed - ? ` · ${((message.time.completed - message.time.created) / 1000).toFixed(1)}s` - : "" - const heading = assistantMetadata - ? `## Assistant (${message.agent} · ${message.model.providerID}/${message.model.id}${duration})` - : "## Assistant" - return [`${heading}\n\n${content.join("\n\n")}`] + return [`## Assistant\n\n${content.join("\n\n")}`] }) return `# ${session.title}\n\n**Session ID:** ${session.id}\n**Created:** ${new Date(session.time.created).toLocaleString()}\n**Updated:** ${new Date(session.time.updated).toLocaleString()}\n\n---\n\n${body.join("\n\n---\n\n")}\n` } diff --git a/packages/tui/src/routes/session/permission.tsx b/packages/tui/src/routes/session/permission.tsx index cfbb2037cc..51553a97d9 100644 --- a/packages/tui/src/routes/session/permission.tsx +++ b/packages/tui/src/routes/session/permission.tsx @@ -12,7 +12,7 @@ import { filetype } from "../../util/filetype" import { Locale } from "../../util/locale" import { webSearchProviderLabel } from "../../util/tool-display" import { getScrollAcceleration } from "../../util/scroll" -import { useTuiConfig } from "../../config" +import { useTuiConfig } from "../../config/v1" import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut } from "../../keymap" import { usePathFormatter } from "../../context/path-format" diff --git a/packages/tui/src/routes/session/sidebar.tsx b/packages/tui/src/routes/session/sidebar.tsx index d2ebcd2851..b1f7744b9a 100644 --- a/packages/tui/src/routes/session/sidebar.tsx +++ b/packages/tui/src/routes/session/sidebar.tsx @@ -1,7 +1,7 @@ import { useData } from "../../context/data" import { createMemo, Show } from "solid-js" import { useTheme } from "../../context/theme" -import { useTuiConfig } from "../../config" +import { useTuiConfig } from "../../config/v1" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { usePluginRuntime } from "../../plugin/runtime" diff --git a/packages/tui/src/ui/dialog-export-options.tsx b/packages/tui/src/ui/dialog-export-options.tsx index 914d0960c8..db05529158 100644 --- a/packages/tui/src/ui/dialog-export-options.tsx +++ b/packages/tui/src/ui/dialog-export-options.tsx @@ -9,20 +9,11 @@ export type ExportFormat = "markdown" | "json" export type DialogExportOptionsProps = { defaultThinking: boolean - defaultToolDetails: boolean - defaultAssistantMetadata: boolean - onConfirm?: (options: { - action: "copy" | "export" - format: ExportFormat - debug: boolean - thinking: boolean - toolDetails: boolean - assistantMetadata: boolean - }) => void + onConfirm?: (options: { action: "copy" | "export"; format: ExportFormat; debug: boolean; thinking: boolean }) => void onCancel?: () => void } -type Active = ExportFormat | "debug" | "thinking" | "toolDetails" | "assistantMetadata" | "copy" | "export" +type Active = ExportFormat | "debug" | "thinking" | "copy" | "export" export function DialogExportOptions(props: DialogExportOptionsProps) { const dialog = useDialog() @@ -31,8 +22,6 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { format: "markdown" as ExportFormat, debug: false, thinking: props.defaultThinking, - toolDetails: props.defaultToolDetails, - assistantMetadata: props.defaultAssistantMetadata, active: "markdown" as Active, }) @@ -42,8 +31,6 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { format: store.format, debug: store.debug, thinking: store.thinking, - toolDetails: store.toolDetails, - assistantMetadata: store.assistantMetadata, }) const activate = () => { @@ -53,8 +40,6 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { } if (store.active === "debug") setStore("debug", !store.debug) if (store.active === "thinking") setStore("thinking", !store.thinking) - if (store.active === "toolDetails") setStore("toolDetails", !store.toolDetails) - if (store.active === "assistantMetadata") setStore("assistantMetadata", !store.assistantMetadata) if (store.active === "copy" || store.active === "export") confirm(store.active) } @@ -67,7 +52,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { cmd: () => { const order: Active[] = store.format === "markdown" - ? ["markdown", "json", "thinking", "toolDetails", "assistantMetadata", "copy", "export"] + ? ["markdown", "json", "thinking", "copy", "export"] : ["markdown", "json", "debug", "copy", "export"] setStore("active", order[(order.indexOf(store.active) + 1) % order.length]) }, @@ -86,11 +71,6 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { setStore("active", format) } - const toggle = (option: "thinking" | "toolDetails" | "assistantMetadata") => { - setStore("active", option) - setStore(option, !store[option]) - } - return ( @@ -121,30 +101,19 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { - - - {(item) => ( - toggle(item[0])} - > - - {store[item[0]] ? "[x]" : "[ ]"} - - {item[1]} - - )} - + { + setStore("active", "thinking") + setStore("thinking", !store.thinking) + }} + > + + {store.thinking ? "[x]" : "[ ]"} + + Include thinking @@ -170,12 +139,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { > Copy - confirm("export")} - > + confirm("export")}> Export @@ -183,26 +147,17 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { ) } -DialogExportOptions.show = ( - dialog: DialogContext, - defaultThinking: boolean, - defaultToolDetails: boolean, - defaultAssistantMetadata: boolean, -) => { +DialogExportOptions.show = (dialog: DialogContext, defaultThinking: boolean) => { return new Promise<{ action: "copy" | "export" format: ExportFormat debug: boolean thinking: boolean - toolDetails: boolean - assistantMetadata: boolean } | null>((resolve) => { dialog.replace( () => ( resolve(options)} onCancel={() => resolve(null)} /> diff --git a/packages/tui/src/ui/dialog-prompt.tsx b/packages/tui/src/ui/dialog-prompt.tsx index f518fb2950..0898b0ae42 100644 --- a/packages/tui/src/ui/dialog-prompt.tsx +++ b/packages/tui/src/ui/dialog-prompt.tsx @@ -3,7 +3,7 @@ import { useTheme } from "../context/theme" import { useDialog, type DialogContext } from "./dialog" import { Show, createEffect, createSignal, onMount, type JSX } from "solid-js" import { Spinner } from "../component/spinner" -import { useTuiConfig } from "../config" +import { useTuiConfig } from "../config/v1" import { useBindings, useCommandShortcut } from "../keymap" export type DialogPromptProps = { diff --git a/packages/tui/src/ui/dialog-select.tsx b/packages/tui/src/ui/dialog-select.tsx index 28235531e2..ef13bc1d50 100644 --- a/packages/tui/src/ui/dialog-select.tsx +++ b/packages/tui/src/ui/dialog-select.tsx @@ -17,7 +17,7 @@ import { isDeepEqual } from "remeda" import { useDialog, type DialogContext } from "./dialog" import { Locale } from "../util/locale" import { getScrollAcceleration } from "../util/scroll" -import { useTuiConfig } from "../config" +import { useTuiConfig } from "../config/v1" import { formatKeyBindings, useBindings, useKeymapSelector } from "../keymap" export interface DialogSelectProps { diff --git a/packages/tui/test/cli/tui/dialog-prompt.test.tsx b/packages/tui/test/cli/tui/dialog-prompt.test.tsx index cca8f321e2..160f660e38 100644 --- a/packages/tui/test/cli/tui/dialog-prompt.test.tsx +++ b/packages/tui/test/cli/tui/dialog-prompt.test.tsx @@ -8,7 +8,7 @@ import path from "node:path" import { onCleanup } from "solid-js" import { tmpdir } from "../../fixture/fixture" import { createTuiResolvedConfig } from "../../fixture/tui-runtime" -import type { TuiKeybind } from "../../../src/config/keybind" +import type { TuiKeybind } from "../../../src/config/v1/keybind" import { TestTuiContexts } from "../../fixture/tui-environment" async function wait(fn: () => boolean, timeout = 2000) { @@ -41,7 +41,7 @@ async function mountPrompt(input: { import("../../../src/ui/dialog-prompt"), import("../../../src/context/kv"), import("../../../src/context/theme"), - import("../../../src/config"), + import("../../../src/config/v1"), import("../../../src/ui/toast"), import("../../../src/keymap"), ]) diff --git a/packages/tui/test/cli/tui/diff-viewer-file-tree.test.tsx b/packages/tui/test/cli/tui/diff-viewer-file-tree.test.tsx index 2a5a172f9c..3c6fc8a84a 100644 --- a/packages/tui/test/cli/tui/diff-viewer-file-tree.test.tsx +++ b/packages/tui/test/cli/tui/diff-viewer-file-tree.test.tsx @@ -6,7 +6,7 @@ import type { JSX } from "solid-js" import { createTuiResolvedConfig } from "../../fixture/tui-runtime" import { KVProvider } from "../../../src/context/kv" import { ThemeProvider } from "../../../src/context/theme" -import { TuiConfigProvider } from "../../../src/config" +import { TuiConfigProvider } from "../../../src/config/v1" import { DiffViewerFileTree } from "../../../src/feature-plugins/system/diff-viewer-file-tree" import { TestTuiContexts } from "../../fixture/tui-environment" import { diff --git a/packages/tui/test/cli/tui/diff-viewer.test.tsx b/packages/tui/test/cli/tui/diff-viewer.test.tsx index c882e67f14..3b77f7ed21 100644 --- a/packages/tui/test/cli/tui/diff-viewer.test.tsx +++ b/packages/tui/test/cli/tui/diff-viewer.test.tsx @@ -7,9 +7,9 @@ import type { TuiPluginApi, TuiPluginMeta, TuiRouteCurrent, TuiRouteDefinition } import type { Session } from "@opencode-ai/sdk/v2" import { KVProvider } from "../../../src/context/kv" import { ThemeProvider } from "../../../src/context/theme" -import { TuiConfigProvider } from "../../../src/config" +import { TuiConfigProvider } from "../../../src/config/v1" import { SDKProvider } from "../../../src/context/sdk" -import { TuiKeybind } from "../../../src/config/keybind" +import { TuiKeybind } from "../../../src/config/v1/keybind" import { OpencodeKeymapProvider } from "../../../src/keymap" import diffViewerPlugin from "../../../src/feature-plugins/system/diff-viewer" import { createTuiPluginApi } from "../../fixture/tui-plugin" diff --git a/packages/tui/test/cli/tui/form.test.tsx b/packages/tui/test/cli/tui/form.test.tsx index b447e3ab43..e73ef9973d 100644 --- a/packages/tui/test/cli/tui/form.test.tsx +++ b/packages/tui/test/cli/tui/form.test.tsx @@ -10,7 +10,7 @@ import type { FormWithLocation } from "../../../src/context/data" import { KVProvider } from "../../../src/context/kv" import { SDKProvider } from "../../../src/context/sdk" import { ThemeProvider } from "../../../src/context/theme" -import { TuiConfigProvider } from "../../../src/config" +import { TuiConfigProvider } from "../../../src/config/v1" import { OpencodeKeymapProvider, registerOpencodeKeymap } from "../../../src/keymap" import { ToastProvider } from "../../../src/ui/toast" import { tmpdir } from "../../fixture/fixture" diff --git a/packages/tui/test/config.test.tsx b/packages/tui/test/config.test.tsx index 782aa55623..96f7430d53 100644 --- a/packages/tui/test/config.test.tsx +++ b/packages/tui/test/config.test.tsx @@ -11,7 +11,7 @@ import { TuiConfigProvider, type Info as TuiConfigInfo, useTuiConfig, -} from "../src/config" +} from "../src/config/v1" const decodeInfo = Schema.decodeUnknownSync(Info) const decodePlugin = Schema.decodeUnknownSync(PluginSpec) diff --git a/packages/tui/test/fixture/tui-runtime.ts b/packages/tui/test/fixture/tui-runtime.ts index 6878c08c60..028396c57d 100644 --- a/packages/tui/test/fixture/tui-runtime.ts +++ b/packages/tui/test/fixture/tui-runtime.ts @@ -1,5 +1,5 @@ -import { resolve, type Info, type Resolved } from "../../src/config" -import { TuiKeybind } from "../../src/config/keybind" +import { resolve, type Info, type Resolved } from "../../src/config/v1" +import { TuiKeybind } from "../../src/config/v1/keybind" type ResolvedInput = Omit & { attention?: Partial diff --git a/packages/tui/test/keymap.test.tsx b/packages/tui/test/keymap.test.tsx index fb3b685dbd..70fbd6e6b2 100644 --- a/packages/tui/test/keymap.test.tsx +++ b/packages/tui/test/keymap.test.tsx @@ -4,7 +4,7 @@ import { createBindingLookup } from "@opentui/keymap/extras" import { testRender, useRenderer } from "@opentui/solid" import { expect, test } from "bun:test" import { onCleanup } from "solid-js" -import { TuiKeybind } from "../src/config/keybind" +import { TuiKeybind } from "../src/config/v1/keybind" import { formatKeySequence, getOpencodeModeStack,