separate tui from server plugin

actual-tui-plugins
Sebastian Herrlinger 2026-03-04 14:42:51 +01:00
parent aab237d89a
commit 5a5405630b
8 changed files with 95 additions and 93 deletions

View File

@ -49,7 +49,7 @@ import { writeHeapSnapshot } from "v8"
import { PromptRefProvider, usePromptRef } from "./context/prompt"
import { TuiConfigProvider } from "./context/tui-config"
import { TuiConfig } from "@/config/tui"
import type { TuiSlotContext, TuiSlotMap, TuiSlots } from "@opencode-ai/plugin"
import type { TuiSlotContext, TuiSlotMap, TuiSlots } from "@opencode-ai/plugin/tui"
type TuiSlot = <K extends keyof TuiSlotMap>(props: { name: K } & TuiSlotMap[K]) => unknown

View File

@ -1,6 +1,6 @@
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2"
import type { CliRenderer } from "@opentui/core"
import type { TuiSlots } from "@opencode-ai/plugin"
import type { TuiSlots } from "@opencode-ai/plugin/tui"
import { createSimpleContext } from "./helper"
import { createGlobalEmitter } from "@solid-primitives/event-bus"
import { batch, onCleanup, onMount } from "solid-js"

View File

@ -1,9 +1,9 @@
import {
type PluginModule,
type TuiPlugin as TuiPluginFn,
type TuiPluginInput,
type TuiPluginModule,
type TuiSlotPlugin,
} from "@opencode-ai/plugin"
} from "@opencode-ai/plugin/tui"
import type { JSX } from "solid-js"
import "@opentui/solid/preload"
@ -79,7 +79,7 @@ export namespace TuiPlugin {
if (!mod) continue
const seen = new Set<unknown>()
for (const entry of Object.values<PluginModule>(mod)) {
for (const entry of Object.values<TuiPluginModule>(mod)) {
if (seen.has(entry)) continue
seen.add(entry)

View File

@ -15,7 +15,7 @@ import { Installation } from "@/installation"
import { useKV } from "../context/kv"
import { useCommandDialog } from "../component/dialog-command"
import { useLocal } from "../context/local"
import type { TuiSlotMap } from "@opencode-ai/plugin"
import type { TuiSlotMap } from "@opencode-ai/plugin/tui"
type Slot = <K extends "home_hint" | "home_footer">(props: { name: K } & TuiSlotMap[K]) => unknown

View File

@ -80,7 +80,7 @@ import { DialogExportOptions } from "../../ui/dialog-export-options"
import { formatTranscript } from "../../util/transcript"
import { UI } from "@/cli/ui.ts"
import { useTuiConfig } from "../../context/tui-config"
import type { TuiSlotMap } from "@opencode-ai/plugin"
import type { TuiSlotMap } from "@opencode-ai/plugin/tui"
addDefaultParsers(parsers.parsers)

View File

@ -10,7 +10,8 @@
},
"exports": {
".": "./src/index.ts",
"./tool": "./src/tool.ts"
"./tool": "./src/tool.ts",
"./tui": "./src/tui.ts"
},
"files": [
"dist"

View File

@ -11,14 +11,10 @@ import type {
Auth,
Config as SDKConfig,
} from "@opencode-ai/sdk"
import type { createOpencodeClient as createOpencodeClientV2, Event as TuiEvent } from "@opencode-ai/sdk/v2"
import type { CliRenderer } from "@opentui/core"
import type { BunShell } from "./shell"
import { type ToolDefinition } from "./tool"
export * from "./tool"
export type { CliRenderer } from "@opentui/core"
export type ProviderContext = {
source: "env" | "config" | "custom" | "api"
@ -41,89 +37,8 @@ export type Config = Omit<SDKConfig, "plugin"> & {
plugin?: Array<string | [string, PluginOptions]>
}
type HexColor = `#${string}`
type RefName = string
type Variant = {
dark: HexColor | RefName | number
light: HexColor | RefName | number
}
type ThemeColorValue = HexColor | RefName | number | Variant
export type ThemeJson = {
$schema?: string
defs?: Record<string, HexColor | RefName>
theme: Record<string, ThemeColorValue> & {
selectedListItemText?: ThemeColorValue
backgroundMenu?: ThemeColorValue
thinkingOpacity?: number
}
}
export type SlotMode = "append" | "replace" | "single_winner"
type SlotRenderer<Node, Props, Context extends object = object> = (ctx: Readonly<Context>, props: Props) => Node
type SlotPlugin<Node, Slots extends object, Context extends object = object> = {
id: string
order?: number
setup?: (ctx: Readonly<Context>, renderer: CliRenderer) => void
dispose?: () => void
slots: {
[K in keyof Slots]?: SlotRenderer<Node, Slots[K], Context>
}
}
export type TuiSlotMap = {
home_hint: {}
home_footer: {}
session_footer: {
session_id: string
}
}
export type TuiSlotContext = {
url: string
directory?: string
}
export type TuiSlotPlugin<Node = unknown> = SlotPlugin<Node, TuiSlotMap, TuiSlotContext>
export type TuiSlots = {
register: (plugin: TuiSlotPlugin) => () => void
}
export type Plugin = (input: PluginInput, options?: PluginOptions) => Promise<Hooks>
export type TuiEventBus = {
on: <Type extends TuiEvent["type"]>(
type: Type,
handler: (event: Extract<TuiEvent, { type: Type }>) => void,
) => () => void
}
export type TuiPluginInput<Renderer = CliRenderer> = {
client: ReturnType<typeof createOpencodeClientV2>
event: TuiEventBus
url: string
directory?: string
renderer: Renderer
slots: TuiSlots
}
export type TuiPlugin<Renderer = CliRenderer> = (
input: TuiPluginInput<Renderer>,
options?: PluginOptions,
) => Promise<void>
export type PluginModule<Renderer = CliRenderer> =
| Plugin
| {
server?: Plugin
tui?: TuiPlugin<Renderer>
slots?: TuiSlotPlugin
themes?: Record<string, ThemeJson>
}
export type AuthHook = {
provider: string
loader?: (auth: () => Promise<Auth>, provider: Provider) => Promise<Record<string, any>>

View File

@ -0,0 +1,86 @@
import type { createOpencodeClient as createOpencodeClientV2, Event as TuiEvent } from "@opencode-ai/sdk/v2"
import type { CliRenderer } from "@opentui/core"
import type { Plugin, PluginOptions } from "./index"
export type { CliRenderer } from "@opentui/core"
type HexColor = `#${string}`
type RefName = string
type Variant = {
dark: HexColor | RefName | number
light: HexColor | RefName | number
}
type ThemeColorValue = HexColor | RefName | number | Variant
export type ThemeJson = {
$schema?: string
defs?: Record<string, HexColor | RefName>
theme: Record<string, ThemeColorValue> & {
selectedListItemText?: ThemeColorValue
backgroundMenu?: ThemeColorValue
thinkingOpacity?: number
}
}
export type SlotMode = "append" | "replace" | "single_winner"
type SlotRenderer<Node, Props, Context extends object = object> = (ctx: Readonly<Context>, props: Props) => Node
type SlotPlugin<Node, Slots extends object, Context extends object = object> = {
id: string
order?: number
setup?: (ctx: Readonly<Context>, renderer: CliRenderer) => void
dispose?: () => void
slots: {
[K in keyof Slots]?: SlotRenderer<Node, Slots[K], Context>
}
}
export type TuiSlotMap = {
home_hint: {}
home_footer: {}
session_footer: {
session_id: string
}
}
export type TuiSlotContext = {
url: string
directory?: string
}
export type TuiSlotPlugin<Node = unknown> = SlotPlugin<Node, TuiSlotMap, TuiSlotContext>
export type TuiSlots = {
register: (plugin: TuiSlotPlugin) => () => void
}
export type TuiEventBus = {
on: <Type extends TuiEvent["type"]>(
type: Type,
handler: (event: Extract<TuiEvent, { type: Type }>) => void,
) => () => void
}
export type TuiPluginInput<Renderer = CliRenderer> = {
client: ReturnType<typeof createOpencodeClientV2>
event: TuiEventBus
url: string
directory?: string
renderer: Renderer
slots: TuiSlots
}
export type TuiPlugin<Renderer = CliRenderer> = (
input: TuiPluginInput<Renderer>,
options?: PluginOptions,
) => Promise<void>
export type TuiPluginModule<Renderer = CliRenderer> =
| TuiPlugin<Renderer>
| {
server?: Plugin
tui?: TuiPlugin<Renderer>
slots?: TuiSlotPlugin
themes?: Record<string, ThemeJson>
}