@xp266/dshtui 0.1.0

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.
Files changed (159) hide show
  1. package/bin/dshtui.js +65 -0
  2. package/cordis.patch.yml +81 -0
  3. package/lib/contract/index.d.mts +2 -0
  4. package/lib/contract/index.mjs +5 -0
  5. package/lib/dialog.d.mts +156 -0
  6. package/lib/dialog.mjs +3 -0
  7. package/lib/index-DqnaSXqP.d.mts +823 -0
  8. package/lib/index.d.mts +17 -0
  9. package/lib/index.mjs +11404 -0
  10. package/lib/prod-react-Dzof4qJx.mjs +4 -0
  11. package/lib/surface-DdxzIgIY.mjs +3126 -0
  12. package/lib/vendor.d.mts +3 -0
  13. package/lib/vendor.mjs +4 -0
  14. package/package.json +128 -0
  15. package/src/apply-theme.ts +8 -0
  16. package/src/boot-log.ts +35 -0
  17. package/src/chat/blocks.ts +41 -0
  18. package/src/chat/bridge.ts +1076 -0
  19. package/src/chat/builtin-tool-views.ts +53 -0
  20. package/src/chat/chat-face.ts +16 -0
  21. package/src/chat/chat-nodes.ts +53 -0
  22. package/src/chat/efforts.ts +4 -0
  23. package/src/chat/interactions.ts +329 -0
  24. package/src/chat/models.ts +263 -0
  25. package/src/chat/partial-json.ts +125 -0
  26. package/src/chat/presets.ts +26 -0
  27. package/src/chat/question-view.ts +106 -0
  28. package/src/chat/retry-status.ts +33 -0
  29. package/src/chat/session-groups.ts +34 -0
  30. package/src/chat/session-list.ts +311 -0
  31. package/src/chat/store.ts +636 -0
  32. package/src/chat/todo-view.ts +66 -0
  33. package/src/chat/tool-view.ts +311 -0
  34. package/src/chat/tool-views.ts +114 -0
  35. package/src/contract/index.ts +704 -0
  36. package/src/contract/upstream.ts +165 -0
  37. package/src/core/caret-nonce.ts +19 -0
  38. package/src/core/composer-layout.ts +65 -0
  39. package/src/core/edit.ts +152 -0
  40. package/src/core/field-view.ts +52 -0
  41. package/src/core/fields.ts +185 -0
  42. package/src/core/metrics.ts +26 -0
  43. package/src/core/paste.ts +149 -0
  44. package/src/core/segments.ts +112 -0
  45. package/src/core/text.ts +290 -0
  46. package/src/dialog.ts +11 -0
  47. package/src/env.ts +38 -0
  48. package/src/harness-home.ts +22 -0
  49. package/src/hot-theme.ts +56 -0
  50. package/src/index.tsx +185 -0
  51. package/src/kernel/registry.ts +102 -0
  52. package/src/kernel/surface.ts +33 -0
  53. package/src/log.ts +249 -0
  54. package/src/model/message.ts +89 -0
  55. package/src/model/selection.ts +63 -0
  56. package/src/performance-guard.ts +28 -0
  57. package/src/runtime/prod-react.ts +10 -0
  58. package/src/terminal/background.ts +66 -0
  59. package/src/terminal/capabilities.ts +54 -0
  60. package/src/terminal/clipboard-backends.ts +69 -0
  61. package/src/terminal/clipboard.ts +161 -0
  62. package/src/terminal/cursor-shape.ts +13 -0
  63. package/src/terminal/glyphs.ts +44 -0
  64. package/src/terminal/mouse.ts +154 -0
  65. package/src/terminal/probe.ts +61 -0
  66. package/src/terminal/screen.ts +349 -0
  67. package/src/theme-settings.ts +31 -0
  68. package/src/theme.ts +319 -0
  69. package/src/ui/app.tsx +454 -0
  70. package/src/ui/chrome/caps.tsx +9 -0
  71. package/src/ui/chrome/hint-service.ts +87 -0
  72. package/src/ui/chrome/input-status.ts +27 -0
  73. package/src/ui/chrome/key-gate.tsx +29 -0
  74. package/src/ui/chrome/status-bar.tsx +147 -0
  75. package/src/ui/contributions.ts +47 -0
  76. package/src/ui/dialog/defaults-dialog.tsx +152 -0
  77. package/src/ui/dialog/dialog-item.tsx +38 -0
  78. package/src/ui/dialog/dialog.tsx +386 -0
  79. package/src/ui/dialog/effort-dialog.tsx +45 -0
  80. package/src/ui/dialog/geometry.ts +131 -0
  81. package/src/ui/dialog/items.ts +124 -0
  82. package/src/ui/dialog/list-dialog.tsx +63 -0
  83. package/src/ui/dialog/models-dialog.tsx +203 -0
  84. package/src/ui/dialog/presets-dialog.tsx +46 -0
  85. package/src/ui/dialog/providers-dialog.tsx +313 -0
  86. package/src/ui/dialog/sessions-dialog.tsx +163 -0
  87. package/src/ui/dialog/sizes.ts +7 -0
  88. package/src/ui/dialog/status-lines.ts +11 -0
  89. package/src/ui/dialog/todo-dialog.tsx +29 -0
  90. package/src/ui/dialog/use-dialog-input.ts +186 -0
  91. package/src/ui/extension-point.ts +188 -0
  92. package/src/ui/home-logo.ts +132 -0
  93. package/src/ui/hooks/use-async-action.ts +25 -0
  94. package/src/ui/hooks/use-async-list.ts +71 -0
  95. package/src/ui/hooks/use-caret.ts +24 -0
  96. package/src/ui/hooks/use-chat-events.ts +153 -0
  97. package/src/ui/hooks/use-mouse-selection.ts +352 -0
  98. package/src/ui/hooks/use-scroll.ts +49 -0
  99. package/src/ui/hooks/use-terminal-size.ts +20 -0
  100. package/src/ui/input/commands.ts +118 -0
  101. package/src/ui/input/composer-bus.ts +14 -0
  102. package/src/ui/input/composer-fields.ts +111 -0
  103. package/src/ui/input/composer-keys.ts +21 -0
  104. package/src/ui/input/composer-paste.ts +20 -0
  105. package/src/ui/input/input-bar.tsx +279 -0
  106. package/src/ui/input/use-composer.ts +495 -0
  107. package/src/ui/key-arbiter.ts +37 -0
  108. package/src/ui/keymap.ts +15 -0
  109. package/src/ui/layout-service.ts +77 -0
  110. package/src/ui/message/layout.ts +763 -0
  111. package/src/ui/message/md/block.ts +247 -0
  112. package/src/ui/message/md/engine.ts +430 -0
  113. package/src/ui/message/md/extensions.ts +58 -0
  114. package/src/ui/message/md/highlight.ts +626 -0
  115. package/src/ui/message/md/index.ts +14 -0
  116. package/src/ui/message/md/inline.ts +97 -0
  117. package/src/ui/message/md/palette.ts +87 -0
  118. package/src/ui/message/message-list.tsx +69 -0
  119. package/src/ui/message/message-row.tsx +137 -0
  120. package/src/ui/message/message-views.ts +16 -0
  121. package/src/ui/message/renderers.ts +39 -0
  122. package/src/ui/message/tool-diff.ts +188 -0
  123. package/src/ui/message/warmup.ts +41 -0
  124. package/src/ui/message/wave.ts +98 -0
  125. package/src/ui/overlay.ts +22 -0
  126. package/src/ui/panels/approval-panel.tsx +193 -0
  127. package/src/ui/panels/exports.ts +3 -0
  128. package/src/ui/panels/question-model.ts +424 -0
  129. package/src/ui/panels/question-panel.tsx +154 -0
  130. package/src/ui/panels/surface.tsx +106 -0
  131. package/src/ui/panels-builtin.tsx +86 -0
  132. package/src/ui/pointer/builtins.ts +281 -0
  133. package/src/ui/pointer/registry.ts +25 -0
  134. package/src/ui/region.tsx +31 -0
  135. package/src/ui/selection/service.ts +92 -0
  136. package/src/ui/selection-registry.ts +88 -0
  137. package/src/ui/selection.tsx +148 -0
  138. package/src/ui/spinner-tick.tsx +24 -0
  139. package/src/ui/use-available-windows.ts +31 -0
  140. package/src/ui/widgets/actions.tsx +84 -0
  141. package/src/ui/widgets/button.tsx +57 -0
  142. package/src/ui/widgets/checkbox.tsx +58 -0
  143. package/src/ui/widgets/header.tsx +36 -0
  144. package/src/ui/widgets/index.ts +8 -0
  145. package/src/ui/widgets/input.tsx +76 -0
  146. package/src/ui/widgets/registry.ts +32 -0
  147. package/src/ui/widgets/search.tsx +59 -0
  148. package/src/ui/widgets/select.tsx +97 -0
  149. package/src/ui/widgets/static.tsx +37 -0
  150. package/src/ui/widgets/types.ts +1 -0
  151. package/src/ui/window-services-bridge.ts +71 -0
  152. package/src/ui/window-services.ts +20 -0
  153. package/src/ui/windows/models-window.tsx +15 -0
  154. package/src/ui/windows/providers-window.tsx +14 -0
  155. package/src/ui/windows/service-window.tsx +18 -0
  156. package/src/ui/windows/sessions-window.tsx +25 -0
  157. package/src/ui/windows-builtin.tsx +94 -0
  158. package/src/ui/windows.ts +18 -0
  159. package/src/vendor.ts +12 -0
