@agentprojectcontext/apx 1.15.6 → 1.16.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 +40 -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.js +102 -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,166 @@
|
|
|
1
|
+
import { type CliRenderer } from "@opentui/core"
|
|
2
|
+
import * as addons from "@opentui/keymap/addons/opentui"
|
|
3
|
+
import { stringifyKeyStroke } from "@opentui/keymap"
|
|
4
|
+
import {
|
|
5
|
+
formatCommandBindings as formatCommandBindingsExtra,
|
|
6
|
+
formatKeySequence as formatKeySequenceExtra,
|
|
7
|
+
} from "@opentui/keymap/extras"
|
|
8
|
+
import {
|
|
9
|
+
KeymapProvider,
|
|
10
|
+
reactiveMatcherFromSignal,
|
|
11
|
+
useKeymap,
|
|
12
|
+
useKeymapSelector,
|
|
13
|
+
useBindings,
|
|
14
|
+
} from "@opentui/keymap/solid"
|
|
15
|
+
import type { Accessor } from "solid-js"
|
|
16
|
+
import type { TuiConfig } from "./config/tui"
|
|
17
|
+
import { useTuiConfig } from "./context/tui-config"
|
|
18
|
+
import { TuiKeybind } from "./config/keybind"
|
|
19
|
+
|
|
20
|
+
export const LEADER_TOKEN = "leader"
|
|
21
|
+
|
|
22
|
+
export const OpencodeKeymapProvider = KeymapProvider
|
|
23
|
+
export const useOpencodeKeymap = useKeymap
|
|
24
|
+
|
|
25
|
+
export { reactiveMatcherFromSignal, useBindings, useKeymapSelector }
|
|
26
|
+
|
|
27
|
+
export type OpenTuiKeymap = ReturnType<typeof useKeymap>
|
|
28
|
+
|
|
29
|
+
const KEY_ALIASES = {
|
|
30
|
+
enter: "return",
|
|
31
|
+
esc: "escape",
|
|
32
|
+
} as const
|
|
33
|
+
|
|
34
|
+
function expandKeyAliases(input: string) {
|
|
35
|
+
const result = Object.entries(KEY_ALIASES).reduce(
|
|
36
|
+
(acc, [alias, key]) => acc.replace(new RegExp(`(^|[+,\\s>])${alias}(?=$|[+,\\s<])`, "gi"), `$1${key}`),
|
|
37
|
+
input,
|
|
38
|
+
)
|
|
39
|
+
if (result === input) return
|
|
40
|
+
return result
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function registerKeyAliases(keymap: OpenTuiKeymap) {
|
|
44
|
+
return keymap.appendBindingExpander((ctx) => {
|
|
45
|
+
const key = expandKeyAliases(ctx.input)
|
|
46
|
+
if (!key) return
|
|
47
|
+
return [{ key, displays: ctx.displays }]
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const inputCommands = [
|
|
52
|
+
"input.move.left",
|
|
53
|
+
"input.move.right",
|
|
54
|
+
"input.move.up",
|
|
55
|
+
"input.move.down",
|
|
56
|
+
"input.select.left",
|
|
57
|
+
"input.select.right",
|
|
58
|
+
"input.select.up",
|
|
59
|
+
"input.select.down",
|
|
60
|
+
"input.line.home",
|
|
61
|
+
"input.line.end",
|
|
62
|
+
"input.select.line.home",
|
|
63
|
+
"input.select.line.end",
|
|
64
|
+
"input.visual.line.home",
|
|
65
|
+
"input.visual.line.end",
|
|
66
|
+
"input.select.visual.line.home",
|
|
67
|
+
"input.select.visual.line.end",
|
|
68
|
+
"input.buffer.home",
|
|
69
|
+
"input.buffer.end",
|
|
70
|
+
"input.select.buffer.home",
|
|
71
|
+
"input.select.buffer.end",
|
|
72
|
+
"input.delete.line",
|
|
73
|
+
"input.delete.to.line.end",
|
|
74
|
+
"input.delete.to.line.start",
|
|
75
|
+
"input.backspace",
|
|
76
|
+
"input.delete",
|
|
77
|
+
"input.newline",
|
|
78
|
+
"input.undo",
|
|
79
|
+
"input.redo",
|
|
80
|
+
"input.word.forward",
|
|
81
|
+
"input.word.backward",
|
|
82
|
+
"input.select.word.forward",
|
|
83
|
+
"input.select.word.backward",
|
|
84
|
+
"input.delete.word.forward",
|
|
85
|
+
"input.delete.word.backward",
|
|
86
|
+
"input.select.all",
|
|
87
|
+
"input.submit",
|
|
88
|
+
] as const
|
|
89
|
+
|
|
90
|
+
function leaderDisplay(config: TuiConfig.Resolved) {
|
|
91
|
+
const key = config.keybinds.get(LEADER_TOKEN)?.[0]?.key
|
|
92
|
+
if (!key) return TuiKeybind.LeaderDefault
|
|
93
|
+
return typeof key === "string" ? key : stringifyKeyStroke(key)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function formatOptions(config: TuiConfig.Resolved) {
|
|
97
|
+
return {
|
|
98
|
+
tokenDisplay: {
|
|
99
|
+
[LEADER_TOKEN]: leaderDisplay(config),
|
|
100
|
+
},
|
|
101
|
+
keyNameAliases: {
|
|
102
|
+
pageup: "pgup",
|
|
103
|
+
pagedown: "pgdn",
|
|
104
|
+
delete: "del",
|
|
105
|
+
},
|
|
106
|
+
modifierAliases: {
|
|
107
|
+
meta: "alt",
|
|
108
|
+
},
|
|
109
|
+
} as const
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function formatKeySequence(parts: Parameters<typeof formatKeySequenceExtra>[0], config: TuiConfig.Resolved) {
|
|
113
|
+
return formatKeySequenceExtra(parts, formatOptions(config))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function formatKeyBindings(
|
|
117
|
+
bindings: Parameters<typeof formatCommandBindingsExtra>[0],
|
|
118
|
+
config: TuiConfig.Resolved,
|
|
119
|
+
) {
|
|
120
|
+
return formatCommandBindingsExtra(bindings, formatOptions(config))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function registerOpencodeKeymap(
|
|
124
|
+
keymap: OpenTuiKeymap,
|
|
125
|
+
renderer: CliRenderer,
|
|
126
|
+
config: Pick<TuiConfig.Resolved, "keybinds" | "leader_timeout">,
|
|
127
|
+
) {
|
|
128
|
+
const offCommaBindings = addons.registerCommaBindings(keymap)
|
|
129
|
+
const offAliasExpander = registerKeyAliases(keymap)
|
|
130
|
+
const offBaseLayout = addons.registerBaseLayoutFallback(keymap)
|
|
131
|
+
const offLeader = addons.registerTimedLeader(keymap, {
|
|
132
|
+
trigger: config.keybinds.get(LEADER_TOKEN),
|
|
133
|
+
name: LEADER_TOKEN,
|
|
134
|
+
timeoutMs: config.leader_timeout,
|
|
135
|
+
})
|
|
136
|
+
const offEscape = addons.registerEscapeClearsPendingSequence(keymap)
|
|
137
|
+
const offBackspace = addons.registerBackspacePopsPendingSequence(keymap)
|
|
138
|
+
const offInputBindings = addons.registerManagedTextareaLayer(keymap, renderer, {
|
|
139
|
+
enabled: () => renderer.currentFocusedEditor !== null,
|
|
140
|
+
bindings: config.keybinds.gather("input", inputCommands),
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
offInputBindings()
|
|
145
|
+
offBackspace()
|
|
146
|
+
offEscape()
|
|
147
|
+
offLeader()
|
|
148
|
+
offAliasExpander()
|
|
149
|
+
offBaseLayout()
|
|
150
|
+
offCommaBindings()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function useCommandShortcut(command: string): Accessor<string> {
|
|
155
|
+
const config = useTuiConfig()
|
|
156
|
+
return useKeymapSelector((keymap) =>
|
|
157
|
+
formatKeySequence(
|
|
158
|
+
keymap.getCommandBindings({ visibility: "registered", commands: [command] }).get(command)?.[0]?.sequence,
|
|
159
|
+
config,
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function useLeaderActive(): Accessor<boolean> {
|
|
165
|
+
return useKeymapSelector((keymap: OpenTuiKeymap) => keymap.getPendingSequence()[0]?.tokenName === LEADER_TOKEN)
|
|
166
|
+
}
|
package/src/tui/layer.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Layer } from "effect"
|
|
2
|
+
import { TuiConfig } from "./config/tui"
|
|
3
|
+
import { Npm } from "@opencode-ai/core/npm"
|
|
4
|
+
import { Observability } from "@opencode-ai/core/effect/observability"
|
|
5
|
+
|
|
6
|
+
export const CliLayer = Observability.layer.pipe(Layer.merge(TuiConfig.layer), Layer.provide(Npm.defaultLayer))
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import type { TuiDialogSelectOption, TuiPluginApi, TuiRouteDefinition, TuiSlotProps } from "@opencode-ai/plugin/tui"
|
|
2
|
+
import type { useEvent } from "@tui/context/event"
|
|
3
|
+
import type { useRoute } from "@tui/context/route"
|
|
4
|
+
import type { useSDK } from "@tui/context/sdk"
|
|
5
|
+
import type { useSync } from "@tui/context/sync"
|
|
6
|
+
import type { useTheme } from "@tui/context/theme"
|
|
7
|
+
import { Dialog as DialogUI, type useDialog } from "@tui/ui/dialog"
|
|
8
|
+
import type { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
|
9
|
+
import type { useOpencodeKeymap } from "../keymap"
|
|
10
|
+
import type { useKV } from "../context/kv"
|
|
11
|
+
import { DialogAlert } from "../ui/dialog-alert"
|
|
12
|
+
import { DialogConfirm } from "../ui/dialog-confirm"
|
|
13
|
+
import { DialogPrompt } from "../ui/dialog-prompt"
|
|
14
|
+
import { DialogSelect, type DialogSelectOption as SelectOption } from "../ui/dialog-select"
|
|
15
|
+
import { Prompt } from "../component/prompt"
|
|
16
|
+
import { Slot as HostSlot } from "./slots"
|
|
17
|
+
import type { useToast } from "../ui/toast"
|
|
18
|
+
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
|
19
|
+
import * as Keymap from "../keymap"
|
|
20
|
+
import { createCommandShim } from "./command-shim"
|
|
21
|
+
|
|
22
|
+
type RouteEntry = {
|
|
23
|
+
key: symbol
|
|
24
|
+
render: TuiRouteDefinition["render"]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type RouteMap = Map<string, RouteEntry[]>
|
|
28
|
+
|
|
29
|
+
type Input = {
|
|
30
|
+
tuiConfig: TuiConfig.Resolved
|
|
31
|
+
dialog: ReturnType<typeof useDialog>
|
|
32
|
+
keymap: ReturnType<typeof useOpencodeKeymap>
|
|
33
|
+
kv: ReturnType<typeof useKV>
|
|
34
|
+
route: ReturnType<typeof useRoute>
|
|
35
|
+
routes: RouteMap
|
|
36
|
+
bump: () => void
|
|
37
|
+
event: ReturnType<typeof useEvent>
|
|
38
|
+
sdk: ReturnType<typeof useSDK>
|
|
39
|
+
sync: ReturnType<typeof useSync>
|
|
40
|
+
theme: ReturnType<typeof useTheme>
|
|
41
|
+
toast: ReturnType<typeof useToast>
|
|
42
|
+
renderer: TuiPluginApi["renderer"]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function routeRegister(routes: RouteMap, list: TuiRouteDefinition[], bump: () => void) {
|
|
46
|
+
const key = Symbol()
|
|
47
|
+
for (const item of list) {
|
|
48
|
+
const prev = routes.get(item.name) ?? []
|
|
49
|
+
prev.push({ key, render: item.render })
|
|
50
|
+
routes.set(item.name, prev)
|
|
51
|
+
}
|
|
52
|
+
bump()
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
for (const item of list) {
|
|
56
|
+
const prev = routes.get(item.name)
|
|
57
|
+
if (!prev) continue
|
|
58
|
+
const next = prev.filter((x) => x.key !== key)
|
|
59
|
+
if (!next.length) {
|
|
60
|
+
routes.delete(item.name)
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
routes.set(item.name, next)
|
|
64
|
+
}
|
|
65
|
+
bump()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function routeNavigate(route: ReturnType<typeof useRoute>, name: string, params?: Record<string, unknown>) {
|
|
70
|
+
if (name === "home") {
|
|
71
|
+
route.navigate({ type: "home" })
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (name === "session") {
|
|
76
|
+
const sessionID = params?.sessionID
|
|
77
|
+
if (typeof sessionID !== "string") return
|
|
78
|
+
route.navigate({ type: "session", sessionID })
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
route.navigate({ type: "plugin", id: name, data: params })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function routeCurrent(route: ReturnType<typeof useRoute>): TuiPluginApi["route"]["current"] {
|
|
86
|
+
if (route.data.type === "home") return { name: "home" }
|
|
87
|
+
if (route.data.type === "session") {
|
|
88
|
+
return {
|
|
89
|
+
name: "session",
|
|
90
|
+
params: {
|
|
91
|
+
sessionID: route.data.sessionID,
|
|
92
|
+
prompt: route.data.prompt,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
name: route.data.id,
|
|
99
|
+
params: route.data.data,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function mapOption<Value>(item: TuiDialogSelectOption<Value>): SelectOption<Value> {
|
|
104
|
+
return {
|
|
105
|
+
...item,
|
|
106
|
+
onSelect: () => item.onSelect?.(),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function pickOption<Value>(item: SelectOption<Value>): TuiDialogSelectOption<Value> {
|
|
111
|
+
return {
|
|
112
|
+
title: item.title,
|
|
113
|
+
value: item.value,
|
|
114
|
+
description: item.description,
|
|
115
|
+
footer: item.footer,
|
|
116
|
+
category: item.category,
|
|
117
|
+
disabled: item.disabled,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function mapOptionCb<Value>(cb?: (item: TuiDialogSelectOption<Value>) => void) {
|
|
122
|
+
if (!cb) return
|
|
123
|
+
return (item: SelectOption<Value>) => cb(pickOption(item))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function stateApi(sync: ReturnType<typeof useSync>): TuiPluginApi["state"] {
|
|
127
|
+
return {
|
|
128
|
+
get ready() {
|
|
129
|
+
return sync.ready
|
|
130
|
+
},
|
|
131
|
+
get config() {
|
|
132
|
+
return sync.data.config
|
|
133
|
+
},
|
|
134
|
+
get provider() {
|
|
135
|
+
return sync.data.provider
|
|
136
|
+
},
|
|
137
|
+
get path() {
|
|
138
|
+
return sync.path
|
|
139
|
+
},
|
|
140
|
+
get vcs() {
|
|
141
|
+
if (!sync.data.vcs) return
|
|
142
|
+
return {
|
|
143
|
+
branch: sync.data.vcs.branch,
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
session: {
|
|
147
|
+
count() {
|
|
148
|
+
return sync.data.session.length
|
|
149
|
+
},
|
|
150
|
+
get(sessionID) {
|
|
151
|
+
return sync.session.get(sessionID)
|
|
152
|
+
},
|
|
153
|
+
diff(sessionID) {
|
|
154
|
+
return (sync.data.session_diff[sessionID] ?? []).flatMap((item) =>
|
|
155
|
+
item.file === undefined ? [] : [{ ...item, file: item.file }],
|
|
156
|
+
)
|
|
157
|
+
},
|
|
158
|
+
todo(sessionID) {
|
|
159
|
+
return sync.data.todo[sessionID] ?? []
|
|
160
|
+
},
|
|
161
|
+
messages(sessionID) {
|
|
162
|
+
return sync.data.message[sessionID] ?? []
|
|
163
|
+
},
|
|
164
|
+
status(sessionID) {
|
|
165
|
+
return sync.data.session_status[sessionID]
|
|
166
|
+
},
|
|
167
|
+
permission(sessionID) {
|
|
168
|
+
return sync.data.permission[sessionID] ?? []
|
|
169
|
+
},
|
|
170
|
+
question(sessionID) {
|
|
171
|
+
return sync.data.question[sessionID] ?? []
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
part(messageID) {
|
|
175
|
+
return sync.data.part[messageID] ?? []
|
|
176
|
+
},
|
|
177
|
+
lsp() {
|
|
178
|
+
return sync.data.lsp.map((item) => ({ id: item.id, root: item.root, status: item.status }))
|
|
179
|
+
},
|
|
180
|
+
mcp() {
|
|
181
|
+
return Object.entries(sync.data.mcp)
|
|
182
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
183
|
+
.map(([name, item]) => ({
|
|
184
|
+
name,
|
|
185
|
+
status: item.status,
|
|
186
|
+
error: item.status === "failed" ? item.error : undefined,
|
|
187
|
+
}))
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function appApi(): TuiPluginApi["app"] {
|
|
193
|
+
return {
|
|
194
|
+
get version() {
|
|
195
|
+
return InstallationVersion
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function createTuiApi(input: Input): TuiPluginApi {
|
|
201
|
+
const lifecycle: TuiPluginApi["lifecycle"] = {
|
|
202
|
+
signal: new AbortController().signal,
|
|
203
|
+
onDispose() {
|
|
204
|
+
return () => {}
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
app: appApi(),
|
|
209
|
+
// Keep deprecated `api.command` working for v1 plugins; remove in v2.
|
|
210
|
+
command: createCommandShim(input.keymap, input.dialog, input.tuiConfig.keybinds),
|
|
211
|
+
keys: {
|
|
212
|
+
formatSequence(parts) {
|
|
213
|
+
return Keymap.formatKeySequence(parts, input.tuiConfig)
|
|
214
|
+
},
|
|
215
|
+
formatBindings(bindings) {
|
|
216
|
+
return Keymap.formatKeyBindings(bindings, input.tuiConfig)
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
keymap: input.keymap,
|
|
220
|
+
route: {
|
|
221
|
+
register(list) {
|
|
222
|
+
return routeRegister(input.routes, list, input.bump)
|
|
223
|
+
},
|
|
224
|
+
navigate(name, params) {
|
|
225
|
+
routeNavigate(input.route, name, params)
|
|
226
|
+
},
|
|
227
|
+
get current() {
|
|
228
|
+
return routeCurrent(input.route)
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
ui: {
|
|
232
|
+
Dialog(props) {
|
|
233
|
+
return (
|
|
234
|
+
<DialogUI size={props.size} onClose={props.onClose}>
|
|
235
|
+
{props.children}
|
|
236
|
+
</DialogUI>
|
|
237
|
+
)
|
|
238
|
+
},
|
|
239
|
+
DialogAlert(props) {
|
|
240
|
+
return <DialogAlert {...props} />
|
|
241
|
+
},
|
|
242
|
+
DialogConfirm(props) {
|
|
243
|
+
return <DialogConfirm {...props} />
|
|
244
|
+
},
|
|
245
|
+
DialogPrompt(props) {
|
|
246
|
+
return <DialogPrompt {...props} description={props.description} />
|
|
247
|
+
},
|
|
248
|
+
DialogSelect(props) {
|
|
249
|
+
return (
|
|
250
|
+
<DialogSelect
|
|
251
|
+
title={props.title}
|
|
252
|
+
placeholder={props.placeholder}
|
|
253
|
+
options={props.options.map(mapOption)}
|
|
254
|
+
flat={props.flat}
|
|
255
|
+
onMove={mapOptionCb(props.onMove)}
|
|
256
|
+
onFilter={props.onFilter}
|
|
257
|
+
onSelect={mapOptionCb(props.onSelect)}
|
|
258
|
+
skipFilter={props.skipFilter}
|
|
259
|
+
current={props.current}
|
|
260
|
+
/>
|
|
261
|
+
)
|
|
262
|
+
},
|
|
263
|
+
Slot<Name extends string>(props: TuiSlotProps<Name>) {
|
|
264
|
+
return <HostSlot {...props} />
|
|
265
|
+
},
|
|
266
|
+
Prompt(props) {
|
|
267
|
+
return (
|
|
268
|
+
<Prompt
|
|
269
|
+
sessionID={props.sessionID}
|
|
270
|
+
workspaceID={props.workspaceID}
|
|
271
|
+
visible={props.visible}
|
|
272
|
+
disabled={props.disabled}
|
|
273
|
+
onSubmit={props.onSubmit}
|
|
274
|
+
ref={props.ref}
|
|
275
|
+
hint={props.hint}
|
|
276
|
+
right={props.right}
|
|
277
|
+
showPlaceholder={props.showPlaceholder}
|
|
278
|
+
placeholders={props.placeholders}
|
|
279
|
+
/>
|
|
280
|
+
)
|
|
281
|
+
},
|
|
282
|
+
toast(inputToast) {
|
|
283
|
+
input.toast.show({
|
|
284
|
+
title: inputToast.title,
|
|
285
|
+
message: inputToast.message,
|
|
286
|
+
variant: inputToast.variant ?? "info",
|
|
287
|
+
duration: inputToast.duration,
|
|
288
|
+
})
|
|
289
|
+
},
|
|
290
|
+
dialog: {
|
|
291
|
+
replace(render, onClose) {
|
|
292
|
+
input.dialog.replace(render, onClose)
|
|
293
|
+
},
|
|
294
|
+
clear() {
|
|
295
|
+
input.dialog.clear()
|
|
296
|
+
},
|
|
297
|
+
setSize(size) {
|
|
298
|
+
input.dialog.setSize(size)
|
|
299
|
+
},
|
|
300
|
+
get size() {
|
|
301
|
+
return input.dialog.size
|
|
302
|
+
},
|
|
303
|
+
get depth() {
|
|
304
|
+
return input.dialog.stack.length
|
|
305
|
+
},
|
|
306
|
+
get open() {
|
|
307
|
+
return input.dialog.stack.length > 0
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
get tuiConfig() {
|
|
312
|
+
return input.tuiConfig
|
|
313
|
+
},
|
|
314
|
+
kv: {
|
|
315
|
+
get(key, fallback) {
|
|
316
|
+
return input.kv.get(key, fallback)
|
|
317
|
+
},
|
|
318
|
+
set(key, value) {
|
|
319
|
+
input.kv.set(key, value)
|
|
320
|
+
},
|
|
321
|
+
get ready() {
|
|
322
|
+
return input.kv.ready
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
state: stateApi(input.sync),
|
|
326
|
+
get client() {
|
|
327
|
+
return input.sdk.client
|
|
328
|
+
},
|
|
329
|
+
event: input.event,
|
|
330
|
+
renderer: input.renderer,
|
|
331
|
+
slots: {
|
|
332
|
+
register() {
|
|
333
|
+
throw new Error("slots.register is only available in plugin context")
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
plugins: {
|
|
337
|
+
list() {
|
|
338
|
+
return []
|
|
339
|
+
},
|
|
340
|
+
async activate() {
|
|
341
|
+
return false
|
|
342
|
+
},
|
|
343
|
+
async deactivate() {
|
|
344
|
+
return false
|
|
345
|
+
},
|
|
346
|
+
async add() {
|
|
347
|
+
return false
|
|
348
|
+
},
|
|
349
|
+
async install() {
|
|
350
|
+
return {
|
|
351
|
+
ok: false,
|
|
352
|
+
message: "plugins.install is only available in plugin context",
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
lifecycle,
|
|
357
|
+
theme: {
|
|
358
|
+
get current() {
|
|
359
|
+
return input.theme.theme
|
|
360
|
+
},
|
|
361
|
+
get selected() {
|
|
362
|
+
return input.theme.selected
|
|
363
|
+
},
|
|
364
|
+
has(name) {
|
|
365
|
+
return input.theme.has(name)
|
|
366
|
+
},
|
|
367
|
+
set(name) {
|
|
368
|
+
return input.theme.set(name)
|
|
369
|
+
},
|
|
370
|
+
async install(_jsonPath) {
|
|
371
|
+
throw new Error("theme.install is only available in plugin context")
|
|
372
|
+
},
|
|
373
|
+
mode() {
|
|
374
|
+
return input.theme.mode()
|
|
375
|
+
},
|
|
376
|
+
get ready() {
|
|
377
|
+
return input.theme.ready
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Legacy `api.command` bridge for v1 plugins; remove in v2.
|
|
2
|
+
import type { TuiCommand, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
|
3
|
+
import { TuiKeybind } from "../config/keybind"
|
|
4
|
+
import type { DialogContext } from "../ui/dialog"
|
|
5
|
+
|
|
6
|
+
const COMMAND_PALETTE_SHOW = "command.palette.show"
|
|
7
|
+
const warned = new Set<string>()
|
|
8
|
+
|
|
9
|
+
type Warn = (api: string, replacement: string) => void
|
|
10
|
+
type LegacyDialog = TuiPluginApi["ui"]["dialog"]
|
|
11
|
+
type CommandShimDialog = DialogContext | LegacyDialog
|
|
12
|
+
type LegacyKeybinds = TuiPluginApi["tuiConfig"]["keybinds"]
|
|
13
|
+
|
|
14
|
+
function warnCommandShim(api: string, replacement: string) {
|
|
15
|
+
// Warn v1 plugins about deprecated `api.command`; remove this shim path in v2.
|
|
16
|
+
console.warn("[tui.plugin] deprecated TUI plugin API", { api, replacement })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createCommandShimDialog(dialog: CommandShimDialog): LegacyDialog {
|
|
20
|
+
if (!("stack" in dialog)) return dialog
|
|
21
|
+
return {
|
|
22
|
+
replace(render, onClose) {
|
|
23
|
+
dialog.replace(render, onClose)
|
|
24
|
+
},
|
|
25
|
+
clear() {
|
|
26
|
+
dialog.clear()
|
|
27
|
+
},
|
|
28
|
+
setSize(size) {
|
|
29
|
+
dialog.setSize(size)
|
|
30
|
+
},
|
|
31
|
+
get size() {
|
|
32
|
+
return dialog.size
|
|
33
|
+
},
|
|
34
|
+
get depth() {
|
|
35
|
+
return dialog.stack.length
|
|
36
|
+
},
|
|
37
|
+
get open() {
|
|
38
|
+
return dialog.stack.length > 0
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function warnOnce(api: string, replacement: string, warn: Warn) {
|
|
44
|
+
if (warned.has(api)) return
|
|
45
|
+
warned.add(api)
|
|
46
|
+
warn(api, replacement)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toCommand(item: TuiCommand, dialog: LegacyDialog) {
|
|
50
|
+
return {
|
|
51
|
+
namespace: "palette",
|
|
52
|
+
name: item.value,
|
|
53
|
+
title: item.title,
|
|
54
|
+
desc: item.description,
|
|
55
|
+
category: item.category,
|
|
56
|
+
suggested: item.suggested,
|
|
57
|
+
hidden: item.hidden,
|
|
58
|
+
enabled: item.enabled,
|
|
59
|
+
slashName: item.slash?.name,
|
|
60
|
+
slashAliases: item.slash?.aliases,
|
|
61
|
+
run() {
|
|
62
|
+
return item.onSelect?.(dialog)
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toBindings(commands: TuiCommand[], keybinds: LegacyKeybinds) {
|
|
68
|
+
return commands.flatMap((item) =>
|
|
69
|
+
item.keybind
|
|
70
|
+
? keybinds.has(TuiKeybind.CommandMap[item.keybind as keyof typeof TuiKeybind.CommandMap] ?? item.keybind)
|
|
71
|
+
? keybinds
|
|
72
|
+
.get(TuiKeybind.CommandMap[item.keybind as keyof typeof TuiKeybind.CommandMap] ?? item.keybind)
|
|
73
|
+
.map((binding) => ({ ...binding, cmd: item.value, desc: binding.desc ?? item.title }))
|
|
74
|
+
: [
|
|
75
|
+
{
|
|
76
|
+
key: item.keybind,
|
|
77
|
+
cmd: item.value,
|
|
78
|
+
desc: item.title,
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
: [],
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createCommandShim(
|
|
86
|
+
keymap: TuiPluginApi["keymap"],
|
|
87
|
+
dialog: CommandShimDialog,
|
|
88
|
+
keybinds: LegacyKeybinds,
|
|
89
|
+
): TuiPluginApi["command"] {
|
|
90
|
+
const shimDialog = createCommandShimDialog(dialog)
|
|
91
|
+
return {
|
|
92
|
+
register(cb) {
|
|
93
|
+
warnOnce("api.command.register", "api.keymap.registerLayer({ commands, bindings })", warnCommandShim)
|
|
94
|
+
const commands = cb()
|
|
95
|
+
return keymap.registerLayer({
|
|
96
|
+
commands: commands.map((item) => toCommand(item, shimDialog)),
|
|
97
|
+
bindings: toBindings(commands, keybinds),
|
|
98
|
+
})
|
|
99
|
+
},
|
|
100
|
+
trigger(value) {
|
|
101
|
+
warnOnce("api.command.trigger", "api.keymap.dispatchCommand(name)", warnCommandShim)
|
|
102
|
+
keymap.dispatchCommand(value)
|
|
103
|
+
},
|
|
104
|
+
show() {
|
|
105
|
+
warnOnce("api.command.show", `api.keymap.dispatchCommand("${COMMAND_PALETTE_SHOW}")`, warnCommandShim)
|
|
106
|
+
keymap.dispatchCommand(COMMAND_PALETTE_SHOW)
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import HomeFooter from "../feature-plugins/home/footer"
|
|
2
|
+
import HomeTips from "../feature-plugins/home/tips"
|
|
3
|
+
import SidebarContext from "../feature-plugins/sidebar/context"
|
|
4
|
+
import SidebarMcp from "../feature-plugins/sidebar/mcp"
|
|
5
|
+
import SidebarLsp from "../feature-plugins/sidebar/lsp"
|
|
6
|
+
import SidebarTodo from "../feature-plugins/sidebar/todo"
|
|
7
|
+
import SidebarFiles from "../feature-plugins/sidebar/files"
|
|
8
|
+
import SidebarFooter from "../feature-plugins/sidebar/footer"
|
|
9
|
+
import PluginManager from "../feature-plugins/system/plugins"
|
|
10
|
+
import SessionV2Debug from "../feature-plugins/system/session-v2"
|
|
11
|
+
import WhichKey from "../feature-plugins/system/which-key"
|
|
12
|
+
import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
13
|
+
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
14
|
+
|
|
15
|
+
export type InternalTuiPlugin = Omit<TuiPluginModule, "id"> & {
|
|
16
|
+
id: string
|
|
17
|
+
tui: TuiPlugin
|
|
18
|
+
enabled?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const INTERNAL_TUI_PLUGINS: InternalTuiPlugin[] = [
|
|
22
|
+
HomeFooter,
|
|
23
|
+
HomeTips,
|
|
24
|
+
SidebarContext,
|
|
25
|
+
SidebarMcp,
|
|
26
|
+
SidebarLsp,
|
|
27
|
+
SidebarTodo,
|
|
28
|
+
SidebarFiles,
|
|
29
|
+
SidebarFooter,
|
|
30
|
+
PluginManager,
|
|
31
|
+
WhichKey,
|
|
32
|
+
...(Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM ? [SessionV2Debug] : []),
|
|
33
|
+
]
|