tulingcode-plugin 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "tulingcode-plugin",
4
- "version": "0.3.0",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
@@ -17,10 +17,10 @@
17
17
  "./tui": "./src/tui.ts"
18
18
  },
19
19
  "files": [
20
- "dist"
20
+ "src"
21
21
  ],
22
22
  "dependencies": {
23
- "tulingcode-sdk": "0.1.0",
23
+ "tulingcode-sdk": "0.2.0",
24
24
  "effect": "4.0.0-beta.48",
25
25
  "zod": "4.1.8"
26
26
  },
@@ -0,0 +1,34 @@
1
+ import type { Plugin } from "@tuling-ai/plugin"
2
+ import { mkdir, rm } from "node:fs/promises"
3
+
4
+ export const FolderWorkspacePlugin: Plugin = async ({ experimental_workspace }) => {
5
+ experimental_workspace.register("folder", {
6
+ name: "Folder",
7
+ description: "Create a blank folder",
8
+ configure(config) {
9
+ const rand = "" + Math.random()
10
+
11
+ return {
12
+ ...config,
13
+ directory: `/tmp/folder/folder-${rand}`,
14
+ }
15
+ },
16
+ async create(config) {
17
+ if (!config.directory) return
18
+ await mkdir(config.directory, { recursive: true })
19
+ },
20
+ async remove(config) {
21
+ await rm(config.directory!, { recursive: true, force: true })
22
+ },
23
+ target(config) {
24
+ return {
25
+ type: "local",
26
+ directory: config.directory!,
27
+ }
28
+ },
29
+ })
30
+
31
+ return {}
32
+ }
33
+
34
+ export default FolderWorkspacePlugin
package/src/example.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { Plugin } from "./index.js"
2
+ import { tool } from "./tool.js"
3
+
4
+ export const ExamplePlugin: Plugin = async (_ctx) => {
5
+ return {
6
+ tool: {
7
+ mytool: tool({
8
+ description: "This is a custom tool",
9
+ args: {
10
+ foo: tool.schema.string().describe("foo"),
11
+ },
12
+ async execute(args) {
13
+ return `Hello ${args.foo}!`
14
+ },
15
+ }),
16
+ },
17
+ }
18
+ }
package/src/index.ts ADDED
@@ -0,0 +1,428 @@
1
+ import type {
2
+ Event,
3
+ createTulingcodeClient,
4
+ Project,
5
+ Model,
6
+ Provider,
7
+ Permission,
8
+ UserMessage,
9
+ Message,
10
+ Part,
11
+ Auth,
12
+ Config as SDKConfig,
13
+ } from "@tuling-ai/sdk"
14
+ import type { Provider as ProviderV2, Model as ModelV2 } from "@tuling-ai/sdk/v2"
15
+
16
+ import type { BunShell } from "./shell.js"
17
+ import { type ToolDefinition } from "./tool.js"
18
+
19
+ export * from "./tool.js"
20
+
21
+ export type ProviderContext = {
22
+ source: "env" | "config" | "custom" | "api"
23
+ info: Provider
24
+ options: Record<string, any>
25
+ }
26
+
27
+ export type WorkspaceInfo = {
28
+ id: string
29
+ type: string
30
+ name: string
31
+ branch: string | null
32
+ directory: string | null
33
+ extra: unknown | null
34
+ projectID: string
35
+ }
36
+
37
+ export type WorkspaceTarget =
38
+ | {
39
+ type: "local"
40
+ directory: string
41
+ }
42
+ | {
43
+ type: "remote"
44
+ url: string | URL
45
+ headers?: HeadersInit
46
+ }
47
+
48
+ export type WorkspaceAdaptor = {
49
+ name: string
50
+ description: string
51
+ configure(config: WorkspaceInfo): WorkspaceInfo | Promise<WorkspaceInfo>
52
+ create(config: WorkspaceInfo, env: Record<string, string | undefined>, from?: WorkspaceInfo): Promise<void>
53
+ remove(config: WorkspaceInfo): Promise<void>
54
+ target(config: WorkspaceInfo): WorkspaceTarget | Promise<WorkspaceTarget>
55
+ }
56
+
57
+ export type PluginInput = {
58
+ client: ReturnType<typeof createTulingcodeClient>
59
+ project: Project
60
+ directory: string
61
+ worktree: string
62
+ experimental_workspace: {
63
+ register(type: string, adaptor: WorkspaceAdaptor): void
64
+ }
65
+ serverUrl: URL
66
+ $: BunShell
67
+ }
68
+
69
+ export type PluginOptions = Record<string, unknown>
70
+
71
+ export type Config = Omit<SDKConfig, "plugin"> & {
72
+ plugin?: Array<string | [string, PluginOptions]>
73
+ }
74
+
75
+ export type Plugin = (input: PluginInput, options?: PluginOptions) => Promise<Hooks>
76
+
77
+ export type PluginModule = {
78
+ id?: string
79
+ server: Plugin
80
+ tui?: never
81
+ }
82
+
83
+ type Rule = {
84
+ key: string
85
+ op: "eq" | "neq"
86
+ value: string
87
+ }
88
+
89
+ export type AuthHook = {
90
+ provider: string
91
+ loader?: (auth: () => Promise<Auth>, provider: Provider) => Promise<Record<string, any>>
92
+ methods: (
93
+ | {
94
+ type: "oauth"
95
+ label: string
96
+ prompts?: Array<
97
+ | {
98
+ type: "text"
99
+ key: string
100
+ message: string
101
+ placeholder?: string
102
+ validate?: (value: string) => string | undefined
103
+ /** @deprecated Use `when` instead */
104
+ condition?: (inputs: Record<string, string>) => boolean
105
+ when?: Rule
106
+ }
107
+ | {
108
+ type: "select"
109
+ key: string
110
+ message: string
111
+ options: Array<{
112
+ label: string
113
+ value: string
114
+ hint?: string
115
+ }>
116
+ /** @deprecated Use `when` instead */
117
+ condition?: (inputs: Record<string, string>) => boolean
118
+ when?: Rule
119
+ }
120
+ >
121
+ authorize(inputs?: Record<string, string>): Promise<AuthOAuthResult>
122
+ }
123
+ | {
124
+ type: "api"
125
+ label: string
126
+ prompts?: Array<
127
+ | {
128
+ type: "text"
129
+ key: string
130
+ message: string
131
+ placeholder?: string
132
+ validate?: (value: string) => string | undefined
133
+ /** @deprecated Use `when` instead */
134
+ condition?: (inputs: Record<string, string>) => boolean
135
+ when?: Rule
136
+ }
137
+ | {
138
+ type: "select"
139
+ key: string
140
+ message: string
141
+ options: Array<{
142
+ label: string
143
+ value: string
144
+ hint?: string
145
+ }>
146
+ /** @deprecated Use `when` instead */
147
+ condition?: (inputs: Record<string, string>) => boolean
148
+ when?: Rule
149
+ }
150
+ >
151
+ authorize?(inputs?: Record<string, string>): Promise<
152
+ | {
153
+ type: "success"
154
+ key: string
155
+ provider?: string
156
+ }
157
+ | {
158
+ type: "failed"
159
+ }
160
+ >
161
+ }
162
+ )[]
163
+ }
164
+
165
+ export type AuthOAuthResult = { url: string; instructions: string } & (
166
+ | {
167
+ method: "auto"
168
+ callback(code?: string): Promise<
169
+ | ({
170
+ type: "success"
171
+ provider?: string
172
+ metadata?: Record<string, string>
173
+ } & (
174
+ | {
175
+ refresh: string
176
+ access: string
177
+ expires: number
178
+ accountId?: string
179
+ enterpriseUrl?: string
180
+ }
181
+ | { key: string }
182
+ ))
183
+ | {
184
+ type: "failed"
185
+ }
186
+ >
187
+ }
188
+ | {
189
+ method: "code"
190
+ callback(code: string): Promise<
191
+ | ({
192
+ type: "success"
193
+ provider?: string
194
+ metadata?: Record<string, string>
195
+ } & (
196
+ | {
197
+ refresh: string
198
+ access: string
199
+ expires: number
200
+ accountId?: string
201
+ enterpriseUrl?: string
202
+ }
203
+ | { key: string }
204
+ ))
205
+ | {
206
+ type: "failed"
207
+ }
208
+ >
209
+ }
210
+ )
211
+
212
+ export type ProviderHookContext = {
213
+ auth?: Auth
214
+ }
215
+
216
+ export type ProviderHook = {
217
+ id: string
218
+ models?: (provider: ProviderV2, ctx: ProviderHookContext) => Promise<Record<string, ModelV2>>
219
+ }
220
+
221
+ /** @deprecated Use AuthOAuthResult instead. */
222
+ export type AuthOuathResult = AuthOAuthResult
223
+
224
+ // ----- Actor lifecycle hook types -----
225
+
226
+ /**
227
+ * Agent types excluded from actor.preStop / actor.postStop by default.
228
+ * Includes the agents registered in src/agent/agent.ts plus runtime-only
229
+ * pseudo-agents ("main" = root actor, "compaction" = auto-compaction subsystem)
230
+ * that are never spawned via the agent registry.
231
+ */
232
+ export const BUILT_IN_AGENTS = [
233
+ "main", "general", "build", "explore", "summary",
234
+ "title", "checkpoint-writer", "dream", "distill", "compaction",
235
+ ] as const
236
+
237
+ export type BuiltInAgent = (typeof BUILT_IN_AGENTS)[number]
238
+ export type ActorMode = "subagent" | "peer"
239
+ export type ActorLifecycle = "ephemeral" | "persistent"
240
+ export type ActorOutcome = "success" | "failure" | "cancelled"
241
+
242
+ export type ActorMatcher = {
243
+ mode?: ActorMode
244
+ agentType?:
245
+ | string
246
+ | string[]
247
+ | { include: string[]; exclude?: string[] }
248
+ | { excludeOnly: string[] } // matches every agent (incl. built-ins) except those listed
249
+ }
250
+
251
+ export type ActorStopBaseInput = {
252
+ sessionID: string
253
+ /** Parent session id when the actor runs in a child session (e.g. checkpoint-writer
254
+ * runs under a child session keyed on parent_id). Undefined when sessionID === parent.
255
+ * Plugins that re-derive paths from sessionID should read `parentSessionID ?? sessionID`. */
256
+ parentSessionID?: string
257
+ actorID: string
258
+ parentActorID?: string
259
+ agentType: string
260
+ mode: ActorMode
261
+ lifecycle: ActorLifecycle
262
+ task: string
263
+ description?: string
264
+ finalText?: string
265
+ task_id?: string // Spec ②: if set, postStop hooks can validate tasks/<task_id>/progress.md
266
+ iteration: number
267
+ }
268
+
269
+ export type ActorPreStopInput = ActorStopBaseInput
270
+
271
+ export type ActorPostStopInput = ActorStopBaseInput & {
272
+ outcome: ActorOutcome
273
+ error?: string // outcome === "failure" 时存在
274
+ // false → the spawned agent cannot use the Write tool (read-only, e.g. explore).
275
+ // Absent/undefined → unknown; hooks must NOT suppress on absence (fail-open).
276
+ canWrite?: boolean
277
+ }
278
+
279
+ export type ActorStopOutput = {
280
+ continue?: boolean
281
+ reason?: string
282
+ }
283
+
284
+ export type ActorPreStopHook = (
285
+ input: ActorPreStopInput,
286
+ output: ActorStopOutput,
287
+ ) => Promise<void>
288
+
289
+ export type ActorPostStopHook = (
290
+ input: ActorPostStopInput,
291
+ output: ActorStopOutput,
292
+ ) => Promise<void>
293
+
294
+ export type ActorPreStopRegistration =
295
+ | ActorPreStopHook
296
+ | { matcher?: ActorMatcher; run: ActorPreStopHook }
297
+
298
+ export type ActorPostStopRegistration =
299
+ | ActorPostStopHook
300
+ | { matcher?: ActorMatcher; run: ActorPostStopHook }
301
+
302
+ export interface Hooks {
303
+ event?: (input: { event: Event }) => Promise<void>
304
+ config?: (input: Config) => Promise<void>
305
+ tool?: {
306
+ [key: string]: ToolDefinition
307
+ }
308
+ auth?: AuthHook
309
+ provider?: ProviderHook
310
+ /**
311
+ * Called when a new message is received
312
+ */
313
+ "chat.message"?: (
314
+ input: {
315
+ sessionID: string
316
+ agent?: string
317
+ model?: { providerID: string; modelID: string }
318
+ messageID?: string
319
+ variant?: string
320
+ },
321
+ output: { message: UserMessage; parts: Part[] },
322
+ ) => Promise<void>
323
+ /**
324
+ * Modify parameters sent to LLM
325
+ */
326
+ "chat.params"?: (
327
+ input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage },
328
+ output: {
329
+ temperature: number
330
+ topP: number
331
+ topK: number
332
+ maxOutputTokens: number | undefined
333
+ options: Record<string, any>
334
+ },
335
+ ) => Promise<void>
336
+ "chat.headers"?: (
337
+ input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage },
338
+ output: { headers: Record<string, string> },
339
+ ) => Promise<void>
340
+ "permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void>
341
+ "command.execute.before"?: (
342
+ input: { command: string; sessionID: string; arguments: string },
343
+ output: { parts: Part[] },
344
+ ) => Promise<void>
345
+ "tool.execute.before"?: (
346
+ input: { tool: string; sessionID: string; callID: string },
347
+ output: { args: any },
348
+ ) => Promise<void>
349
+ "shell.env"?: (
350
+ input: { cwd: string; sessionID?: string; callID?: string },
351
+ output: { env: Record<string, string> },
352
+ ) => Promise<void>
353
+ "tool.execute.after"?: (
354
+ input: { tool: string; sessionID: string; callID: string; args: any },
355
+ output: {
356
+ title: string
357
+ output: string
358
+ metadata: any
359
+ },
360
+ ) => Promise<void>
361
+ "experimental.chat.messages.transform"?: (
362
+ input: {},
363
+ output: {
364
+ messages: {
365
+ info: Message
366
+ parts: Part[]
367
+ }[]
368
+ },
369
+ ) => Promise<void>
370
+ "experimental.chat.system.transform"?: (
371
+ input: { sessionID?: string; model: Model },
372
+ output: {
373
+ system: string[]
374
+ },
375
+ ) => Promise<void>
376
+ /**
377
+ * Called before session compaction starts. Allows plugins to customize
378
+ * the compaction prompt.
379
+ *
380
+ * - `context`: Additional context strings appended to the default prompt
381
+ * - `prompt`: If set, replaces the default compaction prompt entirely
382
+ */
383
+ "experimental.session.compacting"?: (
384
+ input: { sessionID: string },
385
+ output: { context: string[]; prompt?: string },
386
+ ) => Promise<void>
387
+ /**
388
+ * Called after compaction succeeds and before a synthetic user
389
+ * auto-continue message is added.
390
+ *
391
+ * - `enabled`: Defaults to `true`. Set to `false` to skip the synthetic
392
+ * user "continue" turn.
393
+ */
394
+ "experimental.compaction.autocontinue"?: (
395
+ input: {
396
+ sessionID: string
397
+ agent: string
398
+ model: Model
399
+ provider: ProviderContext
400
+ message: UserMessage
401
+ overflow: boolean
402
+ },
403
+ output: { enabled: boolean },
404
+ ) => Promise<void>
405
+ "experimental.text.complete"?: (
406
+ input: { sessionID: string; messageID: string; partID: string },
407
+ output: { text: string },
408
+ ) => Promise<void>
409
+ /**
410
+ * Modify tool definitions (description and parameters) sent to LLM
411
+ */
412
+ "tool.definition"?: (input: { toolID: string }, output: { description: string; parameters: any }) => Promise<void>
413
+ /**
414
+ * Fires when an actor (subagent or peer) is about to deliver finalText to its caller.
415
+ * Set `output.continue = true` with `output.reason = "..."` to inject the reason as a
416
+ * synthetic user message and have the actor run another turn before delivery.
417
+ * Default matcher excludes BUILT_IN_AGENTS.
418
+ */
419
+ "actor.preStop"?: ActorPreStopRegistration
420
+ /**
421
+ * Fires AFTER an actor has delivered finalText to its caller. The actor stays alive
422
+ * until the postStop chain ends. Like preStop, can return continue=true with reason
423
+ * to make the actor run another turn — but the new finalText does NOT propagate to
424
+ * the caller (the caller already got finalText_locked at delivery time).
425
+ * Default matcher excludes BUILT_IN_AGENTS.
426
+ */
427
+ "actor.postStop"?: ActorPostStopRegistration
428
+ }
package/src/shell.ts ADDED
@@ -0,0 +1,136 @@
1
+ export type ShellFunction = (input: Uint8Array) => Uint8Array
2
+
3
+ export type ShellExpression =
4
+ | { toString(): string }
5
+ | Array<ShellExpression>
6
+ | string
7
+ | { raw: string }
8
+ | ReadableStream
9
+
10
+ export interface BunShell {
11
+ (strings: TemplateStringsArray, ...expressions: ShellExpression[]): BunShellPromise
12
+
13
+ /**
14
+ * Perform bash-like brace expansion on the given pattern.
15
+ * @param pattern - Brace pattern to expand
16
+ */
17
+ braces(pattern: string): string[]
18
+
19
+ /**
20
+ * Escape strings for input into shell commands.
21
+ */
22
+ escape(input: string): string
23
+
24
+ /**
25
+ * Change the default environment variables for shells created by this instance.
26
+ */
27
+ env(newEnv?: Record<string, string | undefined>): BunShell
28
+
29
+ /**
30
+ * Default working directory to use for shells created by this instance.
31
+ */
32
+ cwd(newCwd?: string): BunShell
33
+
34
+ /**
35
+ * Configure the shell to not throw an exception on non-zero exit codes.
36
+ */
37
+ nothrow(): BunShell
38
+
39
+ /**
40
+ * Configure whether or not the shell should throw an exception on non-zero exit codes.
41
+ */
42
+ throws(shouldThrow: boolean): BunShell
43
+ }
44
+
45
+ export interface BunShellPromise extends Promise<BunShellOutput> {
46
+ readonly stdin: WritableStream
47
+
48
+ /**
49
+ * Change the current working directory of the shell.
50
+ */
51
+ cwd(newCwd: string): this
52
+
53
+ /**
54
+ * Set environment variables for the shell.
55
+ */
56
+ env(newEnv: Record<string, string> | undefined): this
57
+
58
+ /**
59
+ * By default, the shell will write to the current process's stdout and stderr, as well as buffering that output.
60
+ * This configures the shell to only buffer the output.
61
+ */
62
+ quiet(): this
63
+
64
+ /**
65
+ * Read from stdout as a string, line by line
66
+ * Automatically calls quiet() to disable echoing to stdout.
67
+ */
68
+ lines(): AsyncIterable<string>
69
+
70
+ /**
71
+ * Read from stdout as a string.
72
+ * Automatically calls quiet() to disable echoing to stdout.
73
+ */
74
+ text(encoding?: BufferEncoding): Promise<string>
75
+
76
+ /**
77
+ * Read from stdout as a JSON object
78
+ * Automatically calls quiet()
79
+ */
80
+ json(): Promise<any>
81
+
82
+ /**
83
+ * Read from stdout as an ArrayBuffer
84
+ * Automatically calls quiet()
85
+ */
86
+ arrayBuffer(): Promise<ArrayBuffer>
87
+
88
+ /**
89
+ * Read from stdout as a Blob
90
+ * Automatically calls quiet()
91
+ */
92
+ blob(): Promise<Blob>
93
+
94
+ /**
95
+ * Configure the shell to not throw an exception on non-zero exit codes.
96
+ */
97
+ nothrow(): this
98
+
99
+ /**
100
+ * Configure whether or not the shell should throw an exception on non-zero exit codes.
101
+ */
102
+ throws(shouldThrow: boolean): this
103
+ }
104
+
105
+ export interface BunShellOutput {
106
+ readonly stdout: Buffer
107
+ readonly stderr: Buffer
108
+ readonly exitCode: number
109
+
110
+ /**
111
+ * Read from stdout as a string
112
+ */
113
+ text(encoding?: BufferEncoding): string
114
+
115
+ /**
116
+ * Read from stdout as a JSON object
117
+ */
118
+ json(): any
119
+
120
+ /**
121
+ * Read from stdout as an ArrayBuffer
122
+ */
123
+ arrayBuffer(): ArrayBuffer
124
+
125
+ /**
126
+ * Read from stdout as an Uint8Array
127
+ */
128
+ bytes(): Uint8Array
129
+
130
+ /**
131
+ * Read from stdout as a Blob
132
+ */
133
+ blob(): Blob
134
+ }
135
+
136
+ export type BunShellError = Error & BunShellOutput
package/src/tool.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { z } from "zod"
2
+ import { Effect } from "effect"
3
+
4
+ export type ToolContext = {
5
+ sessionID: string
6
+ messageID: string
7
+ agent: string
8
+ /**
9
+ * Current project directory for this session.
10
+ * Prefer this over process.cwd() when resolving relative paths.
11
+ */
12
+ directory: string
13
+ /**
14
+ * Project worktree root for this session.
15
+ * Useful for generating stable relative paths (e.g. path.relative(worktree, absPath)).
16
+ */
17
+ worktree: string
18
+ abort: AbortSignal
19
+ metadata(input: { title?: string; metadata?: { [key: string]: any } }): void
20
+ ask(input: AskInput): Effect.Effect<void>
21
+ }
22
+
23
+ type AskInput = {
24
+ permission: string
25
+ patterns: string[]
26
+ always: string[]
27
+ metadata: { [key: string]: any }
28
+ }
29
+
30
+ export type ToolResult = string | { output: string; metadata?: { [key: string]: any } }
31
+
32
+ export function tool<Args extends z.ZodRawShape>(input: {
33
+ description: string
34
+ args: Args
35
+ execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<ToolResult>
36
+ }) {
37
+ return input
38
+ }
39
+ tool.schema = z
40
+
41
+ export type ToolDefinition = ReturnType<typeof tool>
package/src/tui.ts ADDED
@@ -0,0 +1,526 @@
1
+ import type {
2
+ AgentPart,
3
+ TulingcodeClient,
4
+ Event,
5
+ FilePart,
6
+ LspStatus,
7
+ McpStatus,
8
+ Todo,
9
+ SessionTaskResponse,
10
+ Message,
11
+ Part,
12
+ Provider,
13
+ PermissionRequest,
14
+ QuestionRequest,
15
+ SessionStatus,
16
+ TextPart,
17
+ Config as SdkConfig,
18
+ } from "@tuling-ai/sdk/v2"
19
+ import type { CliRenderer, ParsedKey, RGBA, SlotMode } from "@opentui/core"
20
+ import type { JSX, SolidPlugin } from "@opentui/solid"
21
+ import type { Config as PluginConfig, PluginOptions } from "./index.js"
22
+
23
+ export type { CliRenderer, SlotMode } from "@opentui/core"
24
+
25
+ export type TuiRouteCurrent =
26
+ | {
27
+ name: "home"
28
+ }
29
+ | {
30
+ name: "session"
31
+ params: {
32
+ sessionID: string
33
+ prompt?: unknown
34
+ }
35
+ }
36
+ | {
37
+ name: string
38
+ params?: Record<string, unknown>
39
+ }
40
+
41
+ export type TuiRouteDefinition = {
42
+ name: string
43
+ render: (input: { params?: Record<string, unknown> }) => JSX.Element
44
+ }
45
+
46
+ export type TuiCommand = {
47
+ title: string
48
+ value: string
49
+ description?: string
50
+ category?: string
51
+ keybind?: string
52
+ suggested?: boolean
53
+ hidden?: boolean
54
+ enabled?: boolean
55
+ slash?: {
56
+ name: string
57
+ aliases?: string[]
58
+ }
59
+ onSelect?: () => void
60
+ }
61
+
62
+ export type TuiKeybind = {
63
+ name: string
64
+ ctrl: boolean
65
+ meta: boolean
66
+ shift: boolean
67
+ super?: boolean
68
+ leader: boolean
69
+ }
70
+
71
+ export type TuiKeybindMap = Record<string, string>
72
+
73
+ export type TuiKeybindSet = {
74
+ readonly all: TuiKeybindMap
75
+ get: (name: string) => string
76
+ match: (name: string, evt: ParsedKey) => boolean
77
+ print: (name: string) => string
78
+ }
79
+
80
+ export type TuiDialogProps = {
81
+ size?: "medium" | "large" | "xlarge"
82
+ onClose: () => void
83
+ children?: JSX.Element
84
+ }
85
+
86
+ export type TuiDialogStack = {
87
+ replace: (render: () => JSX.Element, onClose?: () => void) => void
88
+ clear: () => void
89
+ setSize: (size: "medium" | "large" | "xlarge") => void
90
+ readonly size: "medium" | "large" | "xlarge"
91
+ readonly depth: number
92
+ readonly open: boolean
93
+ }
94
+
95
+ export type TuiDialogAlertProps = {
96
+ title: string
97
+ message: string
98
+ onConfirm?: () => void
99
+ }
100
+
101
+ export type TuiDialogConfirmProps = {
102
+ title: string
103
+ message: string
104
+ onConfirm?: () => void
105
+ onCancel?: () => void
106
+ }
107
+
108
+ export type TuiDialogPromptProps = {
109
+ title: string
110
+ description?: () => JSX.Element
111
+ placeholder?: string
112
+ value?: string
113
+ busy?: boolean
114
+ busyText?: string
115
+ onConfirm?: (value: string) => void
116
+ onCancel?: () => void
117
+ }
118
+
119
+ export type TuiDialogSelectOption<Value = unknown> = {
120
+ title: string
121
+ value: Value
122
+ description?: string
123
+ footer?: JSX.Element | string
124
+ category?: string
125
+ disabled?: boolean
126
+ onSelect?: () => void
127
+ }
128
+
129
+ export type TuiDialogSelectProps<Value = unknown> = {
130
+ title: string
131
+ placeholder?: string
132
+ options: TuiDialogSelectOption<Value>[]
133
+ flat?: boolean
134
+ onMove?: (option: TuiDialogSelectOption<Value>) => void
135
+ onFilter?: (query: string) => void
136
+ onSelect?: (option: TuiDialogSelectOption<Value>) => void
137
+ skipFilter?: boolean
138
+ current?: Value
139
+ }
140
+
141
+ export type TuiPromptInfo = {
142
+ input: string
143
+ mode?: "normal" | "shell"
144
+ parts: (
145
+ | Omit<FilePart, "id" | "messageID" | "sessionID">
146
+ | Omit<AgentPart, "id" | "messageID" | "sessionID">
147
+ | (Omit<TextPart, "id" | "messageID" | "sessionID"> & {
148
+ source?: {
149
+ text: {
150
+ start: number
151
+ end: number
152
+ value: string
153
+ }
154
+ }
155
+ })
156
+ )[]
157
+ }
158
+
159
+ export type TuiPromptRef = {
160
+ focused: boolean
161
+ current: TuiPromptInfo
162
+ set(prompt: TuiPromptInfo): void
163
+ reset(): void
164
+ blur(): void
165
+ focus(): void
166
+ submit(): void
167
+ paste(): void
168
+ }
169
+
170
+ export type TuiPromptProps = {
171
+ sessionID?: string
172
+ workspaceID?: string
173
+ visible?: boolean
174
+ disabled?: boolean
175
+ onSubmit?: () => void
176
+ ref?: (ref: TuiPromptRef | undefined) => void
177
+ hint?: JSX.Element
178
+ right?: JSX.Element
179
+ showPlaceholder?: boolean
180
+ placeholders?: {
181
+ normal?: string[]
182
+ shell?: string[]
183
+ }
184
+ }
185
+
186
+ export type TuiToast = {
187
+ variant?: "info" | "success" | "warning" | "error"
188
+ title?: string
189
+ message: string
190
+ duration?: number
191
+ }
192
+
193
+ export type TuiThemeCurrent = {
194
+ readonly primary: RGBA
195
+ readonly secondary: RGBA
196
+ readonly accent: RGBA
197
+ readonly error: RGBA
198
+ readonly warning: RGBA
199
+ readonly success: RGBA
200
+ readonly info: RGBA
201
+ readonly text: RGBA
202
+ readonly textMuted: RGBA
203
+ readonly selectedListItemText: RGBA
204
+ readonly background: RGBA
205
+ readonly backgroundPanel: RGBA
206
+ readonly backgroundElement: RGBA
207
+ readonly backgroundMenu: RGBA
208
+ readonly border: RGBA
209
+ readonly borderActive: RGBA
210
+ readonly borderSubtle: RGBA
211
+ readonly diffAdded: RGBA
212
+ readonly diffRemoved: RGBA
213
+ readonly diffContext: RGBA
214
+ readonly diffHunkHeader: RGBA
215
+ readonly diffHighlightAdded: RGBA
216
+ readonly diffHighlightRemoved: RGBA
217
+ readonly diffAddedBg: RGBA
218
+ readonly diffRemovedBg: RGBA
219
+ readonly diffContextBg: RGBA
220
+ readonly diffLineNumber: RGBA
221
+ readonly diffAddedLineNumberBg: RGBA
222
+ readonly diffRemovedLineNumberBg: RGBA
223
+ readonly markdownText: RGBA
224
+ readonly markdownHeading: RGBA
225
+ readonly markdownLink: RGBA
226
+ readonly markdownLinkText: RGBA
227
+ readonly markdownCode: RGBA
228
+ readonly markdownBlockQuote: RGBA
229
+ readonly markdownEmph: RGBA
230
+ readonly markdownStrong: RGBA
231
+ readonly markdownHorizontalRule: RGBA
232
+ readonly markdownListItem: RGBA
233
+ readonly markdownListEnumeration: RGBA
234
+ readonly markdownImage: RGBA
235
+ readonly markdownImageText: RGBA
236
+ readonly markdownCodeBlock: RGBA
237
+ readonly syntaxComment: RGBA
238
+ readonly syntaxKeyword: RGBA
239
+ readonly syntaxFunction: RGBA
240
+ readonly syntaxVariable: RGBA
241
+ readonly syntaxString: RGBA
242
+ readonly syntaxNumber: RGBA
243
+ readonly syntaxType: RGBA
244
+ readonly syntaxOperator: RGBA
245
+ readonly syntaxPunctuation: RGBA
246
+ readonly thinkingOpacity: number
247
+ }
248
+
249
+ export type TuiTheme = {
250
+ readonly current: TuiThemeCurrent
251
+ readonly selected: string
252
+ has: (name: string) => boolean
253
+ set: (name: string) => boolean
254
+ install: (jsonPath: string) => Promise<void>
255
+ mode: () => "dark" | "light"
256
+ readonly ready: boolean
257
+ }
258
+
259
+ export type TuiKV = {
260
+ get: <Value = unknown>(key: string, fallback?: Value) => Value
261
+ set: (key: string, value: unknown) => void
262
+ readonly ready: boolean
263
+ }
264
+
265
+ export type TuiState = {
266
+ readonly ready: boolean
267
+ readonly config: SdkConfig
268
+ readonly provider: ReadonlyArray<Provider>
269
+ readonly path: {
270
+ state: string
271
+ config: string
272
+ worktree: string
273
+ directory: string
274
+ }
275
+ readonly vcs: { branch?: string } | undefined
276
+ session: {
277
+ count: () => number
278
+ diff: (sessionID: string) => ReadonlyArray<TuiSidebarFileItem>
279
+ todo: (sessionID: string) => ReadonlyArray<TuiSidebarTodoItem>
280
+ task: (sessionID: string) => ReadonlyArray<TuiSidebarTaskItem>
281
+ messages: (sessionID: string) => ReadonlyArray<Message>
282
+ status: (sessionID: string) => SessionStatus | undefined
283
+ goal: (sessionID: string) => TuiSidebarGoal | undefined
284
+ cwd: (sessionID: string) => string | undefined
285
+ permission: (sessionID: string) => ReadonlyArray<PermissionRequest>
286
+ question: (sessionID: string) => ReadonlyArray<QuestionRequest>
287
+ }
288
+ part: (messageID: string) => ReadonlyArray<Part>
289
+ lsp: () => ReadonlyArray<TuiSidebarLspItem>
290
+ mcp: () => ReadonlyArray<TuiSidebarMcpItem>
291
+ instructions: () => ReadonlyArray<string>
292
+ }
293
+
294
+ type TuiConfigView = Pick<PluginConfig, "$schema" | "theme" | "keybinds" | "plugin"> &
295
+ NonNullable<PluginConfig["tui"]> & {
296
+ plugin_enabled?: Record<string, boolean>
297
+ }
298
+
299
+ export type TuiApp = {
300
+ readonly version: string
301
+ }
302
+
303
+ type Frozen<Value> = Value extends (...args: never[]) => unknown
304
+ ? Value
305
+ : Value extends ReadonlyArray<infer Item>
306
+ ? ReadonlyArray<Frozen<Item>>
307
+ : Value extends object
308
+ ? { readonly [Key in keyof Value]: Frozen<Value[Key]> }
309
+ : Value
310
+
311
+ export type TuiSidebarMcpItem = {
312
+ name: string
313
+ status: McpStatus["status"]
314
+ error?: string
315
+ }
316
+
317
+ export type TuiSidebarLspItem = Pick<LspStatus, "id" | "root" | "status">
318
+
319
+ export type TuiSidebarGoalVerdict = {
320
+ ok: boolean
321
+ impossible?: boolean
322
+ reason: string
323
+ attempt: number
324
+ error?: boolean
325
+ }
326
+
327
+ export type TuiSidebarGoal = {
328
+ condition?: string
329
+ verdicts: { [messageID: string]: TuiSidebarGoalVerdict }
330
+ lastMessageID?: string
331
+ }
332
+
333
+ export type TuiSidebarTodoItem = Pick<Todo, "content" | "status">
334
+
335
+ export type TuiSidebarTaskItem = Pick<
336
+ SessionTaskResponse[number],
337
+ "id" | "status" | "summary" | "owner" | "ended_at"
338
+ >
339
+
340
+ export type TuiSidebarFileItem = {
341
+ file: string
342
+ additions: number
343
+ deletions: number
344
+ }
345
+
346
+ export type TuiHostSlotMap = {
347
+ app: {}
348
+ home_logo: {}
349
+ home_prompt: {
350
+ workspace_id?: string
351
+ ref?: (ref: TuiPromptRef | undefined) => void
352
+ }
353
+ home_prompt_right: {
354
+ workspace_id?: string
355
+ }
356
+ session_prompt: {
357
+ session_id: string
358
+ visible?: boolean
359
+ disabled?: boolean
360
+ on_submit?: () => void
361
+ ref?: (ref: TuiPromptRef | undefined) => void
362
+ }
363
+ session_prompt_right: {
364
+ session_id: string
365
+ }
366
+ home_bottom: {}
367
+ home_footer: {}
368
+ sidebar_title: {
369
+ session_id: string
370
+ title: string
371
+ share_url?: string
372
+ }
373
+ sidebar_content: {
374
+ session_id: string
375
+ }
376
+ sidebar_footer: {
377
+ session_id: string
378
+ }
379
+ }
380
+
381
+ export type TuiSlotMap<Slots extends Record<string, object> = {}> = TuiHostSlotMap & Slots
382
+
383
+ type TuiSlotShape<Name extends string, Slots extends Record<string, object>> = Name extends keyof TuiHostSlotMap
384
+ ? TuiHostSlotMap[Name]
385
+ : Name extends keyof Slots
386
+ ? Slots[Name]
387
+ : Record<string, unknown>
388
+
389
+ export type TuiSlotProps<Name extends string = string, Slots extends Record<string, object> = {}> = {
390
+ name: Name
391
+ mode?: SlotMode
392
+ children?: JSX.Element
393
+ } & TuiSlotShape<Name, Slots>
394
+
395
+ export type TuiSlotContext = {
396
+ theme: TuiTheme
397
+ }
398
+
399
+ type SlotCore<Slots extends Record<string, object> = {}> = SolidPlugin<TuiSlotMap<Slots>, TuiSlotContext>
400
+
401
+ export type TuiSlotPlugin<Slots extends Record<string, object> = {}> = Omit<SlotCore<Slots>, "id"> & {
402
+ id?: never
403
+ }
404
+
405
+ export type TuiSlots = {
406
+ register: {
407
+ (plugin: TuiSlotPlugin): string
408
+ <Slots extends Record<string, object>>(plugin: TuiSlotPlugin<Slots>): string
409
+ }
410
+ }
411
+
412
+ export type TuiEventBus = {
413
+ on: <Type extends Event["type"]>(type: Type, handler: (event: Extract<Event, { type: Type }>) => void) => () => void
414
+ }
415
+
416
+ export type TuiDispose = () => void | Promise<void>
417
+
418
+ export type TuiLifecycle = {
419
+ readonly signal: AbortSignal
420
+ onDispose: (fn: TuiDispose) => () => void
421
+ }
422
+
423
+ export type TuiPluginState = "first" | "updated" | "same"
424
+
425
+ export type TuiPluginEntry = {
426
+ id: string
427
+ source: "file" | "npm" | "internal"
428
+ spec: string
429
+ target: string
430
+ requested?: string
431
+ version?: string
432
+ modified?: number
433
+ first_time: number
434
+ last_time: number
435
+ time_changed: number
436
+ load_count: number
437
+ fingerprint: string
438
+ }
439
+
440
+ export type TuiPluginMeta = TuiPluginEntry & {
441
+ state: TuiPluginState
442
+ }
443
+
444
+ export type TuiPluginStatus = {
445
+ id: string
446
+ source: TuiPluginEntry["source"]
447
+ spec: string
448
+ target: string
449
+ enabled: boolean
450
+ active: boolean
451
+ }
452
+
453
+ export type TuiPluginInstallOptions = {
454
+ global?: boolean
455
+ }
456
+
457
+ export type TuiPluginInstallResult =
458
+ | {
459
+ ok: true
460
+ dir: string
461
+ tui: boolean
462
+ }
463
+ | {
464
+ ok: false
465
+ message: string
466
+ missing?: boolean
467
+ }
468
+
469
+ export type TuiWorkspace = {
470
+ current: () => string | undefined
471
+ set: (workspaceID?: string) => void
472
+ }
473
+
474
+ export type TuiPluginApi = {
475
+ app: TuiApp
476
+ command: {
477
+ register: (cb: () => TuiCommand[]) => () => void
478
+ trigger: (value: string) => void
479
+ show: () => void
480
+ }
481
+ route: {
482
+ register: (routes: TuiRouteDefinition[]) => () => void
483
+ navigate: (name: string, params?: Record<string, unknown>) => void
484
+ readonly current: TuiRouteCurrent
485
+ }
486
+ ui: {
487
+ Dialog: (props: TuiDialogProps) => JSX.Element
488
+ DialogAlert: (props: TuiDialogAlertProps) => JSX.Element
489
+ DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element
490
+ DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element
491
+ DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element
492
+ Slot: <Name extends string>(props: TuiSlotProps<Name>) => JSX.Element | null
493
+ Prompt: (props: TuiPromptProps) => JSX.Element
494
+ toast: (input: TuiToast) => void
495
+ dialog: TuiDialogStack
496
+ }
497
+ keybind: {
498
+ match: (key: string, evt: ParsedKey) => boolean
499
+ print: (key: string) => string
500
+ create: (defaults: TuiKeybindMap, overrides?: Record<string, unknown>) => TuiKeybindSet
501
+ }
502
+ readonly tuiConfig: Frozen<TuiConfigView>
503
+ kv: TuiKV
504
+ state: TuiState
505
+ theme: TuiTheme
506
+ client: TulingcodeClient
507
+ event: TuiEventBus
508
+ renderer: CliRenderer
509
+ slots: TuiSlots
510
+ plugins: {
511
+ list: () => ReadonlyArray<TuiPluginStatus>
512
+ activate: (id: string) => Promise<boolean>
513
+ deactivate: (id: string) => Promise<boolean>
514
+ add: (spec: string) => Promise<boolean>
515
+ install: (spec: string, options?: TuiPluginInstallOptions) => Promise<TuiPluginInstallResult>
516
+ }
517
+ lifecycle: TuiLifecycle
518
+ }
519
+
520
+ export type TuiPlugin = (api: TuiPluginApi, options: PluginOptions | undefined, meta: TuiPluginMeta) => Promise<void>
521
+
522
+ export type TuiPluginModule = {
523
+ id?: string
524
+ tui: TuiPlugin
525
+ server?: never
526
+ }