@@ -0,0 +1,20 @@
1
+ import { keyedRegistry } from '../../kernel/registry.ts'
2
+ import type { PasteHandlerContribution, PasteResult } from '../../contract/index.ts'
3
+
4
+ const handlers = keyedRegistry<PasteHandlerContribution>()
5
+
6
+ export function registerPasteHandler(handler: PasteHandlerContribution): () => void {
7
+ return handlers.register(handler.id, handler, { order: handler.order })
8
+ }
9
+
10
+ export function claimPaste(text: string, cursor: number): PasteResult | undefined {
11
+ for (const handler of handlers.values()) {
12
+ try {
13
+ const result = handler.handle({ text, cursor })
14
+ if (result !== undefined) return result
15
+ } catch {
16
+ // One broken paste handler must not block the remaining handlers.
17
+ }
18
+ }
19
+ return undefined
20
+ }
@@ -0,0 +1,279 @@
1
+ import { Box, Text } from 'ink'
2
+ import type { Ref } from 'react'
3
+ import { useCaret } from '../hooks/use-caret.ts'
4
+ import { caretNonceBold, caretNonceColor, caretNonceText } from '../../core/caret-nonce.ts'
5
+ import { useImperativeHandle, useEffect } from 'react'
6
+ import { COLORS, permissionModeInfo } from '../../theme.ts'
7
+ import { glyphs } from '../../terminal/glyphs.ts'
8
+ import { writeCursorShape } from '../../terminal/cursor-shape.ts'
9
+ import { CHROME_FRAME_ROWS, CHROME_MARGIN_X, CHROME_PAD_X, CHROME_TEXT_X, INPUT_WIDTH_OFFSET, hintBlockTop, inputFrameTop, inputStatusRow } from '../../core/metrics.ts'
10
+ import { inputStatusParts } from '../chrome/input-status.ts'
11
+ import { CapText } from '../chrome/caps.tsx'
12
+ import { colToCharIndex, lineBreaks, textWidth, truncate, wrapLines } from '../../core/text.ts'
13
+ import { hasFieldChar } from '../../core/fields.ts'
14
+ import { expandFieldChars, fieldRowSegments } from '../../core/field-view.ts'
15
+ import type { ComposerApi } from './use-composer.ts'
16
+ import type { CommandHintState } from './commands.ts'
17
+ import { SelectableText } from '../selection.tsx'
18
+ import { Region } from '../region.tsx'
19
+
20
+ export { INPUT_WIDTH_OFFSET }
21
+
22
+ export const HINT_MAX_ROWS = 10
23
+
24
+ export { inputLayout, INPUT_MAX_CONTENT_ROWS } from '../../core/composer-layout.ts'
25
+ import { inputLayout } from '../../core/composer-layout.ts'
26
+
27
+ export interface InputBarHandle {
28
+ clickAt(y: number, x: number): void
29
+ wheel(delta: -1 | 1): void
30
+ hintWheel(delta: -1 | 1): void
31
+ hintClick(absoluteIndex: number): void
32
+ hintSelectAt(absoluteIndex: number): void
33
+ hintConfirm(): void
34
+ hintPick(absoluteIndex: number): void
35
+ }
36
+
37
+ interface InputBarProps {
38
+ ref?: Ref<InputBarHandle>
39
+ width: number
40
+ columns: number
41
+ rows: number
42
+ value: string
43
+ cursor: number
44
+ api: ComposerApi
45
+ modelName: string
46
+ permissionMode: string
47
+ effortName?: string
48
+ presetName?: string
49
+ interactive?: boolean
50
+ statusReady?: boolean
51
+ hint?: CommandHintState | null
52
+ }
53
+
54
+ export function InputBar({
55
+ ref,
56
+ width,
57
+ columns,
58
+ rows,
59
+ value,
60
+ cursor,
61
+ api,
62
+ modelName,
63
+ permissionMode,
64
+ effortName,
65
+ presetName,
66
+ interactive = true,
67
+ statusReady = true,
68
+ hint = null,
69
+ }: InputBarProps) {
70
+ const { setCursorPosition } = useCaret()
71
+ const contentWidth = width - INPUT_WIDTH_OFFSET
72
+ const blockWidth = contentWidth + CHROME_PAD_X * 2
73
+ const permission = permissionModeInfo(permissionMode)
74
+ useEffect(() => {
75
+ writeCursorShape(interactive ? 'beam' : 'block')
76
+ }, [interactive])
77
+ useEffect(() => {
78
+ return () => {
79
+ writeCursorShape('reset')
80
+ }
81
+ }, [])
82
+ const layout = inputLayout(value, cursor, width)
83
+ const { lines, cursorRow, cursorCol, realRows, barHeight, visibleStart } = layout
84
+ const hintCount = hint?.commands.length ?? 0
85
+ const firstRealY = inputFrameTop(rows, realRows)
86
+ const caretVisibleRow = cursorRow - visibleStart
87
+ if (interactive) {
88
+ setCursorPosition({
89
+ x: CHROME_TEXT_X + cursorCol,
90
+ y: firstRealY + caretVisibleRow,
91
+ })
92
+ } else {
93
+ setCursorPosition(undefined)
94
+ }
95
+ useImperativeHandle(ref, () => ({
96
+ clickAt(y, x) {
97
+ const rowInContent = y - firstRealY
98
+ if (rowInContent < 0 || rowInContent >= realRows) return
99
+ const lineIndex = visibleStart + rowInContent
100
+ const lineText = lines[lineIndex]
101
+ const target = lineBreaks(value, contentWidth)[lineIndex]
102
+ if (lineText === undefined || target === undefined) return
103
+ const col = Math.max(0, Math.min(x - CHROME_TEXT_X, textWidth(lineText)))
104
+ api.placeCursor(target.start + colToCharIndex(lineText, col))
105
+ },
106
+ wheel(delta) {
107
+ api.moveLineBy(delta)
108
+ },
109
+ hintWheel(delta) {
110
+ api.hintMove(delta)
111
+ },
112
+ hintClick(absoluteIndex) {
113
+ api.hintClickAt(absoluteIndex)
114
+ },
115
+ hintSelectAt(absoluteIndex) {
116
+ api.selectHint(absoluteIndex)
117
+ },
118
+ hintConfirm() {
119
+ api.confirmHint()
120
+ },
121
+ hintPick(absoluteIndex) {
122
+ api.hintPick(absoluteIndex)
123
+ },
124
+ }))
125
+ const statusSegments: Array<{ text: string; color: string; bold?: boolean }> = []
126
+ if (statusReady) {
127
+ const leftMax = presetName === undefined
128
+ ? contentWidth
129
+ : Math.max(1, contentWidth - textWidth(presetName) - 2)
130
+ const parts: Array<{ text: string; color: string; bold?: boolean }> = [
131
+ { text: permission.name, color: permission.textColor },
132
+ { text: ` ${glyphs.separator} `, color: COLORS.statusSeparator },
133
+ { text: modelName, color: COLORS.modelText },
134
+ ...(effortName === undefined ? [] : [
135
+ { text: ` ${glyphs.separator} `, color: COLORS.statusSeparator },
136
+ { text: effortName, color: COLORS.effortText },
137
+ ]),
138
+ ...(() => {
139
+ const contributed = inputStatusParts({ columns, rows, busy: !interactive, running: !interactive, modelName, permissionMode, effortName, presetName })
140
+ if (contributed.length === 0) return []
141
+ const separator = { text: ` ${glyphs.separator} `, color: COLORS.statusSeparator }
142
+ return [separator, ...contributed.flatMap((part, index) => {
143
+ const entry = { text: part.text, color: part.color ?? COLORS.modelText, ...(part.bold === undefined ? {} : { bold: part.bold }) }
144
+ return index < contributed.length - 1 ? [entry, separator] : [entry]
145
+ })]
146
+ })(),
147
+ ]
148
+ if (interactive) {
149
+ parts.push({ text: caretNonceText(cursor), color: caretNonceColor(cursor), bold: caretNonceBold(cursor) })
150
+ }
151
+ let used = 0
152
+ for (const part of parts) {
153
+ const remaining = leftMax - used
154
+ if (remaining <= 0) break
155
+ const text = truncate(part.text, remaining)
156
+ statusSegments.push({ text, color: part.color, ...(part.bold === undefined ? {} : { bold: part.bold }) })
157
+ used += textWidth(text)
158
+ }
159
+ }
160
+ const blockTop = hintCount > 0 ? Math.max(0, hintBlockTop(rows, barHeight, hintCount) - 1) : firstRealY - 1
161
+ const statusLocalY = inputStatusRow(rows) - blockTop
162
+ const hintTop = hintCount > 0 ? hintBlockTop(rows, barHeight, hintCount) : 0
163
+ return (
164
+ <Region y={blockTop}>
165
+ <Box position="absolute" top={0} left={0} width={columns} height={rows}>
166
+ <Box position="absolute" top={blockTop} left={CHROME_MARGIN_X} width={blockWidth}>
167
+ <CapText background={permission.color} top width={blockWidth} />
168
+ </Box>
169
+ {hintCount > 0 && hint?.commands.map((command, index) => {
170
+ const selected = index === hint.selectedIndex
171
+ const leftWidth = Math.max(1, Math.floor((blockWidth * 2) / 5))
172
+ const label = command.hint === undefined ? command.command : `${command.command} ${command.hint}`
173
+ const labelPiece = truncate(label, leftWidth - 1)
174
+ const descriptionPiece = truncate(command.description, Math.max(1, blockWidth - leftWidth - 2))
175
+ const gap = Math.max(1, leftWidth - textWidth(labelPiece))
176
+ const trail = Math.max(0, blockWidth - 2 - leftWidth - textWidth(descriptionPiece))
177
+ return (
178
+ <Box
179
+ key={command.command}
180
+ position="absolute"
181
+ top={hintTop + index}
182
+ left={CHROME_MARGIN_X}
183
+ width={blockWidth}
184
+ backgroundColor={permission.color}
185
+ >
186
+ <Box flexDirection="row">
187
+ <Text inverse={selected} color={COLORS.ink}>{' '}</Text>
188
+ <SelectableText y={hintTop + index - blockTop} col={CHROME_MARGIN_X + 2} text={labelPiece} inverse={selected} />
189
+ <Text inverse={selected} color={COLORS.ink}>{' '.repeat(gap)}</Text>
190
+ <SelectableText y={hintTop + index - blockTop} col={CHROME_MARGIN_X + 2 + leftWidth} text={descriptionPiece} inverse={selected} />
191
+ <Text inverse={selected} color={COLORS.ink}>{' '.repeat(trail)}</Text>
192
+ </Box>
193
+ </Box>
194
+ )
195
+ })}
196
+ {hintCount > 0 && (
197
+ <Box
198
+ position="absolute"
199
+ top={firstRealY - 1}
200
+ left={CHROME_MARGIN_X}
201
+ width={blockWidth}
202
+ paddingLeft={CHROME_PAD_X}
203
+ paddingRight={CHROME_PAD_X}
204
+ backgroundColor={permission.color}
205
+ >
206
+ <Text>{' '}</Text>
207
+ </Box>
208
+ )}
209
+ {Array.from({ length: realRows }, (_, row) => {
210
+ const line = lines[visibleStart + row] ?? ''
211
+ const expanded = expandFieldChars(line)
212
+ const segments = hasFieldChar(line) ? fieldRowSegments(line) : undefined
213
+ return (
214
+ <Box
215
+ key={`input-${row}`}
216
+ position="absolute"
217
+ top={firstRealY + row}
218
+ left={CHROME_MARGIN_X}
219
+ width={blockWidth}
220
+ paddingLeft={CHROME_PAD_X}
221
+ paddingRight={CHROME_PAD_X}
222
+ backgroundColor={permission.color}
223
+ >
224
+ <SelectableText
225
+ y={firstRealY + row - blockTop}
226
+ col={CHROME_TEXT_X}
227
+ text={expanded || ' '}
228
+ segments={segments}
229
+ />
230
+ </Box>
231
+ )
232
+ })}
233
+ <Box
234
+ position="absolute"
235
+ top={inputStatusRow(rows) - 1}
236
+ left={CHROME_MARGIN_X}
237
+ width={blockWidth}
238
+ paddingLeft={CHROME_PAD_X}
239
+ paddingRight={CHROME_PAD_X}
240
+ backgroundColor={permission.color}
241
+ >
242
+ <Text>{' '}</Text>
243
+ </Box>
244
+ <Box
245
+ position="absolute"
246
+ top={inputStatusRow(rows)}
247
+ left={CHROME_MARGIN_X}
248
+ width={blockWidth}
249
+ paddingLeft={CHROME_PAD_X}
250
+ paddingRight={CHROME_PAD_X}
251
+ backgroundColor={permission.color}
252
+ >
253
+ {statusReady ? (
254
+ <Box flexDirection="row">
255
+ {(() => {
256
+ let col = CHROME_TEXT_X
257
+ return statusSegments.map((segment, index) => {
258
+ const node = <SelectableText key={index} y={statusLocalY} col={col} text={segment.text} color={segment.color} bold={segment.bold} />
259
+ col += textWidth(segment.text)
260
+ return node
261
+ })
262
+ })()}
263
+ </Box>
264
+ ) : (
265
+ <Text>{' '}</Text>
266
+ )}
267
+ {statusReady && presetName !== undefined && (
268
+ <Box position="absolute" top={0} left={CHROME_MARGIN_X + contentWidth - textWidth(presetName)}>
269
+ <SelectableText y={statusLocalY} col={CHROME_TEXT_X + contentWidth - textWidth(presetName)} text={presetName} color={COLORS.presetText} />
270
+ </Box>
271
+ )}
272
+ </Box>
273
+ <Box position="absolute" top={inputStatusRow(rows) + 1} left={CHROME_MARGIN_X} width={blockWidth}>
274
+ <CapText background={permission.color} top={false} width={blockWidth} />
275
+ </Box>
276
+ </Box>
277
+ </Region>
278
+ )
279
+ }