@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,98 @@
1
+ import type { MarkStyle, Segment } from '../../core/segments.ts'
2
+
3
+ const WAVE_STEPS = 6
4
+ const LIGHTEN_SPAN = 0.6
5
+
6
+ interface Hsl {
7
+ h: number
8
+ s: number
9
+ l: number
10
+ }
11
+
12
+ const palettes = new Map<string, string[]>()
13
+
14
+ export function wavePalette(base: string): string[] {
15
+ const cached = palettes.get(base)
16
+ if (cached !== undefined) return cached
17
+ const parsed = hexToHsl(base)
18
+ if (parsed === undefined) return [base]
19
+ const palette: string[] = []
20
+ for (let step = 0; step < WAVE_STEPS; step++) {
21
+ const lightness = parsed.l + (1 - parsed.l) * ((step + 1) / WAVE_STEPS) * LIGHTEN_SPAN
22
+ palette.push(hslToHex({ h: parsed.h, s: parsed.s, l: lightness }))
23
+ }
24
+ palettes.set(base, palette)
25
+ return palette
26
+ }
27
+
28
+ /**
29
+ * Comet wave: the lightest of the six palette colors leads at `head`, trailing
30
+ * progressively darker colors behind it until they blend back into `rest`. The
31
+ * band travels right one step per phase tick, fully exits past the last
32
+ * character, shows one all-rest frame, then re-enters from the left.
33
+ */
34
+ export function waveSegments(text: string, phase: number, palette: string[], rest: string, base: MarkStyle = {}): Segment[] {
35
+ if (text === '' || palette.length === 0) return []
36
+ const clusters = [...text]
37
+ const cycle = clusters.length + WAVE_STEPS
38
+ const head = ((phase % cycle) + cycle) % cycle
39
+ const segments: Segment[] = []
40
+ let runText = ''
41
+ let runColor: string | undefined
42
+ const flush = (): void => {
43
+ if (runText === '' || runColor === undefined) return
44
+ segments.push({ text: runText, style: { ...base, color: runColor } })
45
+ runText = ''
46
+ }
47
+ for (let index = 0; index < clusters.length; index++) {
48
+ const offset = head - index
49
+ const color = offset >= 0 && offset < WAVE_STEPS ? palette[WAVE_STEPS - 1 - offset]! : rest
50
+ if (runColor !== undefined && color !== runColor) {
51
+ flush()
52
+ }
53
+ runColor = color
54
+ runText += clusters[index]
55
+ }
56
+ flush()
57
+ return segments
58
+ }
59
+
60
+ function hexToHsl(hex: string): Hsl | undefined {
61
+ const match = /^#([\da-f]{6})$/i.exec(hex)
62
+ if (match === null) return undefined
63
+ const value = Number.parseInt(match[1]!, 16)
64
+ const r = ((value >> 16) & 0xff) / 255
65
+ const g = ((value >> 8) & 0xff) / 255
66
+ const b = (value & 0xff) / 255
67
+ const max = Math.max(r, g, b)
68
+ const min = Math.min(r, g, b)
69
+ const l = (max + min) / 2
70
+ if (max === min) return { h: 0, s: 0, l }
71
+ const d = max - min
72
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
73
+ let h: number
74
+ if (max === r) h = (g - b) / d + (g < b ? 6 : 0)
75
+ else if (max === g) h = (b - r) / d + 2
76
+ else h = (r - g) / d + 4
77
+ return { h: h * 60, s, l }
78
+ }
79
+
80
+ function hslToHex({ h, s, l }: Hsl): string {
81
+ const hue = ((h % 360) + 360) % 360
82
+ const c = (1 - Math.abs(2 * l - 1)) * s
83
+ const x = c * (1 - Math.abs(((hue / 60) % 2) - 1))
84
+ const m = l - c / 2
85
+ let r = 0
86
+ let g = 0
87
+ let b = 0
88
+ if (hue < 60) [r, g, b] = [c, x, 0]
89
+ else if (hue < 120) [r, g, b] = [x, c, 0]
90
+ else if (hue < 180) [r, g, b] = [0, c, x]
91
+ else if (hue < 240) [r, g, b] = [0, x, c]
92
+ else if (hue < 300) [r, g, b] = [x, 0, c]
93
+ else [r, g, b] = [c, 0, x]
94
+ const toHex = (channel: number): string => Math.round((channel + m) * 255)
95
+ .toString(16)
96
+ .padStart(2, '0')
97
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`
98
+ }
@@ -0,0 +1,22 @@
1
+ import { useCallback, useState } from 'react'
2
+
3
+ export interface OverlayStack<T> {
4
+ top: T | undefined
5
+ push(entry: T): void
6
+ pop(): void
7
+ }
8
+
9
+ export function useOverlayStack<T>(initial: T[] = []): OverlayStack<T> {
10
+ const [entries, setEntries] = useState<T[]>(initial)
11
+ const push = useCallback((entry: T) => {
12
+ setEntries(current => [...current, entry])
13
+ }, [])
14
+ const pop = useCallback(() => {
15
+ setEntries(current => current.slice(0, -1))
16
+ }, [])
17
+ return {
18
+ top: entries[entries.length - 1],
19
+ push,
20
+ pop,
21
+ }
22
+ }
@@ -0,0 +1,193 @@
1
+ import { Box, Text, useInput } from 'ink'
2
+ import { panelAnchorRow } from '../layout-service.ts'
3
+ import { isKeyConsumed } from '../key-arbiter.ts'
4
+ import { useEffect, useState } from 'react'
5
+ import type { Ref } from 'react'
6
+ import { useImperativeHandle } from 'react'
7
+ import { wrapLines } from '../../core/text.ts'
8
+ import { mergeRuns } from '../../core/segments.ts'
9
+ import type { Segment } from '../../core/segments.ts'
10
+ import { COLORS } from '../../theme.ts'
11
+ import { glyphs } from '../../terminal/glyphs.ts'
12
+ import { CHROME_TEXT_X } from '../../core/metrics.ts'
13
+ import { writeCursorShape } from '../../terminal/cursor-shape.ts'
14
+ import { SelectableText } from '../selection.tsx'
15
+ import { PanelSurface, panelLegend } from './surface.tsx'
16
+ import type { PanelPointerHandle, PanelRow } from './surface.tsx'
17
+
18
+ export type { PanelPointerHandle } from './surface.tsx'
19
+
20
+ export const APPROVAL_SECTION_ROWS = 3
21
+ const BUTTON_GAP_TEXT = ' '.repeat(8)
22
+ const BUTTON_ALLOW_TEXT = 'Allow once'
23
+ const ALLOW_BLOCK = ` ${BUTTON_ALLOW_TEXT} `
24
+ const REJECT_BLOCK = ' Reject '
25
+ export const APPROVAL_BUTTON_COLS = {
26
+ allow: CHROME_TEXT_X,
27
+ reject: CHROME_TEXT_X + ALLOW_BLOCK.length + BUTTON_GAP_TEXT.length,
28
+ }
29
+
30
+ export interface ApprovalPanelProps {
31
+ handleRef?: Ref<PanelPointerHandle>
32
+ reason?: string
33
+ command?: string
34
+ background: string
35
+ active: boolean
36
+ columns: number
37
+ rows: number
38
+ innerWidth: number
39
+ blockWidth: number
40
+ onDecide(outcome: 'allowed-once' | 'rejected'): void
41
+ onResize(height: number): void
42
+ }
43
+
44
+ function section(text: string | undefined, innerWidth: number): string[] {
45
+ if (text === undefined || text.trim() === '') return []
46
+ return wrapLines(text, innerWidth)
47
+ }
48
+
49
+ function reasonSection(raw: string | undefined, innerWidth: number): Segment[][] {
50
+ if (raw === undefined || raw.trim() === '') return []
51
+ const colon = raw.indexOf(':')
52
+ const rows: Segment[][] = []
53
+ if (colon === -1) {
54
+ for (const line of wrapLines(raw, Math.max(4, innerWidth))) {
55
+ rows.push([{ text: line, style: { color: COLORS.errorText } }])
56
+ }
57
+ return rows
58
+ }
59
+ rows.push([{ text: raw.slice(0, colon + 1), style: { color: COLORS.errorText } }])
60
+ const description = raw.slice(colon + 1).trim()
61
+ if (description !== '') {
62
+ rows.push([])
63
+ for (const line of wrapLines(description, Math.max(4, innerWidth))) {
64
+ rows.push([{ text: line, style: {} }])
65
+ }
66
+ }
67
+ return rows
68
+ }
69
+
70
+ function commandSection(lines: string[]): Segment[][] {
71
+ return lines.map(line => [{ text: line, style: { color: COLORS.warning } }])
72
+ }
73
+
74
+ function clamp(value: number, max: number): number {
75
+ return Math.max(0, Math.min(max, value))
76
+ }
77
+
78
+ export function ApprovalPanel({ handleRef, reason, command, background, active, columns, rows, innerWidth, blockWidth, onDecide, onResize }: ApprovalPanelProps) {
79
+ const [focusAllow, setFocusAllow] = useState(true)
80
+ const [scroll, setScroll] = useState({ reason: 0, command: 0 })
81
+ const allReason = reasonSection(reason, innerWidth)
82
+ const allCommand = section(command, innerWidth)
83
+ const reasonOverflow = Math.max(0, allReason.length - APPROVAL_SECTION_ROWS)
84
+ const commandOverflow = Math.max(0, allCommand.length - APPROVAL_SECTION_ROWS)
85
+ useEffect(() => {
86
+ setScroll(current => ({
87
+ reason: Math.min(current.reason, reasonOverflow),
88
+ command: Math.min(current.command, commandOverflow),
89
+ }))
90
+ }, [reasonOverflow, commandOverflow])
91
+ const visibleReason = allReason.slice(scroll.reason, scroll.reason + APPROVAL_SECTION_ROWS)
92
+ const visibleCommand = allCommand.slice(scroll.command, scroll.command + APPROVAL_SECTION_ROWS)
93
+ const showCommand = visibleCommand.some(line => line.trim() !== '')
94
+ const body: PanelRow[] = []
95
+ for (const row of visibleReason) body.push({ segments: row })
96
+ if (visibleReason.length > 0) body.push({ segments: [] })
97
+ if (showCommand) {
98
+ for (const row of commandSection(visibleCommand)) body.push({ segments: row })
99
+ body.push({ segments: [] })
100
+ }
101
+ const buttonRowIndex = body.length
102
+ const legend = panelLegend([
103
+ { key: glyphs.pageFlip, description: 'select' },
104
+ { key: 'enter', description: 'submit' },
105
+ ])
106
+ body.push({ segments: mergeRuns([{ text: BUTTON_ALLOW_TEXT + BUTTON_GAP_TEXT + 'Reject', style: {} }]) })
107
+ body.push({ segments: [] })
108
+ body.push({ segments: legend })
109
+ const bodyCount = body.length
110
+ const totalHeight = bodyCount + 2
111
+ const bodyStart = panelAnchorRow(rows, bodyCount)
112
+ useEffect(() => {
113
+ writeCursorShape('hide')
114
+ return () => {
115
+ writeCursorShape('show')
116
+ writeCursorShape('reset')
117
+ }
118
+ }, [])
119
+ useEffect(() => {
120
+ onResize(totalHeight)
121
+ }, [totalHeight])
122
+ useImperativeHandle(handleRef, () => ({
123
+ clickAt(y, x) {
124
+ if (!active) return
125
+ if (y !== bodyStart + buttonRowIndex) return
126
+ if (x >= APPROVAL_BUTTON_COLS.allow && x < APPROVAL_BUTTON_COLS.allow + ALLOW_BLOCK.length) {
127
+ onDecide('allowed-once')
128
+ return
129
+ }
130
+ if (x >= APPROVAL_BUTTON_COLS.reject && x < APPROVAL_BUTTON_COLS.reject + REJECT_BLOCK.length) {
131
+ onDecide('rejected')
132
+ }
133
+ },
134
+ wheel(dir) {
135
+ if (!active) return false
136
+ const delta = dir === -1 ? -1 : 1
137
+ if (reasonOverflow > 0) {
138
+ setScroll(current => ({ ...current, reason: clamp(current.reason + delta, reasonOverflow) }))
139
+ return true
140
+ }
141
+ if (commandOverflow > 0) {
142
+ setScroll(current => ({ ...current, command: clamp(current.command + delta, commandOverflow) }))
143
+ return true
144
+ }
145
+ return false
146
+ },
147
+ }))
148
+ useInput((input, key) => {
149
+ if (!active || isKeyConsumed()) return
150
+ if (key.leftArrow || key.rightArrow || key.tab) {
151
+ setFocusAllow(current => !current)
152
+ return
153
+ }
154
+ if (key.upArrow || key.downArrow) {
155
+ const delta = key.upArrow ? -1 : 1
156
+ if (reasonOverflow > 0) {
157
+ setScroll(current => ({ ...current, reason: clamp(current.reason + delta, reasonOverflow) }))
158
+ } else if (commandOverflow > 0) {
159
+ setScroll(current => ({ ...current, command: clamp(current.command + delta, commandOverflow) }))
160
+ }
161
+ return
162
+ }
163
+ if (key.return) {
164
+ onDecide(focusAllow ? 'allowed-once' : 'rejected')
165
+ return
166
+ }
167
+ if (key.escape) {
168
+ onDecide('rejected')
169
+ }
170
+ })
171
+ const buttonY = bodyStart + buttonRowIndex
172
+ return (
173
+ <PanelSurface
174
+ columns={columns}
175
+ rows={rows}
176
+ body={body.map((row, index) => index === buttonRowIndex
177
+ ? {
178
+ segments: row.segments,
179
+ content: (
180
+ <Box flexDirection="row">
181
+ <SelectableText y={buttonY} col={APPROVAL_BUTTON_COLS.allow} text={ALLOW_BLOCK} inverse={focusAllow} />
182
+ <Text>{BUTTON_GAP_TEXT}</Text>
183
+ <SelectableText y={buttonY} col={APPROVAL_BUTTON_COLS.reject} text={REJECT_BLOCK} inverse={!focusAllow} />
184
+ </Box>
185
+ ),
186
+ }
187
+ : row)}
188
+ bodyStart={bodyStart}
189
+ background={background}
190
+ blockWidth={blockWidth}
191
+ />
192
+ )
193
+ }
@@ -0,0 +1,3 @@
1
+ export { PanelSurface, panelLegend } from './surface.tsx'
2
+ export { panelAnchorRow } from '../layout-service.ts'
3
+ export type { PanelRow, PanelHint, PanelLegendOptions, PanelPointerHandle } from './surface.tsx'