@neurocode-ai/plugin 1.18.13 → 1.18.15

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/src/tui.ts ADDED
@@ -0,0 +1,634 @@
1
+ import type {
2
+ AgentPart,
3
+ NeurocodeClient,
4
+ Event,
5
+ FilePart,
6
+ LspStatus,
7
+ McpStatus,
8
+ Todo,
9
+ Message,
10
+ Part,
11
+ Provider,
12
+ PermissionRequest,
13
+ QuestionRequest,
14
+ Session,
15
+ SessionStatus,
16
+ TextPart,
17
+ Config as SdkConfig,
18
+ } from "@neurocode-ai/sdk/v2"
19
+ import type { CliRenderer, KeyEvent, RGBA, Renderable, SlotMode } from "@opentui/core"
20
+ import type { Binding, Keymap } from "@opentui/keymap"
21
+ import {
22
+ createBindingLookup as createKeymapBindingLookup,
23
+ type BindingConfig,
24
+ type CreateBindingLookupOptions,
25
+ type KeySequenceFormatPart,
26
+ type SequenceBindingLike,
27
+ } from "@opentui/keymap/extras"
28
+ import type { JSX, SolidPlugin } from "@opentui/solid"
29
+ import type { Config as PluginConfig, PluginOptions } from "./index.js"
30
+
31
+ export type { CliRenderer, KeyEvent, Renderable, SlotMode } from "@opentui/core"
32
+ export { stringifyKeySequence, stringifyKeyStroke } from "@opentui/keymap"
33
+ export type { Binding, KeyLike, KeySequencePart, KeyStringifyInput, StringifyOptions } from "@opentui/keymap"
34
+ export { formatCommandBindings, formatKeySequence } from "@opentui/keymap/extras"
35
+ export type {
36
+ BindingConfig,
37
+ BindingLookup,
38
+ BindingValue,
39
+ CreateBindingLookupOptions,
40
+ FormatCommandBindingsOptions,
41
+ FormatKeySequenceOptions,
42
+ KeySequenceFormatPart,
43
+ SequenceBindingLike,
44
+ } from "@opentui/keymap/extras"
45
+
46
+ export function createBindingLookup(
47
+ config: BindingConfig<Renderable, KeyEvent> | undefined,
48
+ options?: CreateBindingLookupOptions<Renderable, KeyEvent>,
49
+ ) {
50
+ return createKeymapBindingLookup<Renderable, KeyEvent>(config ?? {}, options)
51
+ }
52
+
53
+ export type TuiRouteCurrent =
54
+ | {
55
+ name: "home"
56
+ }
57
+ | {
58
+ name: "session"
59
+ params: {
60
+ sessionID: string
61
+ prompt?: unknown
62
+ }
63
+ }
64
+ | {
65
+ name: string
66
+ params?: Record<string, unknown>
67
+ }
68
+
69
+ export type TuiRouteDefinition = {
70
+ name: string
71
+ render: (input: { params?: Record<string, unknown> }) => JSX.Element
72
+ }
73
+
74
+ export type TuiKeys = {
75
+ formatSequence: (parts: readonly KeySequenceFormatPart[] | undefined) => string
76
+ formatBindings: (bindings: readonly SequenceBindingLike[] | undefined) => string | undefined
77
+ }
78
+
79
+ export type TuiKeymap = Keymap<Renderable, KeyEvent>
80
+
81
+ export type TuiModeApi = {
82
+ current: () => string
83
+ push: (mode: string) => () => void
84
+ }
85
+
86
+ /**
87
+ * Legacy `api.command` shape kept so v1 plugins can initialize. Remove in v2.
88
+ *
89
+ * @deprecated Use `api.keymap.registerLayer({ commands, bindings })` instead.
90
+ */
91
+ export type TuiCommand = {
92
+ title: string
93
+ value: string
94
+ description?: string
95
+ category?: string
96
+ keybind?: string
97
+ suggested?: boolean
98
+ hidden?: boolean
99
+ enabled?: boolean
100
+ slash?: {
101
+ name: string
102
+ aliases?: string[]
103
+ }
104
+ onSelect?: (dialog?: TuiDialogStack) => void | Promise<void>
105
+ }
106
+
107
+ /**
108
+ * Legacy `api.command` API kept so v1 plugins can initialize. Remove in v2.
109
+ *
110
+ * @deprecated Use `api.keymap.registerLayer`, `api.keymap.dispatchCommand`, and
111
+ * `api.keymap.dispatchCommand("command.palette.show")` instead.
112
+ */
113
+ export type TuiCommandApi = {
114
+ /** @deprecated Use `api.keymap.registerLayer({ commands, bindings })` instead. */
115
+ register: (cb: () => TuiCommand[]) => () => void
116
+ /** @deprecated Use `api.keymap.dispatchCommand(name)` instead. */
117
+ trigger: (value: string) => void
118
+ /** @deprecated Use `api.keymap.dispatchCommand("command.palette.show")` instead. */
119
+ show: () => void
120
+ }
121
+
122
+ export type TuiDialogProps = {
123
+ size?: "medium" | "large" | "xlarge"
124
+ onClose: () => void
125
+ children?: JSX.Element
126
+ }
127
+
128
+ export type TuiDialogStack = {
129
+ replace: (render: () => JSX.Element, onClose?: () => void) => void
130
+ clear: () => void
131
+ setSize: (size: "medium" | "large" | "xlarge") => void
132
+ readonly size: "medium" | "large" | "xlarge"
133
+ readonly depth: number
134
+ readonly open: boolean
135
+ }
136
+
137
+ export type TuiDialogAlertProps = {
138
+ title: string
139
+ message: string
140
+ onConfirm?: () => void
141
+ }
142
+
143
+ export type TuiDialogConfirmProps = {
144
+ title: string
145
+ message: string
146
+ onConfirm?: () => void
147
+ onCancel?: () => void
148
+ }
149
+
150
+ export type TuiDialogPromptProps = {
151
+ title: string
152
+ description?: () => JSX.Element
153
+ placeholder?: string
154
+ value?: string
155
+ busy?: boolean
156
+ busyText?: string
157
+ onConfirm?: (value: string) => void
158
+ onCancel?: () => void
159
+ }
160
+
161
+ export type TuiDialogSelectOption<Value = unknown> = {
162
+ title: string
163
+ value: Value
164
+ description?: string
165
+ footer?: JSX.Element | string
166
+ category?: string
167
+ disabled?: boolean
168
+ onSelect?: () => void
169
+ }
170
+
171
+ export type TuiDialogSelectProps<Value = unknown> = {
172
+ title: string
173
+ placeholder?: string
174
+ options: TuiDialogSelectOption<Value>[]
175
+ flat?: boolean
176
+ onMove?: (option: TuiDialogSelectOption<Value>) => void
177
+ onFilter?: (query: string) => void
178
+ onSelect?: (option: TuiDialogSelectOption<Value>) => void
179
+ skipFilter?: boolean
180
+ current?: Value
181
+ }
182
+
183
+ export type TuiPromptInfo = {
184
+ input: string
185
+ mode?: "normal" | "shell"
186
+ parts: (
187
+ | Omit<FilePart, "id" | "messageID" | "sessionID">
188
+ | Omit<AgentPart, "id" | "messageID" | "sessionID">
189
+ | (Omit<TextPart, "id" | "messageID" | "sessionID"> & {
190
+ source?: {
191
+ text: {
192
+ start: number
193
+ end: number
194
+ value: string
195
+ }
196
+ }
197
+ })
198
+ )[]
199
+ }
200
+
201
+ export type TuiPromptRef = {
202
+ focused: boolean
203
+ current: TuiPromptInfo
204
+ set(prompt: TuiPromptInfo): void
205
+ reset(): void
206
+ blur(): void
207
+ focus(): void
208
+ submit(): void
209
+ }
210
+
211
+ export type TuiPromptProps = {
212
+ sessionID?: string
213
+ visible?: boolean
214
+ disabled?: boolean
215
+ onSubmit?: () => void
216
+ ref?: (ref: TuiPromptRef | undefined) => void
217
+ hint?: JSX.Element
218
+ right?: JSX.Element
219
+ showPlaceholder?: boolean
220
+ placeholders?: {
221
+ normal?: string[]
222
+ shell?: string[]
223
+ }
224
+ }
225
+
226
+ export type TuiToast = {
227
+ variant?: "info" | "success" | "warning" | "error"
228
+ title?: string
229
+ message: string
230
+ duration?: number
231
+ }
232
+
233
+ export type TuiAttentionWhen = "always" | "focused" | "blurred"
234
+
235
+ export const TuiAttentionSoundNames = ["default", "question", "permission", "error", "done", "subagent_done"] as const
236
+ export type TuiAttentionSoundName = (typeof TuiAttentionSoundNames)[number]
237
+
238
+ export type TuiAttentionSound =
239
+ | boolean
240
+ | {
241
+ name?: TuiAttentionSoundName
242
+ volume?: number
243
+ when?: TuiAttentionWhen
244
+ }
245
+
246
+ export type TuiAttentionNotification =
247
+ | boolean
248
+ | {
249
+ when?: TuiAttentionWhen
250
+ }
251
+
252
+ export type TuiAttentionSoundPack = {
253
+ id: string
254
+ name?: string
255
+ sounds: Partial<Record<TuiAttentionSoundName, string>>
256
+ }
257
+
258
+ export type TuiAttentionSoundPackInfo = {
259
+ id: string
260
+ name?: string
261
+ active: boolean
262
+ builtin: boolean
263
+ }
264
+
265
+ export type TuiAttentionSoundboardActivateOptions = {
266
+ persist?: boolean
267
+ }
268
+
269
+ export type TuiAttentionSoundboard = {
270
+ registerPack(pack: TuiAttentionSoundPack): () => void
271
+ activate(id: string, options?: TuiAttentionSoundboardActivateOptions): boolean
272
+ current(): string
273
+ list(): ReadonlyArray<TuiAttentionSoundPackInfo>
274
+ }
275
+
276
+ export type TuiAttentionNotifyInput = {
277
+ title?: string
278
+ message: string
279
+ notification?: TuiAttentionNotification
280
+ sound?: TuiAttentionSound
281
+ }
282
+
283
+ export type TuiAttentionNotifySkipReason =
284
+ | "attention_disabled"
285
+ | "empty_message"
286
+ | "blurred"
287
+ | "focused"
288
+ | "focus_unknown"
289
+ | "renderer_destroyed"
290
+
291
+ export type TuiAttentionNotifyResult = {
292
+ ok: boolean
293
+ notification: boolean
294
+ sound: boolean
295
+ skipped?: TuiAttentionNotifySkipReason
296
+ }
297
+
298
+ export type TuiAttention = {
299
+ notify(input: TuiAttentionNotifyInput): Promise<TuiAttentionNotifyResult>
300
+ soundboard: TuiAttentionSoundboard
301
+ }
302
+
303
+ export type TuiThemeCurrent = {
304
+ readonly primary: RGBA
305
+ readonly secondary: RGBA
306
+ readonly accent: RGBA
307
+ readonly error: RGBA
308
+ readonly warning: RGBA
309
+ readonly success: RGBA
310
+ readonly info: RGBA
311
+ readonly text: RGBA
312
+ readonly textMuted: RGBA
313
+ readonly selectedListItemText: RGBA
314
+ readonly background: RGBA
315
+ readonly backgroundPanel: RGBA
316
+ readonly backgroundElement: RGBA
317
+ readonly backgroundMenu: RGBA
318
+ readonly border: RGBA
319
+ readonly borderActive: RGBA
320
+ readonly borderSubtle: RGBA
321
+ readonly diffAdded: RGBA
322
+ readonly diffRemoved: RGBA
323
+ readonly diffContext: RGBA
324
+ readonly diffHunkHeader: RGBA
325
+ readonly diffHighlightAdded: RGBA
326
+ readonly diffHighlightRemoved: RGBA
327
+ readonly diffAddedBg: RGBA
328
+ readonly diffRemovedBg: RGBA
329
+ readonly diffContextBg: RGBA
330
+ readonly diffLineNumber: RGBA
331
+ readonly diffAddedLineNumberBg: RGBA
332
+ readonly diffRemovedLineNumberBg: RGBA
333
+ readonly markdownText: RGBA
334
+ readonly markdownHeading: RGBA
335
+ readonly markdownLink: RGBA
336
+ readonly markdownLinkText: RGBA
337
+ readonly markdownCode: RGBA
338
+ readonly markdownBlockQuote: RGBA
339
+ readonly markdownEmph: RGBA
340
+ readonly markdownStrong: RGBA
341
+ readonly markdownHorizontalRule: RGBA
342
+ readonly markdownListItem: RGBA
343
+ readonly markdownListEnumeration: RGBA
344
+ readonly markdownImage: RGBA
345
+ readonly markdownImageText: RGBA
346
+ readonly markdownCodeBlock: RGBA
347
+ readonly syntaxComment: RGBA
348
+ readonly syntaxKeyword: RGBA
349
+ readonly syntaxFunction: RGBA
350
+ readonly syntaxVariable: RGBA
351
+ readonly syntaxString: RGBA
352
+ readonly syntaxNumber: RGBA
353
+ readonly syntaxType: RGBA
354
+ readonly syntaxOperator: RGBA
355
+ readonly syntaxPunctuation: RGBA
356
+ readonly thinkingOpacity: number
357
+ }
358
+
359
+ export type TuiTheme = {
360
+ readonly current: TuiThemeCurrent
361
+ readonly selected: string
362
+ has: (name: string) => boolean
363
+ set: (name: string) => boolean
364
+ install: (jsonPath: string) => Promise<void>
365
+ mode: () => "dark" | "light"
366
+ readonly ready: boolean
367
+ }
368
+
369
+ export type TuiKV = {
370
+ get: <Value = unknown>(key: string, fallback?: Value) => Value
371
+ set: (key: string, value: unknown) => void
372
+ readonly ready: boolean
373
+ }
374
+
375
+ export type TuiState = {
376
+ readonly ready: boolean
377
+ readonly config: SdkConfig
378
+ readonly provider: ReadonlyArray<Provider>
379
+ readonly path: {
380
+ state: string
381
+ config: string
382
+ worktree: string
383
+ directory: string
384
+ }
385
+ readonly vcs: { branch?: string; default_branch?: string } | undefined
386
+ session: {
387
+ count: () => number
388
+ get: (sessionID: string) => Session | undefined
389
+ diff: (sessionID: string) => ReadonlyArray<TuiSidebarFileItem>
390
+ todo: (sessionID: string) => ReadonlyArray<TuiSidebarTodoItem>
391
+ messages: (sessionID: string) => ReadonlyArray<Message>
392
+ status: (sessionID: string) => SessionStatus | undefined
393
+ permission: (sessionID: string) => ReadonlyArray<PermissionRequest>
394
+ question: (sessionID: string) => ReadonlyArray<QuestionRequest>
395
+ }
396
+ part: (messageID: string) => ReadonlyArray<Part>
397
+ lsp: () => ReadonlyArray<TuiSidebarLspItem>
398
+ mcp: () => ReadonlyArray<TuiSidebarMcpItem>
399
+ }
400
+
401
+ type TuiBindingLookupView = {
402
+ readonly bindings: ReadonlyArray<Binding<Renderable, KeyEvent>>
403
+ get: (command: string) => ReadonlyArray<Binding<Renderable, KeyEvent>>
404
+ has: (command: string) => boolean
405
+ gather: (name: string, commands: readonly string[]) => ReadonlyArray<Binding<Renderable, KeyEvent>>
406
+ pick: (name: string, commands: readonly string[]) => Binding<Renderable, KeyEvent>[]
407
+ omit: (name: string, commands: readonly string[]) => Binding<Renderable, KeyEvent>[]
408
+ }
409
+
410
+ type TuiAttentionConfigView = {
411
+ enabled: boolean
412
+ notifications: boolean
413
+ sound: boolean
414
+ volume: number
415
+ sound_pack: string
416
+ sounds: Partial<Record<TuiAttentionSoundName, string>>
417
+ }
418
+
419
+ type TuiConfigView = Pick<PluginConfig, "$schema" | "theme" | "plugin"> &
420
+ NonNullable<PluginConfig["tui"]> & {
421
+ leader_timeout: number
422
+ attention: TuiAttentionConfigView
423
+ plugin_enabled?: Record<string, boolean>
424
+ keybinds: TuiBindingLookupView
425
+ }
426
+
427
+ export type TuiApp = {
428
+ readonly version: string
429
+ }
430
+
431
+ type Frozen<Value> = Value extends (...args: never[]) => unknown
432
+ ? Value
433
+ : Value extends ReadonlyArray<infer Item>
434
+ ? ReadonlyArray<Frozen<Item>>
435
+ : Value extends object
436
+ ? { readonly [Key in keyof Value]: Frozen<Value[Key]> }
437
+ : Value
438
+
439
+ export type TuiSidebarMcpItem = {
440
+ name: string
441
+ status: McpStatus["status"]
442
+ error?: string
443
+ }
444
+
445
+ export type TuiSidebarLspItem = Pick<LspStatus, "id" | "root" | "status">
446
+
447
+ export type TuiSidebarTodoItem = Pick<Todo, "content" | "status">
448
+
449
+ export type TuiSidebarFileItem = {
450
+ file: string
451
+ additions: number
452
+ deletions: number
453
+ }
454
+
455
+ export type TuiHostSlotMap = {
456
+ app: {}
457
+ app_bottom: {}
458
+ home_logo: {}
459
+ home_prompt: {
460
+ ref?: (ref: TuiPromptRef | undefined) => void
461
+ }
462
+ home_prompt_right: {}
463
+ session_prompt: {
464
+ session_id: string
465
+ visible?: boolean
466
+ disabled?: boolean
467
+ on_submit?: () => void
468
+ ref?: (ref: TuiPromptRef | undefined) => void
469
+ }
470
+ session_prompt_right: {
471
+ session_id: string
472
+ }
473
+ home_bottom: {}
474
+ home_footer: {}
475
+ sidebar_title: {
476
+ session_id: string
477
+ title: string
478
+ share_url?: string
479
+ }
480
+ sidebar_content: {
481
+ session_id: string
482
+ }
483
+ sidebar_footer: {
484
+ session_id: string
485
+ }
486
+ }
487
+
488
+ export type TuiSlotMap<Slots extends Record<string, object> = {}> = TuiHostSlotMap & Slots
489
+
490
+ type TuiSlotShape<Name extends string, Slots extends Record<string, object>> = Name extends keyof TuiHostSlotMap
491
+ ? TuiHostSlotMap[Name]
492
+ : Name extends keyof Slots
493
+ ? Slots[Name]
494
+ : Record<string, unknown>
495
+
496
+ export type TuiSlotProps<Name extends string = string, Slots extends Record<string, object> = {}> = {
497
+ name: Name
498
+ mode?: SlotMode
499
+ children?: JSX.Element
500
+ } & TuiSlotShape<Name, Slots>
501
+
502
+ export type TuiSlotContext = {
503
+ theme: TuiTheme
504
+ }
505
+
506
+ type SlotCore<Slots extends Record<string, object> = {}> = SolidPlugin<TuiSlotMap<Slots>, TuiSlotContext>
507
+
508
+ export type TuiSlotPlugin<Slots extends Record<string, object> = {}> = Omit<SlotCore<Slots>, "id"> & {
509
+ id?: never
510
+ }
511
+
512
+ export type TuiSlots = {
513
+ register: {
514
+ (plugin: TuiSlotPlugin): string
515
+ <Slots extends Record<string, object>>(plugin: TuiSlotPlugin<Slots>): string
516
+ }
517
+ }
518
+
519
+ export type TuiEventBus = {
520
+ on: <Type extends Event["type"]>(type: Type, handler: (event: Extract<Event, { type: Type }>) => void) => () => void
521
+ }
522
+
523
+ export type TuiDispose = () => void | Promise<void>
524
+
525
+ export type TuiLifecycle = {
526
+ readonly signal: AbortSignal
527
+ onDispose: (fn: TuiDispose) => () => void
528
+ }
529
+
530
+ export type TuiPluginState = "first" | "updated" | "same"
531
+
532
+ export type TuiPluginEntry = {
533
+ id: string
534
+ source: "file" | "npm" | "internal"
535
+ spec: string
536
+ target: string
537
+ requested?: string
538
+ version?: string
539
+ modified?: number
540
+ first_time: number
541
+ last_time: number
542
+ time_changed: number
543
+ load_count: number
544
+ fingerprint: string
545
+ }
546
+
547
+ export type TuiPluginMeta = TuiPluginEntry & {
548
+ state: TuiPluginState
549
+ }
550
+
551
+ export type TuiPluginStatus = {
552
+ id: string
553
+ source: TuiPluginEntry["source"]
554
+ spec: string
555
+ target: string
556
+ enabled: boolean
557
+ active: boolean
558
+ }
559
+
560
+ export type TuiPluginInstallOptions = {
561
+ global?: boolean
562
+ }
563
+
564
+ export type TuiPluginInstallResult =
565
+ | {
566
+ ok: true
567
+ dir: string
568
+ tui: boolean
569
+ }
570
+ | {
571
+ ok: false
572
+ message: string
573
+ missing?: boolean
574
+ }
575
+
576
+ export type TuiWorkspace = {
577
+ current: () => string | undefined
578
+ set: (workspaceID?: string) => void
579
+ }
580
+
581
+ export type TuiPluginApi = {
582
+ app: TuiApp
583
+ attention: TuiAttention
584
+ /**
585
+ * Legacy `api.command` API kept so v1 plugins can initialize. Remove in v2.
586
+ *
587
+ * @deprecated Use `api.keymap.registerLayer`, `api.keymap.dispatchCommand`, and
588
+ * `api.keymap.dispatchCommand("command.palette.show")` instead.
589
+ */
590
+ command?: TuiCommandApi
591
+ keys: TuiKeys
592
+ keymap: TuiKeymap
593
+ mode: TuiModeApi
594
+ route: {
595
+ register: (routes: TuiRouteDefinition[]) => () => void
596
+ navigate: (name: string, params?: Record<string, unknown>) => void
597
+ readonly current: TuiRouteCurrent
598
+ }
599
+ ui: {
600
+ Dialog: (props: TuiDialogProps) => JSX.Element
601
+ DialogAlert: (props: TuiDialogAlertProps) => JSX.Element
602
+ DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element
603
+ DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element
604
+ DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element
605
+ Slot: <Name extends string>(props: TuiSlotProps<Name>) => JSX.Element | null
606
+ Prompt: (props: TuiPromptProps) => JSX.Element
607
+ toast: (input: TuiToast) => void
608
+ dialog: TuiDialogStack
609
+ }
610
+ readonly tuiConfig: Frozen<TuiConfigView>
611
+ kv: TuiKV
612
+ state: TuiState
613
+ theme: TuiTheme
614
+ client: NeurocodeClient
615
+ event: TuiEventBus
616
+ renderer: CliRenderer
617
+ slots: TuiSlots
618
+ plugins: {
619
+ list: () => ReadonlyArray<TuiPluginStatus>
620
+ activate: (id: string) => Promise<boolean>
621
+ deactivate: (id: string) => Promise<boolean>
622
+ add: (spec: string) => Promise<boolean>
623
+ install: (spec: string, options?: TuiPluginInstallOptions) => Promise<TuiPluginInstallResult>
624
+ }
625
+ lifecycle: TuiLifecycle
626
+ }
627
+
628
+ export type TuiPlugin = (api: TuiPluginApi, options: PluginOptions | undefined, meta: TuiPluginMeta) => Promise<void>
629
+
630
+ export type TuiPluginModule = {
631
+ id?: string
632
+ tui: TuiPlugin
633
+ server?: never
634
+ }