STASH COMPLEX PLUGIN
parent
3ac74b51f5
commit
836e0dbc55
|
|
@ -1,29 +1,238 @@
|
|||
/** @jsxImportSource @opentui/solid */
|
||||
import mytheme from "../themes/mytheme.json" with { type: "json" }
|
||||
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
|
||||
import type { TuiApi, TuiPluginInput } from "@opencode-ai/plugin/tui"
|
||||
|
||||
const slot = (label) => ({
|
||||
const pick = (value: unknown, fallback: string) => {
|
||||
if (typeof value !== "string") return fallback
|
||||
if (!value.trim()) return fallback
|
||||
return value
|
||||
}
|
||||
|
||||
const cfg = (options: Record<string, unknown> | undefined) => {
|
||||
return {
|
||||
label: pick(options?.label, "smoke"),
|
||||
modal: pick(options?.modal, "ctrl+shift+m"),
|
||||
screen: pick(options?.screen, "ctrl+shift+o"),
|
||||
route: pick(options?.route, "workspace-smoke"),
|
||||
}
|
||||
}
|
||||
|
||||
const ui = {
|
||||
panel: "#1d1d1d",
|
||||
border: "#4a4a4a",
|
||||
text: "#f0f0f0",
|
||||
muted: "#a5a5a5",
|
||||
accent: "#5f87ff",
|
||||
}
|
||||
|
||||
const active = (api: TuiApi, id: string) => {
|
||||
const route = api.route.data
|
||||
return route.type === "plugin" && route.id === id
|
||||
}
|
||||
|
||||
const open = (api: TuiApi, input: ReturnType<typeof cfg>, source: string) => {
|
||||
console.log("[smoke] open", { route: input.route, source })
|
||||
api.route.plugin(input.route, { source })
|
||||
api.dialog.clear()
|
||||
}
|
||||
|
||||
const Modal = (props: { api: TuiApi; input: ReturnType<typeof cfg> }) => {
|
||||
const dim = useTerminalDimensions()
|
||||
|
||||
useKeyboard((evt) => {
|
||||
if (evt.defaultPrevented) return
|
||||
if (evt.name !== "return") return
|
||||
|
||||
console.log("[smoke] modal key", { key: evt.name })
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
open(props.api, props.input, "modal")
|
||||
})
|
||||
|
||||
return (
|
||||
<box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
width={dim().width}
|
||||
height={dim().height}
|
||||
alignItems="center"
|
||||
paddingTop={Math.max(3, Math.floor(dim().height / 4))}
|
||||
backgroundColor="#000000"
|
||||
>
|
||||
<box width={64} maxWidth={dim().width - 4} backgroundColor={ui.panel} border borderColor={ui.border}>
|
||||
<box paddingTop={1} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<text fg={ui.text}>
|
||||
<b>{props.input.label} modal</b>
|
||||
</text>
|
||||
<text fg={ui.muted}>Plugin commands and keybinds work without host internals</text>
|
||||
<text fg={ui.muted}>
|
||||
{props.api.keybind.print(props.input.modal)} open modal · {props.api.keybind.print(props.input.screen)} open
|
||||
screen
|
||||
</text>
|
||||
<text fg={ui.muted}>enter opens screen · esc closes</text>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<box
|
||||
onMouseUp={() => open(props.api, props.input, "modal")}
|
||||
backgroundColor={ui.accent}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
>
|
||||
<text fg={ui.text}>open screen</text>
|
||||
</box>
|
||||
<box
|
||||
onMouseUp={() => props.api.dialog.clear()}
|
||||
backgroundColor={ui.border}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
>
|
||||
<text fg={ui.text}>cancel</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
const Screen = (props: { api: TuiApi; input: ReturnType<typeof cfg>; data?: Record<string, unknown> }) => {
|
||||
const dim = useTerminalDimensions()
|
||||
|
||||
useKeyboard((evt) => {
|
||||
if (evt.defaultPrevented) return
|
||||
if (evt.name === "escape" || (evt.ctrl && evt.name === "h")) {
|
||||
console.log("[smoke] screen key", { key: evt.name, ctrl: evt.ctrl })
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.route.home()
|
||||
return
|
||||
}
|
||||
|
||||
if (evt.ctrl && evt.name === "m") {
|
||||
console.log("[smoke] screen key", { key: evt.name, ctrl: evt.ctrl })
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.dialog.replace(() => <Modal api={props.api} input={props.input} />)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<box width={dim().width} height={dim().height} backgroundColor={ui.panel}>
|
||||
<box
|
||||
flexDirection="column"
|
||||
width="100%"
|
||||
height="100%"
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
gap={1}
|
||||
>
|
||||
<text fg={ui.text}>
|
||||
<b>{props.input.label} screen</b>
|
||||
<span style={{ fg: ui.muted }}> plugin route</span>
|
||||
</text>
|
||||
<text fg={ui.text}>Route id: {props.input.route}</text>
|
||||
<text fg={ui.muted}>source: {String(props.data?.source ?? "unknown")}</text>
|
||||
<text fg={ui.muted}>esc or ctrl+h go home · ctrl+m opens modal</text>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<box onMouseUp={() => props.api.route.home()} backgroundColor={ui.border} paddingLeft={1} paddingRight={1}>
|
||||
<text fg={ui.text}>go home</text>
|
||||
</box>
|
||||
<box
|
||||
onMouseUp={() => props.api.dialog.replace(() => <Modal api={props.api} input={props.input} />)}
|
||||
backgroundColor={ui.accent}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
>
|
||||
<text fg={ui.text}>open modal</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
const slot = (api: TuiApi, input: ReturnType<typeof cfg>) => ({
|
||||
id: "workspace-smoke",
|
||||
slots: {
|
||||
home_logo() {
|
||||
return <text>plugin logo:{label}</text>
|
||||
route(_ctx, value) {
|
||||
if (value.route_id !== input.route) return null
|
||||
console.log("[smoke] route render", { route: value.route_id, data: value.data })
|
||||
return <Screen api={api} input={input} data={value.data} />
|
||||
},
|
||||
sidebar_top(_ctx, props) {
|
||||
home_logo() {
|
||||
return <text>plugin logo:{input.label}</text>
|
||||
},
|
||||
sidebar_top(_ctx, value) {
|
||||
return (
|
||||
<text>
|
||||
plugin:{label} session:{props.session_id.slice(0, 8)}
|
||||
plugin:{input.label} session:{value.session_id.slice(0, 8)}
|
||||
</text>
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const reg = (api: TuiApi, input: ReturnType<typeof cfg>) => {
|
||||
api.command.register(() => [
|
||||
{
|
||||
title: `${input.label} modal`,
|
||||
value: "plugin.smoke.modal",
|
||||
keybind: input.modal,
|
||||
category: "Plugin",
|
||||
slash: {
|
||||
name: "smoke",
|
||||
},
|
||||
onSelect: () => {
|
||||
console.log("[smoke] command", { value: "plugin.smoke.modal" })
|
||||
api.dialog.replace(() => <Modal api={api} input={input} />)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} screen`,
|
||||
value: "plugin.smoke.screen",
|
||||
keybind: input.screen,
|
||||
category: "Plugin",
|
||||
slash: {
|
||||
name: "smoke-screen",
|
||||
},
|
||||
onSelect: () => {
|
||||
console.log("[smoke] command", { value: "plugin.smoke.screen" })
|
||||
open(api, input, "command")
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} go home`,
|
||||
value: "plugin.smoke.home",
|
||||
category: "Plugin",
|
||||
enabled: active(api, input.route),
|
||||
onSelect: () => {
|
||||
console.log("[smoke] command", { value: "plugin.smoke.home" })
|
||||
api.route.home()
|
||||
api.dialog.clear()
|
||||
},
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
const themes = {
|
||||
"workspace-plugin-smoke": mytheme,
|
||||
}
|
||||
|
||||
const tui = async (input, options) => {
|
||||
const tui = async (input: TuiPluginInput, options?: Record<string, unknown>) => {
|
||||
if (options?.enabled === false) return
|
||||
const label = typeof options?.label === "string" ? options.label : "smoke"
|
||||
input.slots.register(slot(label))
|
||||
|
||||
const value = cfg(options)
|
||||
console.log("[smoke] init", {
|
||||
label: value.label,
|
||||
modal: value.modal,
|
||||
screen: value.screen,
|
||||
route: value.route,
|
||||
})
|
||||
reg(input.api, value)
|
||||
input.slots.register(slot(input.api, value))
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
[01:37:30] [LOG] 'bootstrapping'
|
||||
[01:37:30] [LOG] 'resolveSystemTheme'
|
||||
[01:37:30] [LOG] '[smoke] init' {
|
||||
label: 'workspace',
|
||||
modal: 'ctrl+shift+m',
|
||||
screen: 'ctrl+shift+o',
|
||||
route: 'workspace-smoke'
|
||||
}
|
||||
[01:37:30] [LOG] [
|
||||
'#45475a', '#f38ba8',
|
||||
'#a6e3a1', '#f9e2af',
|
||||
'#89b4fa', '#f5c2e7',
|
||||
'#94e2d5', '#a6adc8',
|
||||
'#585b70', '#f37799',
|
||||
'#89d88b', '#ebd391',
|
||||
'#74a8fc', '#f2aede',
|
||||
'#6bd7ca', '#bac2de'
|
||||
]
|
||||
[01:37:40] [LOG] '[smoke] command' { value: 'plugin.smoke.modal' }
|
||||
[01:37:46] [LOG] '[smoke] command' { value: 'plugin.smoke.modal' }
|
||||
[01:37:54] [LOG] '[smoke] command' { value: 'plugin.smoke.screen' }
|
||||
[01:37:54] [LOG] '[smoke] open' { route: 'workspace-smoke', source: 'command' }
|
||||
[01:37:54] [LOG] '[smoke] route render' { route: 'workspace-smoke', data: { source: 'command' } }
|
||||
[01:37:54] [LOG] '[smoke] route render' { route: 'workspace-smoke', data: { source: 'command' } }
|
||||
[01:38:04] [LOG] '[smoke] command' { value: 'plugin.smoke.screen' }
|
||||
[01:38:04] [LOG] '[smoke] open' { route: 'workspace-smoke', source: 'command' }
|
||||
[01:38:04] [LOG] '[smoke] route render' { route: 'workspace-smoke', data: { source: 'command' } }
|
||||
[01:38:04] [LOG] '[smoke] route render' { route: 'workspace-smoke', data: { source: 'command' } }
|
||||
[01:38:08] [LOG] '[smoke] screen key' { key: 'escape', ctrl: false }
|
||||
[01:38:30] [LOG] '[smoke] command' { value: 'plugin.smoke.modal' }
|
||||
[01:38:33] [LOG] '[smoke] command' { value: 'plugin.smoke.modal' }
|
||||
[01:38:34] [LOG] '[smoke] open' { route: 'workspace-smoke', source: 'modal' }
|
||||
[01:38:34] [LOG] '[smoke] route render' { route: 'workspace-smoke', data: { source: 'modal' } }
|
||||
[01:38:34] [LOG] '[smoke] route render' { route: 'workspace-smoke', data: { source: 'modal' } }
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||
import { Clipboard } from "@tui/util/clipboard"
|
||||
import { Selection } from "@tui/util/selection"
|
||||
import { createCliRenderer, MouseButton, TextAttributes, type CliRendererConfig } from "@opentui/core"
|
||||
import { createCliRenderer, MouseButton, TextAttributes, type CliRendererConfig, type ParsedKey } from "@opentui/core"
|
||||
import { RouteProvider, useRoute } from "@tui/context/route"
|
||||
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show, on } from "solid-js"
|
||||
import { win32DisableProcessedInput, win32FlushInputBuffer, win32InstallCtrlCGuard } from "./win32"
|
||||
|
|
@ -21,7 +21,7 @@ import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command
|
|||
import { DialogAgent } from "@tui/component/dialog-agent"
|
||||
import { DialogSessionList } from "@tui/component/dialog-session-list"
|
||||
import { DialogWorkspaceList } from "@tui/component/dialog-workspace-list"
|
||||
import { KeybindProvider } from "@tui/context/keybind"
|
||||
import { KeybindProvider, useKeybind } from "@tui/context/keybind"
|
||||
import { ThemeProvider, useTheme } from "@tui/context/theme"
|
||||
import { Home } from "@tui/routes/home"
|
||||
import { Session } from "@tui/routes/session"
|
||||
|
|
@ -41,6 +41,7 @@ import { writeHeapSnapshot } from "v8"
|
|||
import { PromptRefProvider, usePromptRef } from "./context/prompt"
|
||||
import { TuiConfigProvider } from "./context/tui-config"
|
||||
import { TuiConfig } from "@/config/tui"
|
||||
import type { TuiApi, TuiRoute } from "@opencode-ai/plugin/tui"
|
||||
import { TuiPlugin } from "./plugin"
|
||||
|
||||
async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
||||
|
|
@ -212,19 +213,72 @@ function App() {
|
|||
const local = useLocal()
|
||||
const kv = useKV()
|
||||
const command = useCommandDialog()
|
||||
const keybind = useKeybind()
|
||||
const sdk = useSDK()
|
||||
TuiPlugin.init({
|
||||
client: sdk.client,
|
||||
event: sdk.event,
|
||||
renderer,
|
||||
}).catch((error) => {
|
||||
console.error("Failed to load TUI plugins", error)
|
||||
})
|
||||
const toast = useToast()
|
||||
const { theme, mode, setMode } = useTheme()
|
||||
const sync = useSync()
|
||||
const exit = useExit()
|
||||
const promptRef = usePromptRef()
|
||||
const api: TuiApi = {
|
||||
command: {
|
||||
register(cb) {
|
||||
command.register(() => cb())
|
||||
},
|
||||
trigger(value) {
|
||||
command.trigger(value)
|
||||
},
|
||||
},
|
||||
dialog: {
|
||||
clear() {
|
||||
dialog.clear()
|
||||
},
|
||||
replace(input, onClose) {
|
||||
dialog.replace(input, onClose)
|
||||
},
|
||||
get depth() {
|
||||
return dialog.stack.length
|
||||
},
|
||||
},
|
||||
route: {
|
||||
get data() {
|
||||
return route.data
|
||||
},
|
||||
navigate(next: TuiRoute) {
|
||||
route.navigate(next)
|
||||
},
|
||||
home() {
|
||||
route.navigate({ type: "home" })
|
||||
},
|
||||
plugin(id, data) {
|
||||
route.navigate({ type: "plugin", id, data })
|
||||
},
|
||||
},
|
||||
keybind: {
|
||||
parse(evt: ParsedKey) {
|
||||
return keybind.parse(evt)
|
||||
},
|
||||
match(key, evt: ParsedKey) {
|
||||
return keybind.match(key, evt)
|
||||
},
|
||||
print(key) {
|
||||
return keybind.print(key)
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
get current() {
|
||||
return theme
|
||||
},
|
||||
},
|
||||
}
|
||||
TuiPlugin.init({
|
||||
client: sdk.client,
|
||||
event: sdk.event,
|
||||
renderer,
|
||||
api,
|
||||
}).catch((error) => {
|
||||
console.error("Failed to load TUI plugins", error)
|
||||
})
|
||||
|
||||
useKeyboard((evt) => {
|
||||
if (!Flag.OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT) return
|
||||
|
|
@ -267,10 +321,6 @@ function App() {
|
|||
}
|
||||
const [terminalTitleEnabled, setTerminalTitleEnabled] = createSignal(kv.get("terminal_title_enabled", true))
|
||||
|
||||
createEffect(() => {
|
||||
console.log(JSON.stringify(route.data))
|
||||
})
|
||||
|
||||
// Update terminal window title based on current route and session
|
||||
createEffect(() => {
|
||||
if (!terminalTitleEnabled() || Flag.OPENCODE_DISABLE_TERMINAL_TITLE) return
|
||||
|
|
@ -287,9 +337,13 @@ function App() {
|
|||
return
|
||||
}
|
||||
|
||||
// Truncate title to 40 chars max
|
||||
const title = session.title.length > 40 ? session.title.slice(0, 37) + "..." : session.title
|
||||
renderer.setTerminalTitle(`OC | ${title}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (route.data.type === "plugin") {
|
||||
renderer.setTerminalTitle(`OC | ${route.data.id}`)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -760,6 +814,11 @@ function App() {
|
|||
})
|
||||
})
|
||||
|
||||
const plugin = () => {
|
||||
if (route.data.type !== "plugin") return
|
||||
return route.data
|
||||
}
|
||||
|
||||
return (
|
||||
<box
|
||||
width={dimensions().width}
|
||||
|
|
@ -783,6 +842,27 @@ function App() {
|
|||
<Session />
|
||||
</Match>
|
||||
</Switch>
|
||||
<Show when={plugin()}>
|
||||
{(item) => (
|
||||
<TuiPlugin.Slot name="route" mode="replace" route_id={item().id} data={item().data}>
|
||||
<PluginRouteMissing id={item().id} onHome={() => route.navigate({ type: "home" })} />
|
||||
</TuiPlugin.Slot>
|
||||
)}
|
||||
</Show>
|
||||
<TuiPlugin.Slot name="app" />
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
function PluginRouteMissing(props: { id: string; onHome: () => void }) {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<box width="100%" height="100%" alignItems="center" justifyContent="center" flexDirection="column" gap={1}>
|
||||
<text fg={theme.warning}>Unknown plugin route: {props.id}</text>
|
||||
<box onMouseUp={props.onHome} backgroundColor={theme.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<text fg={theme.text}>go home</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
type ParentProps,
|
||||
} from "solid-js"
|
||||
import { useKeyboard } from "@opentui/solid"
|
||||
import { type KeybindKey, useKeybind } from "@tui/context/keybind"
|
||||
import { useKeybind } from "@tui/context/keybind"
|
||||
|
||||
type Context = ReturnType<typeof init>
|
||||
const ctx = createContext<Context>()
|
||||
|
|
@ -21,7 +21,7 @@ export type Slash = {
|
|||
}
|
||||
|
||||
export type CommandOption = DialogSelectOption<string> & {
|
||||
keybind?: KeybindKey
|
||||
keybind?: string
|
||||
suggested?: boolean
|
||||
slash?: Slash
|
||||
hidden?: boolean
|
||||
|
|
|
|||
|
|
@ -80,21 +80,24 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex
|
|||
}
|
||||
return Keybind.fromParsedKey(evt, store.leader)
|
||||
},
|
||||
match(key: KeybindKey, evt: ParsedKey) {
|
||||
const keybind = keybinds()[key]
|
||||
if (!keybind) return false
|
||||
match(key: string, evt: ParsedKey) {
|
||||
const list = keybinds()[key] ?? Keybind.parse(key)
|
||||
if (!list.length) return false
|
||||
const parsed: Keybind.Info = result.parse(evt)
|
||||
for (const key of keybind) {
|
||||
if (Keybind.match(key, parsed)) {
|
||||
for (const item of list) {
|
||||
if (Keybind.match(item, parsed)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
print(key: KeybindKey) {
|
||||
const first = keybinds()[key]?.at(0)
|
||||
print(key: string) {
|
||||
const first = keybinds()[key]?.at(0) ?? Keybind.parse(key).at(0)
|
||||
if (!first) return ""
|
||||
const result = Keybind.toString(first)
|
||||
return result.replace("<leader>", Keybind.toString(keybinds().leader![0]!))
|
||||
const text = Keybind.toString(first)
|
||||
const lead = keybinds().leader?.[0]
|
||||
if (!lead) return text
|
||||
return text.replace("<leader>", Keybind.toString(lead))
|
||||
},
|
||||
}
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -13,7 +13,13 @@ export type SessionRoute = {
|
|||
initialPrompt?: PromptInfo
|
||||
}
|
||||
|
||||
export type Route = HomeRoute | SessionRoute
|
||||
export type PluginRoute = {
|
||||
type: "plugin"
|
||||
id: string
|
||||
data?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type Route = HomeRoute | SessionRoute | PluginRoute
|
||||
|
||||
export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
|
||||
name: "Route",
|
||||
|
|
@ -31,7 +37,6 @@ export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
|
|||
return store
|
||||
},
|
||||
navigate(route: Route) {
|
||||
console.log("navigate", route)
|
||||
setStore(route)
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,43 @@ test("ignores function-only tui exports and loads object exports", async () => {
|
|||
on: () => () => {},
|
||||
},
|
||||
renderer,
|
||||
api: {
|
||||
command: {
|
||||
register: () => {},
|
||||
trigger: () => {},
|
||||
},
|
||||
dialog: {
|
||||
clear: () => {},
|
||||
replace: () => {},
|
||||
get depth() {
|
||||
return 0
|
||||
},
|
||||
},
|
||||
route: {
|
||||
get data() {
|
||||
return { type: "home" as const }
|
||||
},
|
||||
navigate: () => {},
|
||||
home: () => {},
|
||||
plugin: () => {},
|
||||
},
|
||||
keybind: {
|
||||
parse: () => ({
|
||||
name: "",
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
leader: false,
|
||||
}),
|
||||
match: () => false,
|
||||
print: () => "",
|
||||
},
|
||||
theme: {
|
||||
get current() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(await fs.readFile(tmp.extra.objMarker, "utf8")).toBe("called")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { createOpencodeClient as createOpencodeClientV2, Event as TuiEvent } from "@opencode-ai/sdk/v2"
|
||||
import type { CliRenderer, Plugin as CorePlugin } from "@opentui/core"
|
||||
import type { CliRenderer, ParsedKey, Plugin as CorePlugin } from "@opentui/core"
|
||||
import type { Plugin as ServerPlugin, PluginOptions } from "./index"
|
||||
|
||||
export type { CliRenderer, SlotMode } from "@opentui/core"
|
||||
|
|
@ -22,7 +22,77 @@ export type ThemeJson = {
|
|||
}
|
||||
}
|
||||
|
||||
export type TuiRoute =
|
||||
| {
|
||||
type: "home"
|
||||
}
|
||||
| {
|
||||
type: "session"
|
||||
sessionID: string
|
||||
}
|
||||
| {
|
||||
type: "plugin"
|
||||
id: string
|
||||
data?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type TuiCommand = {
|
||||
title: string
|
||||
value: string
|
||||
description?: string
|
||||
category?: string
|
||||
keybind?: string
|
||||
suggested?: boolean
|
||||
hidden?: boolean
|
||||
enabled?: boolean
|
||||
slash?: {
|
||||
name: string
|
||||
aliases?: string[]
|
||||
}
|
||||
onSelect?: () => void
|
||||
}
|
||||
|
||||
export type TuiKeybind = {
|
||||
name: string
|
||||
ctrl: boolean
|
||||
meta: boolean
|
||||
shift: boolean
|
||||
super?: boolean
|
||||
leader: boolean
|
||||
}
|
||||
|
||||
export type TuiApi<Node = unknown> = {
|
||||
command: {
|
||||
register: (cb: () => TuiCommand[]) => void
|
||||
trigger: (value: string) => void
|
||||
}
|
||||
dialog: {
|
||||
clear: () => void
|
||||
replace: (input: Node | (() => Node), onClose?: () => void) => void
|
||||
readonly depth: number
|
||||
}
|
||||
route: {
|
||||
readonly data: TuiRoute
|
||||
navigate: (route: TuiRoute) => void
|
||||
home: () => void
|
||||
plugin: (id: string, data?: Record<string, unknown>) => void
|
||||
}
|
||||
keybind: {
|
||||
parse: (evt: ParsedKey) => TuiKeybind
|
||||
match: (key: string, evt: ParsedKey) => boolean
|
||||
print: (key: string) => string
|
||||
}
|
||||
theme: {
|
||||
readonly current: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export type TuiSlotMap = {
|
||||
app: {}
|
||||
route: {
|
||||
route_id: string
|
||||
data?: Record<string, unknown>
|
||||
}
|
||||
home_logo: {}
|
||||
sidebar_top: {
|
||||
session_id: string
|
||||
|
|
@ -44,21 +114,22 @@ export type TuiEventBus = {
|
|||
) => () => void
|
||||
}
|
||||
|
||||
export type TuiPluginInput<Renderer = CliRenderer> = {
|
||||
export type TuiPluginInput<Renderer = CliRenderer, Node = unknown> = {
|
||||
client: ReturnType<typeof createOpencodeClientV2>
|
||||
event: TuiEventBus
|
||||
renderer: Renderer
|
||||
slots: TuiSlots
|
||||
api: TuiApi<Node>
|
||||
}
|
||||
|
||||
export type TuiPlugin<Renderer = CliRenderer> = (
|
||||
input: TuiPluginInput<Renderer>,
|
||||
export type TuiPlugin<Renderer = CliRenderer, Node = unknown> = (
|
||||
input: TuiPluginInput<Renderer, Node>,
|
||||
options?: PluginOptions,
|
||||
) => Promise<void>
|
||||
|
||||
export type TuiPluginModule<Renderer = CliRenderer> = {
|
||||
export type TuiPluginModule<Renderer = CliRenderer, Node = unknown> = {
|
||||
server?: ServerPlugin
|
||||
tui?: TuiPlugin<Renderer>
|
||||
tui?: TuiPlugin<Renderer, Node>
|
||||
slots?: TuiSlotPlugin
|
||||
themes?: Record<string, ThemeJson>
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue