chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
import type { Plugin } from "@opencode-ai/plugin"
import { mkdir, rm } from "node:fs/promises"
export const FolderWorkspacePlugin: Plugin = async ({ experimental_workspace }) => {
experimental_workspace.register("folder", {
name: "Folder",
description: "Create a blank folder",
configure(config) {
const rand = "" + Math.random()
return {
...config,
directory: `/tmp/folder/folder-${rand}`,
}
},
async create(config) {
if (!config.directory) return
await mkdir(config.directory, { recursive: true })
},
async remove(config) {
await rm(config.directory!, { recursive: true, force: true })
},
target(config) {
return {
type: "local",
directory: config.directory!,
}
},
})
return {}
}
export default FolderWorkspacePlugin

View File

@@ -0,0 +1,18 @@
import { Plugin } from "./index.js"
import { tool } from "./tool.js"
export const ExamplePlugin: Plugin = async (_ctx) => {
return {
tool: {
mytool: tool({
description: "This is a custom tool",
args: {
foo: tool.schema.string().describe("foo"),
},
async execute(args) {
return `Hello ${args.foo}!`
},
}),
},
}
}

View File

@@ -0,0 +1,333 @@
import type {
Event,
createOpencodeClient,
Project,
Model,
Provider,
Permission,
UserMessage,
Message,
Part,
Auth,
Config as SDKConfig,
} from "@opencode-ai/sdk"
import type { Provider as ProviderV2, Model as ModelV2 } from "@opencode-ai/sdk/v2"
import type { BunShell } from "./shell.js"
import { type ToolDefinition } from "./tool.js"
export * from "./tool.js"
export type ProviderContext = {
source: "env" | "config" | "custom" | "api"
info: Provider
options: Record<string, any>
}
export type WorkspaceInfo = {
id: string
type: string
name: string
branch: string | null
directory: string | null
extra: unknown | null
projectID: string
}
export type WorkspaceTarget =
| {
type: "local"
directory: string
}
| {
type: "remote"
url: string | URL
headers?: HeadersInit
}
export type WorkspaceAdaptor = {
name: string
description: string
configure(config: WorkspaceInfo): WorkspaceInfo | Promise<WorkspaceInfo>
create(config: WorkspaceInfo, env: Record<string, string | undefined>, from?: WorkspaceInfo): Promise<void>
remove(config: WorkspaceInfo): Promise<void>
target(config: WorkspaceInfo): WorkspaceTarget | Promise<WorkspaceTarget>
}
export type PluginInput = {
client: ReturnType<typeof createOpencodeClient>
project: Project
directory: string
worktree: string
experimental_workspace: {
register(type: string, adaptor: WorkspaceAdaptor): void
}
serverUrl: URL
$: BunShell
}
export type PluginOptions = Record<string, unknown>
export type Config = Omit<SDKConfig, "plugin"> & {
plugin?: Array<string | [string, PluginOptions]>
}
export type Plugin = (input: PluginInput, options?: PluginOptions) => Promise<Hooks>
export type PluginModule = {
id?: string
server: Plugin
tui?: never
}
type Rule = {
key: string
op: "eq" | "neq"
value: string
}
export type AuthHook = {
provider: string
loader?: (auth: () => Promise<Auth>, provider: Provider) => Promise<Record<string, any>>
methods: (
| {
type: "oauth"
label: string
prompts?: Array<
| {
type: "text"
key: string
message: string
placeholder?: string
validate?: (value: string) => string | undefined
/** @deprecated Use `when` instead */
condition?: (inputs: Record<string, string>) => boolean
when?: Rule
}
| {
type: "select"
key: string
message: string
options: Array<{
label: string
value: string
hint?: string
}>
/** @deprecated Use `when` instead */
condition?: (inputs: Record<string, string>) => boolean
when?: Rule
}
>
authorize(inputs?: Record<string, string>): Promise<AuthOAuthResult>
}
| {
type: "api"
label: string
prompts?: Array<
| {
type: "text"
key: string
message: string
placeholder?: string
validate?: (value: string) => string | undefined
/** @deprecated Use `when` instead */
condition?: (inputs: Record<string, string>) => boolean
when?: Rule
}
| {
type: "select"
key: string
message: string
options: Array<{
label: string
value: string
hint?: string
}>
/** @deprecated Use `when` instead */
condition?: (inputs: Record<string, string>) => boolean
when?: Rule
}
>
authorize?(inputs?: Record<string, string>): Promise<
| {
type: "success"
key: string
provider?: string
}
| {
type: "failed"
}
>
}
)[]
}
export type AuthOAuthResult = { url: string; instructions: string } & (
| {
method: "auto"
callback(): Promise<
| ({
type: "success"
provider?: string
} & (
| {
refresh: string
access: string
expires: number
accountId?: string
enterpriseUrl?: string
}
| { key: string }
))
| {
type: "failed"
}
>
}
| {
method: "code"
callback(code: string): Promise<
| ({
type: "success"
provider?: string
} & (
| {
refresh: string
access: string
expires: number
accountId?: string
enterpriseUrl?: string
}
| { key: string }
))
| {
type: "failed"
}
>
}
)
export type ProviderHookContext = {
auth?: Auth
}
export type ProviderHook = {
id: string
models?: (provider: ProviderV2, ctx: ProviderHookContext) => Promise<Record<string, ModelV2>>
}
/** @deprecated Use AuthOAuthResult instead. */
export type AuthOuathResult = AuthOAuthResult
export interface Hooks {
event?: (input: { event: Event }) => Promise<void>
config?: (input: Config) => Promise<void>
tool?: {
[key: string]: ToolDefinition
}
auth?: AuthHook
provider?: ProviderHook
/**
* Called when a new message is received
*/
"chat.message"?: (
input: {
sessionID: string
agent?: string
model?: { providerID: string; modelID: string }
messageID?: string
variant?: string
},
output: { message: UserMessage; parts: Part[] },
) => Promise<void>
/**
* Modify parameters sent to LLM
*/
"chat.params"?: (
input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage },
output: {
temperature: number
topP: number
topK: number
maxOutputTokens: number | undefined
options: Record<string, any>
},
) => Promise<void>
"chat.headers"?: (
input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage },
output: { headers: Record<string, string> },
) => Promise<void>
"permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void>
"command.execute.before"?: (
input: { command: string; sessionID: string; arguments: string },
output: { parts: Part[] },
) => Promise<void>
"tool.execute.before"?: (
input: { tool: string; sessionID: string; callID: string },
output: { args: any },
) => Promise<void>
"shell.env"?: (
input: { cwd: string; sessionID?: string; callID?: string },
output: { env: Record<string, string> },
) => Promise<void>
"tool.execute.after"?: (
input: { tool: string; sessionID: string; callID: string; args: any },
output: {
title: string
output: string
metadata: any
},
) => Promise<void>
"experimental.chat.messages.transform"?: (
input: {},
output: {
messages: {
info: Message
parts: Part[]
}[]
},
) => Promise<void>
"experimental.chat.system.transform"?: (
input: { sessionID?: string; model: Model },
output: {
system: string[]
},
) => Promise<void>
/**
* Called before session compaction starts. Allows plugins to customize
* the compaction prompt.
*
* - `context`: Additional context strings appended to the default prompt
* - `prompt`: If set, replaces the default compaction prompt entirely
*/
"experimental.session.compacting"?: (
input: { sessionID: string },
output: { context: string[]; prompt?: string },
) => Promise<void>
/**
* Called after compaction succeeds and before a synthetic user
* auto-continue message is added.
*
* - `enabled`: Defaults to `true`. Set to `false` to skip the synthetic
* user "continue" turn.
*/
"experimental.compaction.autocontinue"?: (
input: {
sessionID: string
agent: string
model: Model
provider: ProviderContext
message: UserMessage
overflow: boolean
},
output: { enabled: boolean },
) => Promise<void>
"experimental.text.complete"?: (
input: { sessionID: string; messageID: string; partID: string },
output: { text: string },
) => Promise<void>
/**
* Modify tool definitions (description and parameters) sent to LLM
*/
"tool.definition"?: (input: { toolID: string }, output: { description: string; parameters: any }) => Promise<void>
}

View File

@@ -0,0 +1,136 @@
export type ShellFunction = (input: Uint8Array) => Uint8Array
export type ShellExpression =
| { toString(): string }
| Array<ShellExpression>
| string
| { raw: string }
| ReadableStream
export interface BunShell {
(strings: TemplateStringsArray, ...expressions: ShellExpression[]): BunShellPromise
/**
* Perform bash-like brace expansion on the given pattern.
* @param pattern - Brace pattern to expand
*/
braces(pattern: string): string[]
/**
* Escape strings for input into shell commands.
*/
escape(input: string): string
/**
* Change the default environment variables for shells created by this instance.
*/
env(newEnv?: Record<string, string | undefined>): BunShell
/**
* Default working directory to use for shells created by this instance.
*/
cwd(newCwd?: string): BunShell
/**
* Configure the shell to not throw an exception on non-zero exit codes.
*/
nothrow(): BunShell
/**
* Configure whether or not the shell should throw an exception on non-zero exit codes.
*/
throws(shouldThrow: boolean): BunShell
}
export interface BunShellPromise extends Promise<BunShellOutput> {
readonly stdin: WritableStream
/**
* Change the current working directory of the shell.
*/
cwd(newCwd: string): this
/**
* Set environment variables for the shell.
*/
env(newEnv: Record<string, string> | undefined): this
/**
* By default, the shell will write to the current process's stdout and stderr, as well as buffering that output.
* This configures the shell to only buffer the output.
*/
quiet(): this
/**
* Read from stdout as a string, line by line
* Automatically calls quiet() to disable echoing to stdout.
*/
lines(): AsyncIterable<string>
/**
* Read from stdout as a string.
* Automatically calls quiet() to disable echoing to stdout.
*/
text(encoding?: BufferEncoding): Promise<string>
/**
* Read from stdout as a JSON object
* Automatically calls quiet()
*/
json(): Promise<any>
/**
* Read from stdout as an ArrayBuffer
* Automatically calls quiet()
*/
arrayBuffer(): Promise<ArrayBuffer>
/**
* Read from stdout as a Blob
* Automatically calls quiet()
*/
blob(): Promise<Blob>
/**
* Configure the shell to not throw an exception on non-zero exit codes.
*/
nothrow(): this
/**
* Configure whether or not the shell should throw an exception on non-zero exit codes.
*/
throws(shouldThrow: boolean): this
}
export interface BunShellOutput {
readonly stdout: Buffer
readonly stderr: Buffer
readonly exitCode: number
/**
* Read from stdout as a string
*/
text(encoding?: BufferEncoding): string
/**
* Read from stdout as a JSON object
*/
json(): any
/**
* Read from stdout as an ArrayBuffer
*/
arrayBuffer(): ArrayBuffer
/**
* Read from stdout as an Uint8Array
*/
bytes(): Uint8Array
/**
* Read from stdout as a Blob
*/
blob(): Blob
}
export type BunShellError = Error & BunShellOutput

