// opencode TUI plugin exposing /cc-zdr, /cc-debug and /cc-status, plus a sidebar panel // showing the current toggle state. It flips the shared toggle file (see ./toggles.ts) // that the provider reads on every request, so a change takes effect without restarting // opencode. // // opencode loads this module's default export (`{ id, tui }`) from a `tui.json` plugin // entry. It is loaded from source (not dist) so opencode's Bun/Solid transform compiles // the JSX and maps `solid-js` / `@opentui/solid` to its internal modules; that keeps this // package free of runtime and build dependencies. The ambient types in ./tui-shims.d.ts // back the JSX and `createSignal` references for `tsc -p tsconfig.tui.json`. // // Note: in this opencode version slash commands and the Ctrl+P palette read the same // `namespace: "palette"` command list, so these entries appear in both; `hidden: true` // would remove them from both. import { createSignal } from "solid-js"; import { readToggles, toggle, type ToggleName, type Toggles } from "./toggles.js"; type ToastVariant = "info" | "success" | "warning" | "error"; type TuiCommand = { name: string; title?: string; desc?: string; category?: string; namespace?: string; slashName?: string; slashAliases?: string[]; suggested?: boolean; hidden?: boolean; enabled?: boolean | (() => boolean); run: () => void | Promise; }; type TuiTheme = { current: { text: unknown; textMuted: unknown; success: unknown } }; type TuiSlotHandler = (ctx: { theme: TuiTheme }, props: { session_id: string }) => unknown; type TuiSlotPlugin = { order?: number; slots: Record }; type TuiApi = { keymap: { registerLayer(layer: { commands?: readonly TuiCommand[] }): () => void }; ui: { toast(input: { title?: string; message: string; variant?: ToastVariant; duration?: number }): void }; slots: { register(plugin: TuiSlotPlugin): string }; theme: TuiTheme; lifecycle: { onDispose(fn: () => void): () => void }; }; const ID = "commandcode-toggles"; const CATEGORY = "CommandCode"; const SIDEBAR_ORDER = 90; function state(value: boolean | undefined): string { return value === true ? "on" : "off"; } function summary(toggles: Toggles = readToggles()): string { return `zdr=${state(toggles.zdr)}, debug=${state(toggles.debug)}`; } export const tui = async (api: TuiApi): Promise => { const [toggles, setToggles] = createSignal(readToggles()); const refresh = (): void => setToggles(readToggles()); const flip = (name: ToggleName): void => { const next = toggle(name); setToggles(next); api.ui.toast({ title: CATEGORY, message: `${name} ${state(next[name])} (${summary(next)})`, variant: next[name] === true ? "success" : "info", }); }; const Status = () => ( CommandCode zdr: {state(toggles().zdr)} debug: {state(toggles().debug)} ); api.slots.register({ order: SIDEBAR_ORDER, slots: { sidebar_content() { return ; }, }, }); api.keymap.registerLayer({ commands: [ { name: `${ID}.zdr`, title: "CommandCode: toggle ZDR header", desc: "Send the x-cmd-zdr header on requests", category: CATEGORY, namespace: "palette", slashName: "cc-zdr", run: () => flip("zdr"), }, { name: `${ID}.debug`, title: "CommandCode: toggle debug tracing", desc: "Write a redacted request and stream trace to a log file", category: CATEGORY, namespace: "palette", slashName: "cc-debug", run: () => flip("debug"), }, { name: `${ID}.status`, title: "CommandCode: show toggle status", desc: "Show the current zdr and debug state", category: CATEGORY, namespace: "palette", slashName: "cc-status", run: () => api.ui.toast({ title: CATEGORY, message: summary(), variant: "info" }), }, ], }); const timer = setInterval(refresh, 1500); api.lifecycle.onDispose(() => clearInterval(timer)); }; export const id = ID; export default { id: ID, tui };