@agentprojectcontext/apx 1.15.6 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +46 -5
- package/src/cli/commands/log.js +113 -0
- package/src/cli/commands/overlay.js +253 -0
- package/src/cli/commands/sys.js +88 -16
- package/src/cli/index.js +23 -1
- package/src/cli/terminal-chat/renderer.js +71 -56
- package/src/cli-ts/commands/agent.ts +173 -0
- package/src/cli-ts/commands/chat.ts +119 -0
- package/src/cli-ts/commands/daemon.ts +112 -0
- package/src/cli-ts/commands/exec.ts +109 -0
- package/src/cli-ts/commands/mcp.ts +235 -0
- package/src/cli-ts/commands/session.ts +224 -0
- package/src/cli-ts/commands/status.ts +61 -0
- package/src/cli-ts/http.ts +36 -0
- package/src/cli-ts/index.ts +73 -0
- package/src/cli-ts/ui.ts +107 -0
- package/src/core/logging.js +81 -0
- package/src/daemon/api.js +58 -0
- package/src/daemon/engines/anthropic.js +60 -1
- package/src/daemon/engines/index.js +2 -1
- package/src/daemon/engines/ollama.js +70 -3
- package/src/daemon/index.js +58 -0
- package/src/daemon/overlay-ws.js +40 -0
- package/src/daemon/plugins/index.js +2 -1
- package/src/daemon/plugins/overlay.js +177 -0
- package/src/daemon/plugins/telegram.js +15 -3
- package/src/daemon/super-agent-langchain.js +296 -0
- package/src/daemon/super-agent.js +115 -19
- package/src/daemon/transcription.js +262 -59
- package/src/daemon/whisper-server.py +57 -6
- package/src/overlay/index.html +44 -0
- package/src/overlay/main.js +480 -0
- package/src/overlay/package.json +3 -0
- package/src/overlay/preload.js +34 -0
- package/src/overlay/renderer.js +371 -0
- package/src/overlay/style.css +250 -0
- package/src/tui/_shims/cli-error.ts +6 -0
- package/src/tui/_shims/cli-logo.ts +18 -0
- package/src/tui/_shims/cli-ui.ts +1 -0
- package/src/tui/_shims/config-console-state.ts +7 -0
- package/src/tui/_shims/core-any.ts +30 -0
- package/src/tui/_shims/core-binary.ts +13 -0
- package/src/tui/_shims/core-flag.ts +3 -0
- package/src/tui/_shims/core-log.ts +14 -0
- package/src/tui/_shims/lsp-language.ts +1 -0
- package/src/tui/_shims/opencode-any.ts +135 -0
- package/src/tui/_shims/opencode-sdk-v2.ts +48 -0
- package/src/tui/_shims/plugin-tui.ts +13 -0
- package/src/tui/_shims/provider-provider.ts +10 -0
- package/src/tui/_shims/session-retry.ts +1 -0
- package/src/tui/_shims/session-schema.ts +15 -0
- package/src/tui/_shims/session-session.ts +3 -0
- package/src/tui/_shims/snapshot.ts +4 -0
- package/src/tui/_shims/tool-any.ts +18 -0
- package/src/tui/_shims/util-error.ts +7 -0
- package/src/tui/_shims/util-filesystem.ts +79 -0
- package/src/tui/_shims/util-format.ts +7 -0
- package/src/tui/_shims/util-iife.ts +3 -0
- package/src/tui/_shims/util-locale.ts +10 -0
- package/src/tui/_shims/util-process.ts +38 -0
- package/src/tui/app.tsx +783 -0
- package/src/tui/asset/charge.wav +0 -0
- package/src/tui/asset/pulse-a.wav +0 -0
- package/src/tui/asset/pulse-b.wav +0 -0
- package/src/tui/asset/pulse-c.wav +0 -0
- package/src/tui/attach.ts +100 -0
- package/src/tui/component/bg-pulse-render.ts +436 -0
- package/src/tui/component/bg-pulse.tsx +99 -0
- package/src/tui/component/border.tsx +21 -0
- package/src/tui/component/dialog-agent.tsx +31 -0
- package/src/tui/component/dialog-console-org.tsx +103 -0
- package/src/tui/component/dialog-mcp.tsx +85 -0
- package/src/tui/component/dialog-model.tsx +175 -0
- package/src/tui/component/dialog-provider.tsx +456 -0
- package/src/tui/component/dialog-retry-action.tsx +160 -0
- package/src/tui/component/dialog-session-delete-failed.tsx +99 -0
- package/src/tui/component/dialog-session-list.tsx +323 -0
- package/src/tui/component/dialog-session-rename.tsx +31 -0
- package/src/tui/component/dialog-skill.tsx +36 -0
- package/src/tui/component/dialog-stash.tsx +87 -0
- package/src/tui/component/dialog-status.tsx +168 -0
- package/src/tui/component/dialog-tag.tsx +44 -0
- package/src/tui/component/dialog-theme-list.tsx +50 -0
- package/src/tui/component/dialog-variant.tsx +39 -0
- package/src/tui/component/dialog-workspace-create.tsx +302 -0
- package/src/tui/component/dialog-workspace-file-changes.tsx +138 -0
- package/src/tui/component/dialog-workspace-unavailable.tsx +69 -0
- package/src/tui/component/error-component.tsx +92 -0
- package/src/tui/component/logo.tsx +896 -0
- package/src/tui/component/plugin-route-missing.tsx +14 -0
- package/src/tui/component/prompt/autocomplete.tsx +869 -0
- package/src/tui/component/prompt/cwd.ts +0 -0
- package/src/tui/component/prompt/frecency.tsx +90 -0
- package/src/tui/component/prompt/history.tsx +108 -0
- package/src/tui/component/prompt/index.tsx +1809 -0
- package/src/tui/component/prompt/part.ts +16 -0
- package/src/tui/component/prompt/stash.tsx +101 -0
- package/src/tui/component/prompt/traits.ts +35 -0
- package/src/tui/component/spinner.tsx +24 -0
- package/src/tui/component/startup-loading.tsx +63 -0
- package/src/tui/component/todo-item.tsx +32 -0
- package/src/tui/component/use-connected.tsx +9 -0
- package/src/tui/component/workspace-label.tsx +19 -0
- package/src/tui/config/cwd.ts +5 -0
- package/src/tui/config/keybind.ts +432 -0
- package/src/tui/config/tui-migrate.ts +154 -0
- package/src/tui/config/tui-schema.ts +34 -0
- package/src/tui/config/tui.ts +46 -0
- package/src/tui/context/aggregate-failures.ts +34 -0
- package/src/tui/context/args.tsx +15 -0
- package/src/tui/context/command-palette.tsx +163 -0
- package/src/tui/context/directory.ts +15 -0
- package/src/tui/context/editor-zed.ts +283 -0
- package/src/tui/context/editor.ts +468 -0
- package/src/tui/context/event-apx.ts +22 -0
- package/src/tui/context/event.ts +6 -0
- package/src/tui/context/exit.tsx +60 -0
- package/src/tui/context/helper.tsx +25 -0
- package/src/tui/context/kv.tsx +81 -0
- package/src/tui/context/local.tsx +608 -0
- package/src/tui/context/path-format.tsx +39 -0
- package/src/tui/context/project-apx.tsx +48 -0
- package/src/tui/context/project.tsx +7 -0
- package/src/tui/context/prompt.tsx +18 -0
- package/src/tui/context/route.tsx +52 -0
- package/src/tui/context/sdk-apx.tsx +185 -0
- package/src/tui/context/sdk.tsx +6 -0
- package/src/tui/context/sync-apx.tsx +178 -0
- package/src/tui/context/sync-v2.tsx +16 -0
- package/src/tui/context/sync.tsx +118 -0
- package/src/tui/context/theme/aura.json +69 -0
- package/src/tui/context/theme/ayu.json +80 -0
- package/src/tui/context/theme/carbonfox.json +248 -0
- package/src/tui/context/theme/catppuccin-frappe.json +230 -0
- package/src/tui/context/theme/catppuccin-macchiato.json +230 -0
- package/src/tui/context/theme/catppuccin.json +112 -0
- package/src/tui/context/theme/cobalt2.json +225 -0
- package/src/tui/context/theme/cursor.json +249 -0
- package/src/tui/context/theme/dracula.json +219 -0
- package/src/tui/context/theme/everforest.json +241 -0
- package/src/tui/context/theme/flexoki.json +237 -0
- package/src/tui/context/theme/github.json +233 -0
- package/src/tui/context/theme/gruvbox.json +242 -0
- package/src/tui/context/theme/kanagawa.json +77 -0
- package/src/tui/context/theme/lucent-orng.json +234 -0
- package/src/tui/context/theme/material.json +235 -0
- package/src/tui/context/theme/matrix.json +77 -0
- package/src/tui/context/theme/mercury.json +252 -0
- package/src/tui/context/theme/monokai.json +221 -0
- package/src/tui/context/theme/nightowl.json +221 -0
- package/src/tui/context/theme/nord.json +223 -0
- package/src/tui/context/theme/one-dark.json +84 -0
- package/src/tui/context/theme/opencode.json +245 -0
- package/src/tui/context/theme/orng.json +249 -0
- package/src/tui/context/theme/osaka-jade.json +93 -0
- package/src/tui/context/theme/palenight.json +222 -0
- package/src/tui/context/theme/rosepine.json +234 -0
- package/src/tui/context/theme/solarized.json +223 -0
- package/src/tui/context/theme/synthwave84.json +226 -0
- package/src/tui/context/theme/tokyonight.json +243 -0
- package/src/tui/context/theme/vercel.json +245 -0
- package/src/tui/context/theme/vesper.json +218 -0
- package/src/tui/context/theme/zenburn.json +223 -0
- package/src/tui/context/theme.tsx +1247 -0
- package/src/tui/context/tui-config.tsx +9 -0
- package/src/tui/event.ts +16 -0
- package/src/tui/feature-plugins/home/footer.tsx +94 -0
- package/src/tui/feature-plugins/home/tips-view.tsx +166 -0
- package/src/tui/feature-plugins/home/tips.tsx +59 -0
- package/src/tui/feature-plugins/sidebar/context.tsx +65 -0
- package/src/tui/feature-plugins/sidebar/files.tsx +63 -0
- package/src/tui/feature-plugins/sidebar/footer.tsx +94 -0
- package/src/tui/feature-plugins/sidebar/lsp.tsx +65 -0
- package/src/tui/feature-plugins/sidebar/mcp.tsx +97 -0
- package/src/tui/feature-plugins/sidebar/todo.tsx +49 -0
- package/src/tui/feature-plugins/system/plugins.tsx +269 -0
- package/src/tui/feature-plugins/system/session-v2.tsx +1143 -0
- package/src/tui/feature-plugins/system/which-key.tsx +608 -0
- package/src/tui/keymap.tsx +166 -0
- package/src/tui/layer.ts +6 -0
- package/src/tui/plugin/api.tsx +381 -0
- package/src/tui/plugin/command-shim.ts +109 -0
- package/src/tui/plugin/internal.ts +33 -0
- package/src/tui/plugin/runtime.ts +1069 -0
- package/src/tui/plugin/slots.tsx +60 -0
- package/src/tui/routes/home.tsx +96 -0
- package/src/tui/routes/session/dialog-fork-from-timeline.tsx +76 -0
- package/src/tui/routes/session/dialog-message.tsx +108 -0
- package/src/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/tui/routes/session/footer.tsx +91 -0
- package/src/tui/routes/session/index.tsx +188 -0
- package/src/tui/routes/session/permission.tsx +722 -0
- package/src/tui/routes/session/question.tsx +490 -0
- package/src/tui/routes/session/sidebar.tsx +102 -0
- package/src/tui/routes/session/subagent-footer.tsx +133 -0
- package/src/tui/run.ts +84 -0
- package/src/tui/thread.ts +261 -0
- package/src/tui/tsconfig.json +40 -0
- package/src/tui/ui/dialog-alert.tsx +66 -0
- package/src/tui/ui/dialog-confirm.tsx +108 -0
- package/src/tui/ui/dialog-export-options.tsx +217 -0
- package/src/tui/ui/dialog-help.tsx +40 -0
- package/src/tui/ui/dialog-prompt.tsx +101 -0
- package/src/tui/ui/dialog-select.tsx +553 -0
- package/src/tui/ui/dialog.tsx +211 -0
- package/src/tui/ui/link.tsx +34 -0
- package/src/tui/ui/spinner.ts +368 -0
- package/src/tui/ui/toast.tsx +111 -0
- package/src/tui/util/clipboard.ts +217 -0
- package/src/tui/util/editor.ts +37 -0
- package/src/tui/util/model.ts +23 -0
- package/src/tui/util/provider-origin.ts +7 -0
- package/src/tui/util/revert-diff.ts +18 -0
- package/src/tui/util/scroll.ts +25 -0
- package/src/tui/util/selection.ts +65 -0
- package/src/tui/util/signal.ts +41 -0
- package/src/tui/util/sound.ts +156 -0
- package/src/tui/util/transcript.ts +112 -0
- package/src/tui/validate-session.ts +29 -0
- package/src/tui/win32.ts +130 -0
- package/src/tui/worker.ts +104 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
export * as TuiKeybind from "./keybind"
|
|
2
|
+
|
|
3
|
+
import type { KeyEvent, Renderable } from "@opentui/core"
|
|
4
|
+
import type { Binding } from "@opentui/keymap"
|
|
5
|
+
import type { BindingCommandMap, BindingConfig, BindingDefaults } from "@opentui/keymap/extras"
|
|
6
|
+
import type { DeepMutable } from "@opencode-ai/core/schema"
|
|
7
|
+
import { Schema } from "effect"
|
|
8
|
+
|
|
9
|
+
const KeyStroke = Schema.Struct({
|
|
10
|
+
name: Schema.String,
|
|
11
|
+
ctrl: Schema.optional(Schema.Boolean),
|
|
12
|
+
shift: Schema.optional(Schema.Boolean),
|
|
13
|
+
meta: Schema.optional(Schema.Boolean),
|
|
14
|
+
super: Schema.optional(Schema.Boolean),
|
|
15
|
+
hyper: Schema.optional(Schema.Boolean),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const BindingObject = Schema.Struct({
|
|
19
|
+
key: Schema.Union(Schema.String, KeyStroke),
|
|
20
|
+
event: Schema.optional(Schema.Union(Schema.Literal("press"), Schema.Literal("release"))),
|
|
21
|
+
preventDefault: Schema.optional(Schema.Boolean),
|
|
22
|
+
fallthrough: Schema.optional(Schema.Boolean),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const BindingItem = Schema.Union(Schema.String, KeyStroke, BindingObject)
|
|
26
|
+
export const BindingValueSchema = Schema.Union(
|
|
27
|
+
Schema.Literal(false),
|
|
28
|
+
Schema.Literal("none"),
|
|
29
|
+
BindingItem,
|
|
30
|
+
Schema.Array(BindingItem),
|
|
31
|
+
)
|
|
32
|
+
export type BindingValueSchema = DeepMutable<Schema.Schema.Type<typeof BindingValueSchema>>
|
|
33
|
+
|
|
34
|
+
type Definition = {
|
|
35
|
+
default: BindingValueSchema
|
|
36
|
+
description: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const inputUndoDefault = process.platform === "win32" ? "ctrl+z,ctrl+-,super+z" : "ctrl+-,super+z"
|
|
40
|
+
export const LeaderDefault = "ctrl+x"
|
|
41
|
+
|
|
42
|
+
const keybind = (value: Definition["default"], description: string): Definition => ({ default: value, description })
|
|
43
|
+
|
|
44
|
+
export const Definitions = {
|
|
45
|
+
leader: keybind(LeaderDefault, "Leader key for keybind combinations"),
|
|
46
|
+
|
|
47
|
+
app_exit: keybind("ctrl+c,ctrl+d,<leader>q", "Exit the application"),
|
|
48
|
+
app_debug: keybind("none", "Toggle debug panel"),
|
|
49
|
+
app_console: keybind("none", "Toggle console"),
|
|
50
|
+
app_heap_snapshot: keybind("none", "Write heap snapshot"),
|
|
51
|
+
app_toggle_animations: keybind("none", "Toggle animations"),
|
|
52
|
+
app_toggle_file_context: keybind("none", "Toggle file context"),
|
|
53
|
+
app_toggle_diffwrap: keybind("none", "Toggle diff wrapping"),
|
|
54
|
+
app_toggle_paste_summary: keybind("none", "Toggle paste summary"),
|
|
55
|
+
app_toggle_session_directory_filter: keybind("none", "Toggle session directory filtering"),
|
|
56
|
+
command_list: keybind("ctrl+p", "List available commands"),
|
|
57
|
+
help_show: keybind("none", "Open help dialog"),
|
|
58
|
+
docs_open: keybind("none", "Open documentation"),
|
|
59
|
+
|
|
60
|
+
editor_open: keybind("<leader>e", "Open external editor"),
|
|
61
|
+
theme_list: keybind("<leader>t", "List available themes"),
|
|
62
|
+
theme_switch_mode: keybind("none", "Switch between light and dark theme mode"),
|
|
63
|
+
theme_mode_lock: keybind("none", "Lock or unlock theme mode"),
|
|
64
|
+
sidebar_toggle: keybind("<leader>b", "Toggle sidebar"),
|
|
65
|
+
scrollbar_toggle: keybind("none", "Toggle session scrollbar"),
|
|
66
|
+
status_view: keybind("<leader>s", "View status"),
|
|
67
|
+
|
|
68
|
+
session_export: keybind("<leader>x", "Export session to editor"),
|
|
69
|
+
session_copy: keybind("none", "Copy session transcript"),
|
|
70
|
+
session_new: keybind("<leader>n", "Create a new session"),
|
|
71
|
+
session_list: keybind("<leader>l", "List all sessions"),
|
|
72
|
+
session_timeline: keybind("<leader>g", "Show session timeline"),
|
|
73
|
+
session_fork: keybind("none", "Fork session from message"),
|
|
74
|
+
session_rename: keybind("ctrl+r", "Rename session"),
|
|
75
|
+
session_delete: keybind("ctrl+d", "Delete session"),
|
|
76
|
+
session_share: keybind("none", "Share current session"),
|
|
77
|
+
session_unshare: keybind("none", "Unshare current session"),
|
|
78
|
+
session_interrupt: keybind("escape", "Interrupt current session"),
|
|
79
|
+
session_compact: keybind("<leader>c", "Compact the session"),
|
|
80
|
+
session_toggle_timestamps: keybind("none", "Toggle message timestamps"),
|
|
81
|
+
session_toggle_generic_tool_output: keybind("none", "Toggle generic tool output"),
|
|
82
|
+
session_child_first: keybind("<leader>down", "Go to first child session"),
|
|
83
|
+
session_child_cycle: keybind("right", "Go to next child session"),
|
|
84
|
+
session_child_cycle_reverse: keybind("left", "Go to previous child session"),
|
|
85
|
+
session_parent: keybind("up", "Go to parent session"),
|
|
86
|
+
session_pin_toggle: keybind("ctrl+f", "Pin or unpin session in the session list"),
|
|
87
|
+
session_toggle_recent: keybind("ctrl+h", "Show or hide session in the Recent group"),
|
|
88
|
+
session_cycle_recent: keybind("<leader>]", "Cycle to the previous recent session"),
|
|
89
|
+
session_cycle_recent_reverse: keybind("<leader>[", "Cycle to the next recent session"),
|
|
90
|
+
session_quick_switch_1: keybind("<leader>1", "Switch to session in quick slot 1"),
|
|
91
|
+
session_quick_switch_2: keybind("<leader>2", "Switch to session in quick slot 2"),
|
|
92
|
+
session_quick_switch_3: keybind("<leader>3", "Switch to session in quick slot 3"),
|
|
93
|
+
session_quick_switch_4: keybind("<leader>4", "Switch to session in quick slot 4"),
|
|
94
|
+
session_quick_switch_5: keybind("<leader>5", "Switch to session in quick slot 5"),
|
|
95
|
+
session_quick_switch_6: keybind("<leader>6", "Switch to session in quick slot 6"),
|
|
96
|
+
session_quick_switch_7: keybind("<leader>7", "Switch to session in quick slot 7"),
|
|
97
|
+
session_quick_switch_8: keybind("<leader>8", "Switch to session in quick slot 8"),
|
|
98
|
+
session_quick_switch_9: keybind("<leader>9", "Switch to session in quick slot 9"),
|
|
99
|
+
|
|
100
|
+
stash_delete: keybind("ctrl+d", "Delete stash entry"),
|
|
101
|
+
model_provider_list: keybind("ctrl+a", "Open provider list from model dialog"),
|
|
102
|
+
model_favorite_toggle: keybind("ctrl+f", "Toggle model favorite status"),
|
|
103
|
+
model_list: keybind("<leader>m", "List available models"),
|
|
104
|
+
model_cycle_recent: keybind("f2", "Next recently used model"),
|
|
105
|
+
model_cycle_recent_reverse: keybind("shift+f2", "Previous recently used model"),
|
|
106
|
+
model_cycle_favorite: keybind("none", "Next favorite model"),
|
|
107
|
+
model_cycle_favorite_reverse: keybind("none", "Previous favorite model"),
|
|
108
|
+
mcp_list: keybind("none", "List MCP servers"),
|
|
109
|
+
provider_connect: keybind("none", "Connect provider"),
|
|
110
|
+
console_org_switch: keybind("none", "Switch console organization"),
|
|
111
|
+
agent_list: keybind("<leader>a", "List agents"),
|
|
112
|
+
agent_cycle: keybind("tab", "Next agent"),
|
|
113
|
+
agent_cycle_reverse: keybind("shift+tab", "Previous agent"),
|
|
114
|
+
variant_cycle: keybind("ctrl+t", "Cycle model variants"),
|
|
115
|
+
variant_list: keybind("none", "List model variants"),
|
|
116
|
+
|
|
117
|
+
messages_page_up: keybind("pageup,ctrl+alt+b", "Scroll messages up by one page"),
|
|
118
|
+
messages_page_down: keybind("pagedown,ctrl+alt+f", "Scroll messages down by one page"),
|
|
119
|
+
messages_line_up: keybind("ctrl+alt+y", "Scroll messages up by one line"),
|
|
120
|
+
messages_line_down: keybind("ctrl+alt+e", "Scroll messages down by one line"),
|
|
121
|
+
messages_half_page_up: keybind("ctrl+alt+u", "Scroll messages up by half page"),
|
|
122
|
+
messages_half_page_down: keybind("ctrl+alt+d", "Scroll messages down by half page"),
|
|
123
|
+
messages_first: keybind("ctrl+g,home", "Navigate to first message"),
|
|
124
|
+
messages_last: keybind("ctrl+alt+g,end", "Navigate to last message"),
|
|
125
|
+
messages_next: keybind("none", "Navigate to next message"),
|
|
126
|
+
messages_previous: keybind("none", "Navigate to previous message"),
|
|
127
|
+
messages_last_user: keybind("none", "Navigate to last user message"),
|
|
128
|
+
messages_copy: keybind("<leader>y", "Copy message"),
|
|
129
|
+
messages_undo: keybind("<leader>u", "Undo message"),
|
|
130
|
+
messages_redo: keybind("<leader>r", "Redo message"),
|
|
131
|
+
messages_toggle_conceal: keybind("<leader>h", "Toggle code block concealment in messages"),
|
|
132
|
+
tool_details: keybind("none", "Toggle tool details visibility"),
|
|
133
|
+
display_thinking: keybind("none", "Toggle thinking blocks visibility"),
|
|
134
|
+
|
|
135
|
+
prompt_submit: keybind("none", "Submit prompt"),
|
|
136
|
+
prompt_editor_context_clear: keybind("none", "Clear editor context"),
|
|
137
|
+
prompt_skills: keybind("none", "Open skill selector"),
|
|
138
|
+
prompt_stash: keybind("none", "Stash prompt"),
|
|
139
|
+
prompt_stash_pop: keybind("none", "Pop stashed prompt"),
|
|
140
|
+
prompt_stash_list: keybind("none", "List stashed prompts"),
|
|
141
|
+
workspace_set: keybind("none", "Set workspace"),
|
|
142
|
+
|
|
143
|
+
input_clear: keybind("ctrl+c", "Clear input field"),
|
|
144
|
+
input_paste: keybind({ key: "ctrl+v", preventDefault: false }, "Paste from clipboard"),
|
|
145
|
+
input_submit: keybind("return", "Submit input"),
|
|
146
|
+
input_newline: keybind("shift+return,ctrl+return,alt+return,ctrl+j", "Insert newline in input"),
|
|
147
|
+
input_move_left: keybind("left,ctrl+b", "Move cursor left in input"),
|
|
148
|
+
input_move_right: keybind("right,ctrl+f", "Move cursor right in input"),
|
|
149
|
+
input_move_up: keybind("up", "Move cursor up in input"),
|
|
150
|
+
input_move_down: keybind("down", "Move cursor down in input"),
|
|
151
|
+
input_select_left: keybind("shift+left", "Select left in input"),
|
|
152
|
+
input_select_right: keybind("shift+right", "Select right in input"),
|
|
153
|
+
input_select_up: keybind("shift+up", "Select up in input"),
|
|
154
|
+
input_select_down: keybind("shift+down", "Select down in input"),
|
|
155
|
+
input_line_home: keybind("ctrl+a", "Move to start of line in input"),
|
|
156
|
+
input_line_end: keybind("ctrl+e", "Move to end of line in input"),
|
|
157
|
+
input_select_line_home: keybind("ctrl+shift+a", "Select to start of line in input"),
|
|
158
|
+
input_select_line_end: keybind("ctrl+shift+e", "Select to end of line in input"),
|
|
159
|
+
input_visual_line_home: keybind("alt+a", "Move to start of visual line in input"),
|
|
160
|
+
input_visual_line_end: keybind("alt+e", "Move to end of visual line in input"),
|
|
161
|
+
input_select_visual_line_home: keybind("alt+shift+a", "Select to start of visual line in input"),
|
|
162
|
+
input_select_visual_line_end: keybind("alt+shift+e", "Select to end of visual line in input"),
|
|
163
|
+
input_buffer_home: keybind("home", "Move to start of buffer in input"),
|
|
164
|
+
input_buffer_end: keybind("end", "Move to end of buffer in input"),
|
|
165
|
+
input_select_buffer_home: keybind("shift+home", "Select to start of buffer in input"),
|
|
166
|
+
input_select_buffer_end: keybind("shift+end", "Select to end of buffer in input"),
|
|
167
|
+
input_delete_line: keybind("ctrl+shift+d", "Delete line in input"),
|
|
168
|
+
input_delete_to_line_end: keybind("ctrl+k", "Delete to end of line in input"),
|
|
169
|
+
input_delete_to_line_start: keybind("ctrl+u", "Delete to start of line in input"),
|
|
170
|
+
input_backspace: keybind("backspace,shift+backspace", "Backspace in input"),
|
|
171
|
+
input_delete: keybind("ctrl+d,delete,shift+delete", "Delete character in input"),
|
|
172
|
+
input_undo: keybind(inputUndoDefault, "Undo in input"),
|
|
173
|
+
input_redo: keybind("ctrl+.,super+shift+z", "Redo in input"),
|
|
174
|
+
input_word_forward: keybind("alt+f,alt+right,ctrl+right", "Move word forward in input"),
|
|
175
|
+
input_word_backward: keybind("alt+b,alt+left,ctrl+left", "Move word backward in input"),
|
|
176
|
+
input_select_word_forward: keybind("alt+shift+f,alt+shift+right", "Select word forward in input"),
|
|
177
|
+
input_select_word_backward: keybind("alt+shift+b,alt+shift+left", "Select word backward in input"),
|
|
178
|
+
input_delete_word_forward: keybind("alt+d,alt+delete,ctrl+delete", "Delete word forward in input"),
|
|
179
|
+
input_delete_word_backward: keybind("ctrl+w,ctrl+backspace,alt+backspace", "Delete word backward in input"),
|
|
180
|
+
input_select_all: keybind("super+a", "Select all in input"),
|
|
181
|
+
history_previous: keybind("up", "Previous history item"),
|
|
182
|
+
history_next: keybind("down", "Next history item"),
|
|
183
|
+
|
|
184
|
+
"dialog.select.prev": keybind("up,ctrl+p", "Move to previous dialog item"),
|
|
185
|
+
"dialog.select.next": keybind("down,ctrl+n", "Move to next dialog item"),
|
|
186
|
+
"dialog.select.page_up": keybind("pageup", "Move up one page in dialog"),
|
|
187
|
+
"dialog.select.page_down": keybind("pagedown", "Move down one page in dialog"),
|
|
188
|
+
"dialog.select.home": keybind("home", "Move to first dialog item"),
|
|
189
|
+
"dialog.select.end": keybind("end", "Move to last dialog item"),
|
|
190
|
+
"dialog.select.submit": keybind("return", "Submit selected dialog item"),
|
|
191
|
+
"dialog.mcp.toggle": keybind("space", "Toggle MCP in MCP dialog"),
|
|
192
|
+
"prompt.autocomplete.prev": keybind("up,ctrl+p", "Move to previous autocomplete item"),
|
|
193
|
+
"prompt.autocomplete.next": keybind("down,ctrl+n", "Move to next autocomplete item"),
|
|
194
|
+
"prompt.autocomplete.hide": keybind("escape", "Hide autocomplete"),
|
|
195
|
+
"prompt.autocomplete.select": keybind("return", "Select autocomplete item"),
|
|
196
|
+
"prompt.autocomplete.complete": keybind("tab", "Complete autocomplete item"),
|
|
197
|
+
"permission.prompt.fullscreen": keybind("ctrl+f", "Toggle permission prompt fullscreen"),
|
|
198
|
+
"plugins.toggle": keybind("space", "Toggle plugin"),
|
|
199
|
+
"dialog.plugins.install": keybind("shift+i", "Install plugin from plugin dialog"),
|
|
200
|
+
|
|
201
|
+
terminal_suspend: keybind("ctrl+z", "Suspend terminal"),
|
|
202
|
+
terminal_title_toggle: keybind("none", "Toggle terminal title"),
|
|
203
|
+
tips_toggle: keybind("<leader>h", "Toggle tips on home screen"),
|
|
204
|
+
plugin_manager: keybind("none", "Open plugin manager dialog"),
|
|
205
|
+
plugin_install: keybind("none", "Install plugin"),
|
|
206
|
+
|
|
207
|
+
which_key_toggle: keybind("ctrl+alt+k", "Toggle which-key panel"),
|
|
208
|
+
which_key_layout_toggle: keybind("ctrl+alt+shift+k", "Switch which-key layout"),
|
|
209
|
+
which_key_pending_toggle: keybind("ctrl+alt+shift+p", "Toggle which-key pending preview"),
|
|
210
|
+
which_key_group_previous: keybind("ctrl+alt+left,ctrl+alt+[", "Previous which-key group"),
|
|
211
|
+
which_key_group_next: keybind("ctrl+alt+right,ctrl+alt+]", "Next which-key group"),
|
|
212
|
+
which_key_scroll_up: keybind("ctrl+alt+up,ctrl+alt+p", "Scroll which-key up"),
|
|
213
|
+
which_key_scroll_down: keybind("ctrl+alt+down,ctrl+alt+n", "Scroll which-key down"),
|
|
214
|
+
which_key_page_up: keybind("ctrl+alt+pageup", "Page which-key up"),
|
|
215
|
+
which_key_page_down: keybind("ctrl+alt+pagedown", "Page which-key down"),
|
|
216
|
+
which_key_home: keybind("ctrl+alt+home", "Jump to first which-key binding"),
|
|
217
|
+
which_key_end: keybind("ctrl+alt+end", "Jump to last which-key binding"),
|
|
218
|
+
} satisfies Record<string, Definition>
|
|
219
|
+
|
|
220
|
+
type KeybindName = keyof typeof Definitions
|
|
221
|
+
const KeybindNames = new Set<string>(Object.keys(Definitions))
|
|
222
|
+
|
|
223
|
+
export const KeybindOverrides = Schema.Struct(
|
|
224
|
+
Object.fromEntries(
|
|
225
|
+
Object.entries(Definitions).map(([name, item]) => [
|
|
226
|
+
name,
|
|
227
|
+
Schema.optional(BindingValueSchema).annotations({ description: item.description }),
|
|
228
|
+
]),
|
|
229
|
+
),
|
|
230
|
+
).annotations({ description: "TUI keybinding overrides" })
|
|
231
|
+
export const Descriptions = Object.fromEntries(
|
|
232
|
+
Object.entries(Definitions).map(([name, item]) => [name, item.description]),
|
|
233
|
+
) as Record<KeybindName, string>
|
|
234
|
+
export const CommandMap = {
|
|
235
|
+
app_exit: "app.exit",
|
|
236
|
+
app_debug: "app.debug",
|
|
237
|
+
app_console: "app.console",
|
|
238
|
+
app_heap_snapshot: "app.heap_snapshot",
|
|
239
|
+
app_toggle_animations: "app.toggle.animations",
|
|
240
|
+
app_toggle_file_context: "app.toggle.file_context",
|
|
241
|
+
app_toggle_diffwrap: "app.toggle.diffwrap",
|
|
242
|
+
app_toggle_paste_summary: "app.toggle.paste_summary",
|
|
243
|
+
app_toggle_session_directory_filter: "app.toggle.session_directory_filter",
|
|
244
|
+
command_list: "command.palette.show",
|
|
245
|
+
help_show: "help.show",
|
|
246
|
+
docs_open: "docs.open",
|
|
247
|
+
editor_open: "prompt.editor",
|
|
248
|
+
theme_list: "theme.switch",
|
|
249
|
+
theme_switch_mode: "theme.switch_mode",
|
|
250
|
+
theme_mode_lock: "theme.mode.lock",
|
|
251
|
+
sidebar_toggle: "session.sidebar.toggle",
|
|
252
|
+
scrollbar_toggle: "session.toggle.scrollbar",
|
|
253
|
+
status_view: "opencode.status",
|
|
254
|
+
session_export: "session.export",
|
|
255
|
+
session_copy: "session.copy",
|
|
256
|
+
session_new: "session.new",
|
|
257
|
+
session_list: "session.list",
|
|
258
|
+
session_timeline: "session.timeline",
|
|
259
|
+
session_fork: "session.fork",
|
|
260
|
+
session_rename: "session.rename",
|
|
261
|
+
session_delete: "session.delete",
|
|
262
|
+
session_share: "session.share",
|
|
263
|
+
session_unshare: "session.unshare",
|
|
264
|
+
session_interrupt: "session.interrupt",
|
|
265
|
+
session_compact: "session.compact",
|
|
266
|
+
session_toggle_timestamps: "session.toggle.timestamps",
|
|
267
|
+
session_toggle_generic_tool_output: "session.toggle.generic_tool_output",
|
|
268
|
+
session_child_first: "session.child.first",
|
|
269
|
+
session_child_cycle: "session.child.next",
|
|
270
|
+
session_child_cycle_reverse: "session.child.previous",
|
|
271
|
+
session_parent: "session.parent",
|
|
272
|
+
session_pin_toggle: "session.pin.toggle",
|
|
273
|
+
session_toggle_recent: "session.toggle.recent",
|
|
274
|
+
session_cycle_recent: "session.cycle_recent",
|
|
275
|
+
session_cycle_recent_reverse: "session.cycle_recent_reverse",
|
|
276
|
+
session_quick_switch_1: "session.quick_switch.1",
|
|
277
|
+
session_quick_switch_2: "session.quick_switch.2",
|
|
278
|
+
session_quick_switch_3: "session.quick_switch.3",
|
|
279
|
+
session_quick_switch_4: "session.quick_switch.4",
|
|
280
|
+
session_quick_switch_5: "session.quick_switch.5",
|
|
281
|
+
session_quick_switch_6: "session.quick_switch.6",
|
|
282
|
+
session_quick_switch_7: "session.quick_switch.7",
|
|
283
|
+
session_quick_switch_8: "session.quick_switch.8",
|
|
284
|
+
session_quick_switch_9: "session.quick_switch.9",
|
|
285
|
+
stash_delete: "stash.delete",
|
|
286
|
+
model_provider_list: "model.dialog.provider",
|
|
287
|
+
model_favorite_toggle: "model.dialog.favorite",
|
|
288
|
+
model_list: "model.list",
|
|
289
|
+
model_cycle_recent: "model.cycle_recent",
|
|
290
|
+
model_cycle_recent_reverse: "model.cycle_recent_reverse",
|
|
291
|
+
model_cycle_favorite: "model.cycle_favorite",
|
|
292
|
+
model_cycle_favorite_reverse: "model.cycle_favorite_reverse",
|
|
293
|
+
mcp_list: "mcp.list",
|
|
294
|
+
provider_connect: "provider.connect",
|
|
295
|
+
console_org_switch: "console.org.switch",
|
|
296
|
+
agent_list: "agent.list",
|
|
297
|
+
agent_cycle: "agent.cycle",
|
|
298
|
+
agent_cycle_reverse: "agent.cycle.reverse",
|
|
299
|
+
variant_cycle: "variant.cycle",
|
|
300
|
+
variant_list: "variant.list",
|
|
301
|
+
messages_page_up: "session.page.up",
|
|
302
|
+
messages_page_down: "session.page.down",
|
|
303
|
+
messages_line_up: "session.line.up",
|
|
304
|
+
messages_line_down: "session.line.down",
|
|
305
|
+
messages_half_page_up: "session.half.page.up",
|
|
306
|
+
messages_half_page_down: "session.half.page.down",
|
|
307
|
+
messages_first: "session.first",
|
|
308
|
+
messages_last: "session.last",
|
|
309
|
+
messages_next: "session.message.next",
|
|
310
|
+
messages_previous: "session.message.previous",
|
|
311
|
+
messages_last_user: "session.messages_last_user",
|
|
312
|
+
messages_copy: "messages.copy",
|
|
313
|
+
messages_undo: "session.undo",
|
|
314
|
+
messages_redo: "session.redo",
|
|
315
|
+
messages_toggle_conceal: "session.toggle.conceal",
|
|
316
|
+
tool_details: "session.toggle.actions",
|
|
317
|
+
display_thinking: "session.toggle.thinking",
|
|
318
|
+
prompt_submit: "prompt.submit",
|
|
319
|
+
prompt_editor_context_clear: "prompt.editor_context.clear",
|
|
320
|
+
prompt_skills: "prompt.skills",
|
|
321
|
+
prompt_stash: "prompt.stash",
|
|
322
|
+
prompt_stash_pop: "prompt.stash.pop",
|
|
323
|
+
prompt_stash_list: "prompt.stash.list",
|
|
324
|
+
workspace_set: "workspace.set",
|
|
325
|
+
input_clear: "prompt.clear",
|
|
326
|
+
input_paste: "prompt.paste",
|
|
327
|
+
input_submit: "input.submit",
|
|
328
|
+
input_newline: "input.newline",
|
|
329
|
+
input_move_left: "input.move.left",
|
|
330
|
+
input_move_right: "input.move.right",
|
|
331
|
+
input_move_up: "input.move.up",
|
|
332
|
+
input_move_down: "input.move.down",
|
|
333
|
+
input_select_left: "input.select.left",
|
|
334
|
+
input_select_right: "input.select.right",
|
|
335
|
+
input_select_up: "input.select.up",
|
|
336
|
+
input_select_down: "input.select.down",
|
|
337
|
+
input_line_home: "input.line.home",
|
|
338
|
+
input_line_end: "input.line.end",
|
|
339
|
+
input_select_line_home: "input.select.line.home",
|
|
340
|
+
input_select_line_end: "input.select.line.end",
|
|
341
|
+
input_visual_line_home: "input.visual.line.home",
|
|
342
|
+
input_visual_line_end: "input.visual.line.end",
|
|
343
|
+
input_select_visual_line_home: "input.select.visual.line.home",
|
|
344
|
+
input_select_visual_line_end: "input.select.visual.line.end",
|
|
345
|
+
input_buffer_home: "input.buffer.home",
|
|
346
|
+
input_buffer_end: "input.buffer.end",
|
|
347
|
+
input_select_buffer_home: "input.select.buffer.home",
|
|
348
|
+
input_select_buffer_end: "input.select.buffer.end",
|
|
349
|
+
input_delete_line: "input.delete.line",
|
|
350
|
+
input_delete_to_line_end: "input.delete.to.line.end",
|
|
351
|
+
input_delete_to_line_start: "input.delete.to.line.start",
|
|
352
|
+
input_backspace: "input.backspace",
|
|
353
|
+
input_delete: "input.delete",
|
|
354
|
+
input_undo: "input.undo",
|
|
355
|
+
input_redo: "input.redo",
|
|
356
|
+
input_word_forward: "input.word.forward",
|
|
357
|
+
input_word_backward: "input.word.backward",
|
|
358
|
+
input_select_word_forward: "input.select.word.forward",
|
|
359
|
+
input_select_word_backward: "input.select.word.backward",
|
|
360
|
+
input_delete_word_forward: "input.delete.word.forward",
|
|
361
|
+
input_delete_word_backward: "input.delete.word.backward",
|
|
362
|
+
input_select_all: "input.select.all",
|
|
363
|
+
history_previous: "prompt.history.previous",
|
|
364
|
+
history_next: "prompt.history.next",
|
|
365
|
+
terminal_suspend: "terminal.suspend",
|
|
366
|
+
terminal_title_toggle: "terminal.title.toggle",
|
|
367
|
+
tips_toggle: "tips.toggle",
|
|
368
|
+
plugin_manager: "plugins.list",
|
|
369
|
+
plugin_install: "plugins.install",
|
|
370
|
+
which_key_toggle: "which-key.toggle",
|
|
371
|
+
which_key_layout_toggle: "which-key.layout.toggle",
|
|
372
|
+
which_key_pending_toggle: "which-key.pending.toggle",
|
|
373
|
+
which_key_group_previous: "which-key.group.previous",
|
|
374
|
+
which_key_group_next: "which-key.group.next",
|
|
375
|
+
which_key_scroll_up: "which-key.scroll.up",
|
|
376
|
+
which_key_scroll_down: "which-key.scroll.down",
|
|
377
|
+
which_key_page_up: "which-key.page.up",
|
|
378
|
+
which_key_page_down: "which-key.page.down",
|
|
379
|
+
which_key_home: "which-key.home",
|
|
380
|
+
which_key_end: "which-key.end",
|
|
381
|
+
} satisfies BindingCommandMap
|
|
382
|
+
const CommandDescriptions = Object.fromEntries(
|
|
383
|
+
Object.entries(Definitions).map(([name, item]) => [
|
|
384
|
+
CommandMap[name as keyof typeof CommandMap] ?? name,
|
|
385
|
+
item.description,
|
|
386
|
+
]),
|
|
387
|
+
) as Record<string, string>
|
|
388
|
+
|
|
389
|
+
export type Keybinds = { [K in KeybindName]: BindingValueSchema }
|
|
390
|
+
export type KeybindOverrides = Partial<Keybinds>
|
|
391
|
+
export type BindingLookupView = {
|
|
392
|
+
readonly bindings: readonly Binding<Renderable, KeyEvent>[]
|
|
393
|
+
get(command: string): readonly Binding<Renderable, KeyEvent>[]
|
|
394
|
+
has(command: string): boolean
|
|
395
|
+
gather(name: string, commands: readonly string[]): readonly Binding<Renderable, KeyEvent>[]
|
|
396
|
+
pick(name: string, commands: readonly string[]): Binding<Renderable, KeyEvent>[]
|
|
397
|
+
omit(name: string, commands: readonly string[]): Binding<Renderable, KeyEvent>[]
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function toBindingConfig(keybinds: Keybinds): BindingConfig<Renderable, KeyEvent> {
|
|
401
|
+
return Object.fromEntries(Object.entries(keybinds)) as BindingConfig<Renderable, KeyEvent>
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const decodeBindingValue = Schema.decodeUnknownSync(BindingValueSchema)
|
|
405
|
+
|
|
406
|
+
export function defaultValue(name: KeybindName) {
|
|
407
|
+
return Definitions[name].default
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function parse(keybinds: KeybindOverrides): Keybinds {
|
|
411
|
+
const invalid = unknownKeys(keybinds)
|
|
412
|
+
if (invalid.length) throw new Error(`Unrecognized keybind${invalid.length === 1 ? "" : "s"}: ${invalid.join(", ")}`)
|
|
413
|
+
return Object.fromEntries(
|
|
414
|
+
Object.entries(Definitions).map(([name, item]) => [
|
|
415
|
+
name,
|
|
416
|
+
decodeBindingValue(keybinds[name as KeybindName] ?? item.default),
|
|
417
|
+
]),
|
|
418
|
+
) as Keybinds
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export const Keybinds = { parse }
|
|
422
|
+
|
|
423
|
+
export function unknownKeys(input: object) {
|
|
424
|
+
return Object.keys(input).filter((key) => !KeybindNames.has(key))
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function bindingDefaults(): BindingDefaults<Renderable, KeyEvent> {
|
|
428
|
+
return ({ command, binding }) => {
|
|
429
|
+
if (binding.desc !== undefined) return
|
|
430
|
+
return { desc: CommandDescriptions[command] }
|
|
431
|
+
}
|
|
432
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import { type ParseError as JsoncParseError, applyEdits, modify, parse as parseJsonc } from "jsonc-parser"
|
|
3
|
+
import { unique } from "remeda"
|
|
4
|
+
import { Option, Schema } from "effect"
|
|
5
|
+
import { DiffStyle, ScrollAcceleration, ScrollSpeed } from "./tui-schema"
|
|
6
|
+
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
7
|
+
import { Global } from "@opencode-ai/core/global"
|
|
8
|
+
import { Filesystem } from "@/util/filesystem"
|
|
9
|
+
import * as Log from "@opencode-ai/core/util/log"
|
|
10
|
+
import * as ConfigPaths from "@/config/paths"
|
|
11
|
+
|
|
12
|
+
const log = Log.create({ service: "tui.migrate" })
|
|
13
|
+
|
|
14
|
+
const TUI_SCHEMA_URL = "https://opencode.ai/tui.json"
|
|
15
|
+
|
|
16
|
+
const decodeTheme = Schema.decodeUnknownOption(Schema.String)
|
|
17
|
+
const decodeRecord = Schema.decodeUnknownOption(Schema.Record({ key: Schema.String, value: Schema.Unknown }))
|
|
18
|
+
const decodeScrollSpeed = Schema.decodeUnknownOption(ScrollSpeed)
|
|
19
|
+
const decodeScrollAcceleration = Schema.decodeUnknownOption(ScrollAcceleration)
|
|
20
|
+
const decodeDiffStyle = Schema.decodeUnknownOption(DiffStyle)
|
|
21
|
+
|
|
22
|
+
interface MigrateInput {
|
|
23
|
+
cwd: string
|
|
24
|
+
directories: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Migrates tui-specific keys (theme, keybinds, tui) from opencode.json files
|
|
29
|
+
* into dedicated tui.json files. Migration is performed per-directory and
|
|
30
|
+
* skips only locations where a tui.json already exists.
|
|
31
|
+
*/
|
|
32
|
+
export async function migrateTuiConfig(input: MigrateInput) {
|
|
33
|
+
const opencode = await opencodeFiles(input)
|
|
34
|
+
for (const file of opencode) {
|
|
35
|
+
const source = await Filesystem.readText(file).catch((error) => {
|
|
36
|
+
log.warn("failed to read config for tui migration", { path: file, error })
|
|
37
|
+
return undefined
|
|
38
|
+
})
|
|
39
|
+
if (!source) continue
|
|
40
|
+
const errors: JsoncParseError[] = []
|
|
41
|
+
const data = parseJsonc(source, errors, { allowTrailingComma: true })
|
|
42
|
+
if (errors.length || !data || typeof data !== "object" || Array.isArray(data)) continue
|
|
43
|
+
|
|
44
|
+
const theme = decodeTheme("theme" in data ? data.theme : undefined)
|
|
45
|
+
const keybinds = decodeRecord("keybinds" in data ? data.keybinds : undefined)
|
|
46
|
+
const legacyTui = decodeRecord("tui" in data ? data.tui : undefined)
|
|
47
|
+
const extracted = {
|
|
48
|
+
theme: Option.getOrUndefined(theme),
|
|
49
|
+
keybinds: Option.getOrUndefined(keybinds),
|
|
50
|
+
tui: Option.getOrUndefined(legacyTui),
|
|
51
|
+
}
|
|
52
|
+
const tui = extracted.tui ? normalizeTui(extracted.tui) : undefined
|
|
53
|
+
if (extracted.theme === undefined && extracted.keybinds === undefined && !tui) continue
|
|
54
|
+
|
|
55
|
+
const target = path.join(path.dirname(file), "tui.json")
|
|
56
|
+
const targetExists = await Filesystem.exists(target)
|
|
57
|
+
if (targetExists) continue
|
|
58
|
+
|
|
59
|
+
const payload: Record<string, unknown> = {
|
|
60
|
+
$schema: TUI_SCHEMA_URL,
|
|
61
|
+
}
|
|
62
|
+
if (extracted.theme !== undefined) payload.theme = extracted.theme
|
|
63
|
+
if (extracted.keybinds !== undefined) payload.keybinds = extracted.keybinds
|
|
64
|
+
if (tui) Object.assign(payload, tui)
|
|
65
|
+
|
|
66
|
+
const wrote = await Filesystem.write(target, JSON.stringify(payload, null, 2))
|
|
67
|
+
.then(() => true)
|
|
68
|
+
.catch((error) => {
|
|
69
|
+
log.warn("failed to write tui migration target", { from: file, to: target, error })
|
|
70
|
+
return false
|
|
71
|
+
})
|
|
72
|
+
if (!wrote) continue
|
|
73
|
+
|
|
74
|
+
const stripped = await backupAndStripLegacy(file, source)
|
|
75
|
+
if (!stripped) {
|
|
76
|
+
log.warn("tui config migrated but source file was not stripped", { from: file, to: target })
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
log.info("migrated tui config", { from: file, to: target })
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeTui(data: Record<string, unknown>):
|
|
84
|
+
| {
|
|
85
|
+
scroll_speed: number | undefined
|
|
86
|
+
scroll_acceleration: { enabled: boolean } | undefined
|
|
87
|
+
diff_style: "auto" | "stacked" | undefined
|
|
88
|
+
}
|
|
89
|
+
| undefined {
|
|
90
|
+
const parsed = {
|
|
91
|
+
scroll_speed: Option.getOrUndefined(decodeScrollSpeed(data.scroll_speed)),
|
|
92
|
+
scroll_acceleration: Option.getOrUndefined(decodeScrollAcceleration(data.scroll_acceleration)),
|
|
93
|
+
diff_style: Option.getOrUndefined(decodeDiffStyle(data.diff_style)),
|
|
94
|
+
}
|
|
95
|
+
return parsed.scroll_speed === undefined &&
|
|
96
|
+
parsed.diff_style === undefined &&
|
|
97
|
+
parsed.scroll_acceleration === undefined
|
|
98
|
+
? undefined
|
|
99
|
+
: parsed
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function backupAndStripLegacy(file: string, source: string) {
|
|
103
|
+
const backup = file + ".tui-migration.bak"
|
|
104
|
+
const hasBackup = await Filesystem.exists(backup)
|
|
105
|
+
const backed = hasBackup
|
|
106
|
+
? true
|
|
107
|
+
: await Filesystem.write(backup, source)
|
|
108
|
+
.then(() => true)
|
|
109
|
+
.catch((error) => {
|
|
110
|
+
log.warn("failed to backup source config during tui migration", { path: file, backup, error })
|
|
111
|
+
return false
|
|
112
|
+
})
|
|
113
|
+
if (!backed) return false
|
|
114
|
+
|
|
115
|
+
const text = ["theme", "keybinds", "tui"].reduce((acc, key) => {
|
|
116
|
+
const edits = modify(acc, [key], undefined, {
|
|
117
|
+
formattingOptions: {
|
|
118
|
+
insertSpaces: true,
|
|
119
|
+
tabSize: 2,
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
if (!edits.length) return acc
|
|
123
|
+
return applyEdits(acc, edits)
|
|
124
|
+
}, source)
|
|
125
|
+
|
|
126
|
+
return Filesystem.write(file, text)
|
|
127
|
+
.then(() => {
|
|
128
|
+
log.info("stripped tui keys from server config", { path: file, backup })
|
|
129
|
+
return true
|
|
130
|
+
})
|
|
131
|
+
.catch((error) => {
|
|
132
|
+
log.warn("failed to strip legacy tui keys from server config", { path: file, backup, error })
|
|
133
|
+
return false
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function opencodeFiles(input: { directories: string[]; cwd: string }) {
|
|
138
|
+
const files = [
|
|
139
|
+
...ConfigPaths.fileInDirectory(Global.Path.config, "opencode"),
|
|
140
|
+
...(await Filesystem.findUp(["opencode.json", "opencode.jsonc"], input.cwd, undefined, { rootFirst: true })),
|
|
141
|
+
]
|
|
142
|
+
for (const dir of unique(input.directories)) {
|
|
143
|
+
files.push(...ConfigPaths.fileInDirectory(dir, "opencode"))
|
|
144
|
+
}
|
|
145
|
+
if (Flag.OPENCODE_CONFIG) files.push(Flag.OPENCODE_CONFIG)
|
|
146
|
+
|
|
147
|
+
const existing = await Promise.all(
|
|
148
|
+
unique(files).map(async (file) => {
|
|
149
|
+
const ok = await Filesystem.exists(file)
|
|
150
|
+
return ok ? file : undefined
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
153
|
+
return existing.filter((file): file is string => !!file)
|
|
154
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TuiKeybind } from "./keybind"
|
|
2
|
+
import { Schema } from "effect"
|
|
3
|
+
|
|
4
|
+
export const KeymapLeaderTimeoutDefault = 2000
|
|
5
|
+
const KeymapLeaderTimeout = Schema.Int.pipe(
|
|
6
|
+
Schema.filter((n) => n > 0),
|
|
7
|
+
).annotations({ description: "Leader key timeout in milliseconds" })
|
|
8
|
+
|
|
9
|
+
export const ScrollSpeed = Schema.Number.pipe(
|
|
10
|
+
Schema.filter((n) => n >= 0.001),
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export const ScrollAcceleration = Schema.Struct({
|
|
14
|
+
enabled: Schema.Boolean.annotations({ description: "Enable scroll acceleration" }),
|
|
15
|
+
}).annotations({ description: "Scroll acceleration settings" })
|
|
16
|
+
|
|
17
|
+
export const DiffStyle = Schema.Union(Schema.Literal("auto"), Schema.Literal("stacked")).annotations({
|
|
18
|
+
description: "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column",
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const TuiInfo = Schema.Struct({
|
|
22
|
+
$schema: Schema.optional(Schema.String),
|
|
23
|
+
theme: Schema.optional(Schema.String),
|
|
24
|
+
keybinds: Schema.optional(TuiKeybind.KeybindOverrides),
|
|
25
|
+
plugin: Schema.optional(Schema.Array(Schema.Unknown)),
|
|
26
|
+
plugin_enabled: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.Boolean })),
|
|
27
|
+
leader_timeout: Schema.optional(KeymapLeaderTimeout),
|
|
28
|
+
scroll_speed: Schema.optional(ScrollSpeed).annotations({
|
|
29
|
+
description: "TUI scroll speed",
|
|
30
|
+
}),
|
|
31
|
+
scroll_acceleration: Schema.optional(ScrollAcceleration),
|
|
32
|
+
diff_style: Schema.optional(DiffStyle),
|
|
33
|
+
mouse: Schema.optional(Schema.Boolean).annotations({ description: "Enable or disable mouse capture (default: true)" }),
|
|
34
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export * as TuiConfig from "./tui"
|
|
2
|
+
|
|
3
|
+
import { createBindingLookup } from "@opentui/keymap/extras"
|
|
4
|
+
import { KeymapLeaderTimeoutDefault } from "./tui-schema"
|
|
5
|
+
import { TuiKeybind } from "./keybind"
|
|
6
|
+
|
|
7
|
+
export type Info = {
|
|
8
|
+
theme?: string
|
|
9
|
+
keybinds?: TuiKeybind.KeybindOverrides
|
|
10
|
+
plugin?: unknown[]
|
|
11
|
+
plugin_enabled?: Record<string, boolean>
|
|
12
|
+
leader_timeout?: number
|
|
13
|
+
scroll_speed?: number
|
|
14
|
+
scroll_acceleration?: { enabled: boolean }
|
|
15
|
+
diff_style?: "auto" | "stacked"
|
|
16
|
+
mouse?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Resolved = Omit<Info, "keybinds" | "leader_timeout"> & {
|
|
20
|
+
keybinds: TuiKeybind.BindingLookupView
|
|
21
|
+
leader_timeout: number
|
|
22
|
+
plugin_origins?: unknown[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildResolved(info: Info): Resolved {
|
|
26
|
+
const keybinds = TuiKeybind.parse({ ...(info.keybinds ?? {}) })
|
|
27
|
+
return {
|
|
28
|
+
...info,
|
|
29
|
+
keybinds: createBindingLookup(TuiKeybind.toBindingConfig(keybinds), {
|
|
30
|
+
commandMap: TuiKeybind.CommandMap,
|
|
31
|
+
bindingDefaults: TuiKeybind.bindingDefaults(),
|
|
32
|
+
}),
|
|
33
|
+
leader_timeout: info.leader_timeout ?? KeymapLeaderTimeoutDefault,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const _default: Resolved = buildResolved({})
|
|
38
|
+
|
|
39
|
+
/** Returns resolved TUI config. In APX mode this always returns the default config. */
|
|
40
|
+
export async function get(): Promise<Resolved> {
|
|
41
|
+
return _default
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function waitForDependencies(): Promise<void> {
|
|
45
|
+
// no-op in APX mode
|
|
46
|
+
}
|