View File

@@ -0,0 +1,41 @@
import { z } from "zod"
import { Effect } from "effect"
export type ToolContext = {
sessionID: string
messageID: string
agent: string
/**
* Current project directory for this session.
* Prefer this over process.cwd() when resolving relative paths.
*/
directory: string
/**
* Project worktree root for this session.
* Useful for generating stable relative paths (e.g. path.relative(worktree, absPath)).
*/
worktree: string
abort: AbortSignal
metadata(input: { title?: string; metadata?: { [key: string]: any } }): void
ask(input: AskInput): Effect.Effect<void>
}
type AskInput = {
permission: string
patterns: string[]
always: string[]
metadata: { [key: string]: any }
}
export type ToolResult = string | { output: string; metadata?: { [key: string]: any } }
export function tool<Args extends z.ZodRawShape>(input: {
description: string
args: Args
execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<ToolResult>
}) {
return input
}
tool.schema = z
export type ToolDefinition = ReturnType<typeof tool>

View File

@@ -0,0 +1,501 @@
import type {
AgentPart,
OpencodeClient,
Event,
FilePart,
LspStatus,
McpStatus,
Todo,
Message,
Part,
Provider,
PermissionRequest,
QuestionRequest,
SessionStatus,
TextPart,
Config as SdkConfig,
} from "@opencode-ai/sdk/v2"
import type { CliRenderer, ParsedKey, RGBA, SlotMode } from "@opentui/core"
import type { JSX, SolidPlugin } from "@opentui/solid"
import type { Config as PluginConfig, PluginOptions } from "./index.js"
export type { CliRenderer, SlotMode } from "@opentui/core"
export type TuiRouteCurrent =
| {
name: "home"
}
| {
name: "session"
params: {
sessionID: string
prompt?: unknown
}
}
| {
name: string
params?: Record<string, unknown>
}
export type TuiRouteDefinition = {
name: string
render: (input: { params?: Record<string, unknown> }) => JSX.Element
}
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 TuiKeybindMap = Record<string, string>
export type TuiKeybindSet = {
readonly all: TuiKeybindMap
get: (name: string) => string
match: (name: string, evt: ParsedKey) => boolean
print: (name: string) => string
}
export type TuiDialogProps = {
size?: "medium" | "large" | "xlarge"
onClose: () => void
children?: JSX.Element
}
export type TuiDialogStack = {
replace: (render: () => JSX.Element, onClose?: () => void) => void
clear: () => void
setSize: (size: "medium" | "large" | "xlarge") => void
readonly size: "medium" | "large" | "xlarge"
readonly depth: number
readonly open: boolean
}
export type TuiDialogAlertProps = {
title: string
message: string
onConfirm?: () => void
}
export type TuiDialogConfirmProps = {
title: string
message: string
onConfirm?: () => void
onCancel?: () => void
}
export type TuiDialogPromptProps = {
title: string
description?: () => JSX.Element
placeholder?: string
value?: string
busy?: boolean
busyText?: string
onConfirm?: (value: string) => void
onCancel?: () => void
}
export type TuiDialogSelectOption<Value = unknown> = {
title: string
value: Value
description?: string
footer?: JSX.Element | string
category?: string
disabled?: boolean
onSelect?: () => void
}
export type TuiDialogSelectProps<Value = unknown> = {
title: string
placeholder?: string
options: TuiDialogSelectOption<Value>[]
flat?: boolean
onMove?: (option: TuiDialogSelectOption<Value>) => void
onFilter?: (query: string) => void
onSelect?: (option: TuiDialogSelectOption<Value>) => void
skipFilter?: boolean
current?: Value
}
export type TuiPromptInfo = {
input: string
mode?: "normal" | "shell"
parts: (
| Omit<FilePart, "id" | "messageID" | "sessionID">
| Omit<AgentPart, "id" | "messageID" | "sessionID">
| (Omit<TextPart, "id" | "messageID" | "sessionID"> & {
source?: {
text: {
start: number
end: number
value: string
}
}
})
)[]
}
export type TuiPromptRef = {
focused: boolean
current: TuiPromptInfo
set(prompt: TuiPromptInfo): void
reset(): void
blur(): void
focus(): void
submit(): void
}
export type TuiPromptProps = {
sessionID?: string
workspaceID?: string
visible?: boolean
disabled?: boolean
onSubmit?: () => void
ref?: (ref: TuiPromptRef | undefined) => void
hint?: JSX.Element
right?: JSX.Element
showPlaceholder?: boolean
placeholders?: {
normal?: string[]
shell?: string[]
}
}
export type TuiToast = {
variant?: "info" | "success" | "warning" | "error"
title?: string
message: string
duration?: number
}
export type TuiThemeCurrent = {
readonly primary: RGBA
readonly secondary: RGBA
readonly accent: RGBA
readonly error: RGBA
readonly warning: RGBA
readonly success: RGBA
readonly info: RGBA
readonly text: RGBA
readonly textMuted: RGBA
readonly selectedListItemText: RGBA
readonly background: RGBA
readonly backgroundPanel: RGBA
readonly backgroundElement: RGBA
readonly backgroundMenu: RGBA
readonly border: RGBA
readonly borderActive: RGBA
readonly borderSubtle: RGBA
readonly diffAdded: RGBA
readonly diffRemoved: RGBA
readonly diffContext: RGBA
readonly diffHunkHeader: RGBA
readonly diffHighlightAdded: RGBA
readonly diffHighlightRemoved: RGBA
readonly diffAddedBg: RGBA
readonly diffRemovedBg: RGBA
readonly diffContextBg: RGBA
readonly diffLineNumber: RGBA
readonly diffAddedLineNumberBg: RGBA
readonly diffRemovedLineNumberBg: RGBA
readonly markdownText: RGBA
readonly markdownHeading: RGBA
readonly markdownLink: RGBA
readonly markdownLinkText: RGBA
readonly markdownCode: RGBA
readonly markdownBlockQuote: RGBA
readonly markdownEmph: RGBA
readonly markdownStrong: RGBA
readonly markdownHorizontalRule: RGBA
readonly markdownListItem: RGBA
readonly markdownListEnumeration: RGBA
readonly markdownImage: RGBA
readonly markdownImageText: RGBA
readonly markdownCodeBlock: RGBA
readonly syntaxComment: RGBA
readonly syntaxKeyword: RGBA
readonly syntaxFunction: RGBA
readonly syntaxVariable: RGBA
readonly syntaxString: RGBA
readonly syntaxNumber: RGBA
readonly syntaxType: RGBA
readonly syntaxOperator: RGBA
readonly syntaxPunctuation: RGBA
readonly thinkingOpacity: number
}
export type TuiTheme = {
readonly current: TuiThemeCurrent
readonly selected: string
has: (name: string) => boolean
set: (name: string) => boolean
install: (jsonPath: string) => Promise<void>
mode: () => "dark" | "light"
readonly ready: boolean
}
export type TuiKV = {
get: <Value = unknown>(key: string, fallback?: Value) => Value
set: (key: string, value: unknown) => void
readonly ready: boolean
}
export type TuiState = {
readonly ready: boolean
readonly config: SdkConfig
readonly provider: ReadonlyArray<Provider>
readonly path: {
state: string
config: string
worktree: string
directory: string
}
readonly vcs: { branch?: string } | undefined
session: {
count: () => number
diff: (sessionID: string) => ReadonlyArray<TuiSidebarFileItem>
todo: (sessionID: string) => ReadonlyArray<TuiSidebarTodoItem>
messages: (sessionID: string) => ReadonlyArray<Message>
status: (sessionID: string) => SessionStatus | undefined
permission: (sessionID: string) => ReadonlyArray<PermissionRequest>
question: (sessionID: string) => ReadonlyArray<QuestionRequest>
}
part: (messageID: string) => ReadonlyArray<Part>
lsp: () => ReadonlyArray<TuiSidebarLspItem>
mcp: () => ReadonlyArray<TuiSidebarMcpItem>
}
type TuiConfigView = Pick<PluginConfig, "$schema" | "theme" | "keybinds" | "plugin"> &
NonNullable<PluginConfig["tui"]> & {
plugin_enabled?: Record<string, boolean>
}
export type TuiApp = {
readonly version: string
}
type Frozen<Value> = Value extends (...args: never[]) => unknown
? Value
: Value extends ReadonlyArray<infer Item>
? ReadonlyArray<Frozen<Item>>
: Value extends object
? { readonly [Key in keyof Value]: Frozen<Value[Key]> }
: Value
export type TuiSidebarMcpItem = {
name: string
status: McpStatus["status"]
error?: string
}
export type TuiSidebarLspItem = Pick<LspStatus, "id" | "root" | "status">
export type TuiSidebarTodoItem = Pick<Todo, "content" | "status">
export type TuiSidebarFileItem = {
file: string
additions: number
deletions: number
}
export type TuiHostSlotMap = {
app: {}
home_logo: {}
home_prompt: {
workspace_id?: string
ref?: (ref: TuiPromptRef | undefined) => void
}
home_prompt_right: {
workspace_id?: string
}
session_prompt: {
session_id: string
visible?: boolean
disabled?: boolean
on_submit?: () => void
ref?: (ref: TuiPromptRef | undefined) => void
}
session_prompt_right: {
session_id: string
}
home_bottom: {}
home_footer: {}
sidebar_title: {
session_id: string
title: string
share_url?: string
}
sidebar_content: {
session_id: string
}
sidebar_footer: {
session_id: string
}
}
export type TuiSlotMap<Slots extends Record<string, object> = {}> = TuiHostSlotMap & Slots
type TuiSlotShape<Name extends string, Slots extends Record<string, object>> = Name extends keyof TuiHostSlotMap
? TuiHostSlotMap[Name]
: Name extends keyof Slots
? Slots[Name]
: Record<string, unknown>
export type TuiSlotProps<Name extends string = string, Slots extends Record<string, object> = {}> = {
name: Name
mode?: SlotMode
children?: JSX.Element
} & TuiSlotShape<Name, Slots>
export type TuiSlotContext = {
theme: TuiTheme
}
type SlotCore<Slots extends Record<string, object> = {}> = SolidPlugin<TuiSlotMap<Slots>, TuiSlotContext>
export type TuiSlotPlugin<Slots extends Record<string, object> = {}> = Omit<SlotCore<Slots>, "id"> & {
id?: never
}
export type TuiSlots = {
register: {
(plugin: TuiSlotPlugin): string
<Slots extends Record<string, object>>(plugin: TuiSlotPlugin<Slots>): string
}
}
export type TuiEventBus = {
on: <Type extends Event["type"]>(type: Type, handler: (event: Extract<Event, { type: Type }>) => void) => () => void
}
export type TuiDispose = () => void | Promise<void>
export type TuiLifecycle = {
readonly signal: AbortSignal
onDispose: (fn: TuiDispose) => () => void
}
export type TuiPluginState = "first" | "updated" | "same"
export type TuiPluginEntry = {
id: string
source: "file" | "npm" | "internal"
spec: string
target: string
requested?: string
version?: string
modified?: number
first_time: number
last_time: number
time_changed: number
load_count: number
fingerprint: string
}
export type TuiPluginMeta = TuiPluginEntry & {
state: TuiPluginState
}
export type TuiPluginStatus = {
id: string
source: TuiPluginEntry["source"]
spec: string
target: string
enabled: boolean
active: boolean
}
export type TuiPluginInstallOptions = {
global?: boolean
}
export type TuiPluginInstallResult =
| {
ok: true
dir: string
tui: boolean
}
| {
ok: false
message: string
missing?: boolean
}
export type TuiWorkspace = {
current: () => string | undefined
set: (workspaceID?: string) => void
}
export type TuiPluginApi = {
app: TuiApp
command: {
register: (cb: () => TuiCommand[]) => () => void
trigger: (value: string) => void
show: () => 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
Slot: <Name extends string>(props: TuiSlotProps<Name>) => JSX.Element | null
Prompt: (props: TuiPromptProps) => 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
event: TuiEventBus
renderer: CliRenderer
slots: TuiSlots
plugins: {
list: () => ReadonlyArray<TuiPluginStatus>
activate: (id: string) => Promise<boolean>
deactivate: (id: string) => Promise<boolean>
add: (spec: string) => Promise<boolean>
install: (spec: string, options?: TuiPluginInstallOptions) => Promise<TuiPluginInstallResult>
}
lifecycle: TuiLifecycle
}
export type TuiPlugin = (api: TuiPluginApi, options: PluginOptions | undefined, meta: TuiPluginMeta) => Promise<void>
export type TuiPluginModule = {
id?: string
tui: TuiPlugin
server?: never
}