collapse tui api types
This commit is contained in:
@@ -59,7 +59,7 @@ import { PromptRefProvider, usePromptRef } from "./context/prompt"
|
||||
import { TuiConfigProvider, useTuiConfig } from "./context/tui-config"
|
||||
import { TuiConfig } from "@/config/tui"
|
||||
import { createTuiApi, TuiPluginRuntime, type RouteMap } from "./plugin"
|
||||
import type { TuiHostPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import type { TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import { FormatError, FormatUnknownError } from "@/cli/error"
|
||||
|
||||
async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
||||
@@ -278,7 +278,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
map.set(key, next)
|
||||
return next
|
||||
}
|
||||
const workspace: TuiHostPluginApi["workspace"] = {
|
||||
const workspace: TuiPluginApi["workspace"] = {
|
||||
current() {
|
||||
return sdk.workspaceID
|
||||
},
|
||||
@@ -310,19 +310,14 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
sync,
|
||||
theme: themeState,
|
||||
toast,
|
||||
})
|
||||
const host: TuiHostPluginApi = {
|
||||
...api,
|
||||
get client() {
|
||||
return sdk.client
|
||||
},
|
||||
client: () => sdk.client,
|
||||
scopedClient: scoped,
|
||||
workspace,
|
||||
event: sdk.event,
|
||||
renderer,
|
||||
}
|
||||
})
|
||||
const [ready, setReady] = createSignal(false)
|
||||
TuiPluginRuntime.init(host)
|
||||
TuiPluginRuntime.init(api)
|
||||
.catch((error) => {
|
||||
console.error("Failed to load TUI plugins", error)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ParsedKey } from "@opentui/core"
|
||||
import type { TuiApi, TuiDialogSelectOption, TuiRouteDefinition } from "@opencode-ai/plugin/tui"
|
||||
import type { TuiDialogSelectOption, TuiPluginApi, TuiRouteDefinition } from "@opencode-ai/plugin/tui"
|
||||
import type { useCommandDialog } from "@tui/component/dialog-command"
|
||||
import type { useKeybind } from "@tui/context/keybind"
|
||||
import type { useRoute } from "@tui/context/route"
|
||||
@@ -35,6 +35,11 @@ type Input = {
|
||||
sync: ReturnType<typeof useSync>
|
||||
theme: ReturnType<typeof useTheme>
|
||||
toast: ReturnType<typeof useToast>
|
||||
client: () => TuiPluginApi["client"]
|
||||
scopedClient: TuiPluginApi["scopedClient"]
|
||||
workspace: TuiPluginApi["workspace"]
|
||||
event: TuiPluginApi["event"]
|
||||
renderer: TuiPluginApi["renderer"]
|
||||
}
|
||||
|
||||
function routeRegister(routes: RouteMap, list: TuiRouteDefinition[], bump: () => void) {
|
||||
@@ -77,7 +82,7 @@ function routeNavigate(route: ReturnType<typeof useRoute>, name: string, params?
|
||||
route.navigate({ type: "plugin", id: name, data: params })
|
||||
}
|
||||
|
||||
function routeCurrent(route: ReturnType<typeof useRoute>): TuiApi["route"]["current"] {
|
||||
function routeCurrent(route: ReturnType<typeof useRoute>): TuiPluginApi["route"]["current"] {
|
||||
if (route.data.type === "home") return { name: "home" }
|
||||
if (route.data.type === "session") {
|
||||
return {
|
||||
@@ -118,7 +123,7 @@ function mapOptionCb<Value>(cb?: (item: TuiDialogSelectOption<Value>) => void) {
|
||||
return (item: SelectOption<Value>) => cb(pickOption(item))
|
||||
}
|
||||
|
||||
function stateApi(sync: ReturnType<typeof useSync>): TuiApi["state"] {
|
||||
function stateApi(sync: ReturnType<typeof useSync>): TuiPluginApi["state"] {
|
||||
return {
|
||||
get ready() {
|
||||
return sync.ready
|
||||
@@ -187,7 +192,7 @@ function stateApi(sync: ReturnType<typeof useSync>): TuiApi["state"] {
|
||||
}
|
||||
}
|
||||
|
||||
function appApi(): TuiApi["app"] {
|
||||
function appApi(): TuiPluginApi["app"] {
|
||||
return {
|
||||
get version() {
|
||||
return Installation.VERSION
|
||||
@@ -195,7 +200,14 @@ function appApi(): TuiApi["app"] {
|
||||
}
|
||||
}
|
||||
|
||||
export function createTuiApi(input: Input): TuiApi {
|
||||
export function createTuiApi(input: Input): TuiPluginApi {
|
||||
const lifecycle: TuiPluginApi["lifecycle"] = {
|
||||
signal: new AbortController().signal,
|
||||
onDispose() {
|
||||
return () => {}
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
app: appApi(),
|
||||
command: {
|
||||
@@ -304,6 +316,19 @@ export function createTuiApi(input: Input): TuiApi {
|
||||
},
|
||||
},
|
||||
state: stateApi(input.sync),
|
||||
get client() {
|
||||
return input.client()
|
||||
},
|
||||
scopedClient: input.scopedClient,
|
||||
workspace: input.workspace,
|
||||
event: input.event,
|
||||
renderer: input.renderer,
|
||||
slots: {
|
||||
register() {
|
||||
throw new Error("slots.register is only available in plugin context")
|
||||
},
|
||||
},
|
||||
lifecycle,
|
||||
theme: {
|
||||
get current() {
|
||||
return input.theme.theme
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
type TuiSlotPlugin,
|
||||
type TuiTheme,
|
||||
} from "@opencode-ai/plugin/tui"
|
||||
import type { CliRenderer } from "@opentui/core"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
@@ -42,7 +41,7 @@ type Deps = {
|
||||
type Api = HostPluginApi
|
||||
|
||||
type Scope = {
|
||||
lifecycle: TuiPluginApi<CliRenderer>["lifecycle"]
|
||||
lifecycle: TuiPluginApi["lifecycle"]
|
||||
wrap: (fn: (() => void) | undefined) => () => void
|
||||
dispose: () => Promise<void>
|
||||
}
|
||||
@@ -87,7 +86,7 @@ function runCleanup(fn: () => unknown, ms: number): Promise<CleanupResult> {
|
||||
})
|
||||
}
|
||||
|
||||
function isTuiPlugin(value: unknown): value is TuiPlugin<CliRenderer> {
|
||||
function isTuiPlugin(value: unknown): value is TuiPlugin {
|
||||
return typeof value === "function"
|
||||
}
|
||||
|
||||
@@ -310,7 +309,7 @@ function scope(load: Loaded, name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const lifecycle: TuiPluginApi<CliRenderer>["lifecycle"] = {
|
||||
const lifecycle: TuiPluginApi["lifecycle"] = {
|
||||
signal: ctrl.signal,
|
||||
onDispose,
|
||||
}
|
||||
@@ -373,8 +372,8 @@ function plug(plugin: TuiSlotPlugin, id: string): HostSlotPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
function pluginApi(api: Api, host: HostSlots, load: Loaded, state: Scope, base: string): TuiPluginApi<CliRenderer> {
|
||||
const command: TuiPluginApi<CliRenderer>["command"] = {
|
||||
function pluginApi(api: Api, host: HostSlots, load: Loaded, state: Scope, base: string): TuiPluginApi {
|
||||
const command: TuiPluginApi["command"] = {
|
||||
register(cb) {
|
||||
return state.wrap(api.command.register(cb))
|
||||
},
|
||||
@@ -383,7 +382,7 @@ function pluginApi(api: Api, host: HostSlots, load: Loaded, state: Scope, base:
|
||||
},
|
||||
}
|
||||
|
||||
const route: TuiPluginApi<CliRenderer>["route"] = {
|
||||
const route: TuiPluginApi["route"] = {
|
||||
register(list) {
|
||||
return state.wrap(api.route.register(list))
|
||||
},
|
||||
@@ -395,11 +394,11 @@ function pluginApi(api: Api, host: HostSlots, load: Loaded, state: Scope, base:
|
||||
},
|
||||
}
|
||||
|
||||
const theme: TuiPluginApi<CliRenderer>["theme"] = Object.assign(Object.create(api.theme), {
|
||||
const theme: TuiPluginApi["theme"] = Object.assign(Object.create(api.theme), {
|
||||
install: load.install,
|
||||
})
|
||||
|
||||
const event: TuiPluginApi<CliRenderer>["event"] = {
|
||||
const event: TuiPluginApi["event"] = {
|
||||
on(type, handler) {
|
||||
return state.wrap(api.event.on(type, handler))
|
||||
},
|
||||
@@ -407,7 +406,7 @@ function pluginApi(api: Api, host: HostSlots, load: Loaded, state: Scope, base:
|
||||
|
||||
let count = 0
|
||||
|
||||
const slots: TuiPluginApi<CliRenderer>["slots"] = {
|
||||
const slots: TuiPluginApi["slots"] = {
|
||||
register(plugin) {
|
||||
const id = count ? `${base}:${count}` : base
|
||||
count += 1
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CliRenderer } from "@opentui/core"
|
||||
import { type SlotMode, type TuiHostPluginApi, type TuiSlotContext, type TuiSlotMap } from "@opencode-ai/plugin/tui"
|
||||
import { type SlotMode, type TuiPluginApi, type TuiSlotContext, type TuiSlotMap } from "@opencode-ai/plugin/tui"
|
||||
import { createSlot, createSolidSlotRegistry, type JSX, type SolidPlugin } from "@opentui/solid"
|
||||
import { isRecord } from "@/util/record"
|
||||
|
||||
@@ -12,7 +11,7 @@ type SlotProps<K extends keyof TuiSlotMap> = {
|
||||
type Slot = <K extends keyof TuiSlotMap>(props: SlotProps<K>) => JSX.Element | null
|
||||
export type HostSlotPlugin = SolidPlugin<TuiSlotMap, TuiSlotContext>
|
||||
|
||||
export type HostPluginApi = TuiHostPluginApi<CliRenderer>
|
||||
export type HostPluginApi = TuiPluginApi
|
||||
export type HostSlots = {
|
||||
register: (plugin: HostSlotPlugin) => () => void
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ type Opts = {
|
||||
export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
|
||||
const kv: Record<string, unknown> = {}
|
||||
const count = opts.count
|
||||
const ctrl = new AbortController()
|
||||
const own = createOpencodeClient({
|
||||
baseUrl: "http://localhost:4096",
|
||||
})
|
||||
@@ -182,6 +183,15 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
|
||||
},
|
||||
},
|
||||
renderer,
|
||||
slots: {
|
||||
register: () => "fixture-slot",
|
||||
},
|
||||
lifecycle: {
|
||||
signal: ctrl.signal,
|
||||
onDispose() {
|
||||
return () => {}
|
||||
},
|
||||
},
|
||||
command: {
|
||||
register: () => {
|
||||
if (count) count.command_add += 1
|
||||
|
||||
+33
-43
@@ -255,37 +255,6 @@ type Frozen<Value> = Value extends (...args: never[]) => unknown
|
||||
? { readonly [Key in keyof Value]: Frozen<Value[Key]> }
|
||||
: Value
|
||||
|
||||
export type TuiApi = {
|
||||
app: TuiApp
|
||||
command: {
|
||||
register: (cb: () => TuiCommand[]) => () => void
|
||||
trigger: (value: string) => void
|
||||
}
|
||||
route: {
|
||||
register: (routes: TuiRouteDefinition[]) => () => void
|
||||
navigate: (name: string, params?: Record<string, unknown>) => void
|
||||
readonly current: TuiRouteCurrent
|
||||
}
|
||||
ui: {
|
||||
Dialog: (props: TuiDialogProps) => JSX.Element
|
||||
DialogAlert: (props: TuiDialogAlertProps) => JSX.Element
|
||||
DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element
|
||||
DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element
|
||||
DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element
|
||||
toast: (input: TuiToast) => void
|
||||
dialog: TuiDialogStack
|
||||
}
|
||||
keybind: {
|
||||
match: (key: string, evt: ParsedKey) => boolean
|
||||
print: (key: string) => string
|
||||
create: (defaults: TuiKeybindMap, overrides?: Record<string, unknown>) => TuiKeybindSet
|
||||
}
|
||||
readonly tuiConfig: Frozen<TuiConfigView>
|
||||
kv: TuiKV
|
||||
state: TuiState
|
||||
theme: TuiTheme
|
||||
}
|
||||
|
||||
export type TuiSidebarMcpItem = {
|
||||
name: string
|
||||
status: McpStatus["status"]
|
||||
@@ -370,26 +339,47 @@ export type TuiWorkspace = {
|
||||
set: (workspaceID?: string) => void
|
||||
}
|
||||
|
||||
export type TuiHostPluginApi<Renderer = CliRenderer> = TuiApi & {
|
||||
export type TuiPluginApi = {
|
||||
app: TuiApp
|
||||
command: {
|
||||
register: (cb: () => TuiCommand[]) => () => void
|
||||
trigger: (value: string) => void
|
||||
}
|
||||
route: {
|
||||
register: (routes: TuiRouteDefinition[]) => () => void
|
||||
navigate: (name: string, params?: Record<string, unknown>) => void
|
||||
readonly current: TuiRouteCurrent
|
||||
}
|
||||
ui: {
|
||||
Dialog: (props: TuiDialogProps) => JSX.Element
|
||||
DialogAlert: (props: TuiDialogAlertProps) => JSX.Element
|
||||
DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element
|
||||
DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element
|
||||
DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element
|
||||
toast: (input: TuiToast) => void
|
||||
dialog: TuiDialogStack
|
||||
}
|
||||
keybind: {
|
||||
match: (key: string, evt: ParsedKey) => boolean
|
||||
print: (key: string) => string
|
||||
create: (defaults: TuiKeybindMap, overrides?: Record<string, unknown>) => TuiKeybindSet
|
||||
}
|
||||
readonly tuiConfig: Frozen<TuiConfigView>
|
||||
kv: TuiKV
|
||||
state: TuiState
|
||||
theme: TuiTheme
|
||||
client: OpencodeClient
|
||||
scopedClient: (workspaceID?: string) => OpencodeClient
|
||||
workspace: TuiWorkspace
|
||||
event: TuiEventBus
|
||||
renderer: Renderer
|
||||
}
|
||||
|
||||
export type TuiPluginApi<Renderer = CliRenderer> = TuiHostPluginApi<Renderer> & {
|
||||
renderer: CliRenderer
|
||||
slots: TuiSlots
|
||||
lifecycle: TuiLifecycle
|
||||
}
|
||||
|
||||
export type TuiPlugin<Renderer = CliRenderer> = (
|
||||
api: TuiPluginApi<Renderer>,
|
||||
options: PluginOptions | undefined,
|
||||
meta: TuiPluginMeta,
|
||||
) => Promise<void>
|
||||
export type TuiPlugin = (api: TuiPluginApi, options: PluginOptions | undefined, meta: TuiPluginMeta) => Promise<void>
|
||||
|
||||
export type TuiPluginModule<Renderer = CliRenderer> = {
|
||||
export type TuiPluginModule = {
|
||||
server?: Plugin
|
||||
tui?: TuiPlugin<Renderer>
|
||||
tui?: TuiPlugin
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user