@oh-my-pi/pi-tui 18.4.2 → 18.4.4

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 (463) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/types/apps/cleanse-board.d.ts +17 -0
  3. package/dist/types/apps/git/help.d.ts +16 -0
  4. package/dist/types/apps/git/sidebar.d.ts +18 -0
  5. package/dist/types/apps/live-visualizer.d.ts +10 -0
  6. package/dist/types/apps/ps-top.d.ts +15 -0
  7. package/dist/types/apps/standalone-picker.d.ts +3 -0
  8. package/dist/types/autocomplete.d.ts +8 -0
  9. package/dist/types/chat/advisor-message.d.ts +12 -2
  10. package/dist/types/chat/assistant-message.d.ts +20 -0
  11. package/dist/types/chat/bash-execution.d.ts +9 -0
  12. package/dist/types/chat/chat-transcript-builder.d.ts +6 -1
  13. package/dist/types/chat/collab-prompt-message.d.ts +4 -0
  14. package/dist/types/chat/compaction-summary-message.d.ts +15 -0
  15. package/dist/types/chat/eval-execution.d.ts +8 -0
  16. package/dist/types/chat/execution-shared.d.ts +58 -0
  17. package/dist/types/chat/late-diagnostics-message.d.ts +15 -1
  18. package/dist/types/chat/read-tool-group.d.ts +15 -0
  19. package/dist/types/chat/skill-message.d.ts +9 -0
  20. package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +3 -0
  21. package/dist/types/chat/thinking-display.d.ts +2 -0
  22. package/dist/types/chat/todo-reminder.d.ts +4 -1
  23. package/dist/types/chat/tool-execution.d.ts +25 -1
  24. package/dist/types/chat/transcript-actions.d.ts +37 -0
  25. package/dist/types/chat/transcript-entry.d.ts +3 -1
  26. package/dist/types/chat/user-message.d.ts +25 -0
  27. package/dist/types/chrome/collab-qrcode.d.ts +7 -0
  28. package/dist/types/chrome/context-thresholds.d.ts +11 -0
  29. package/dist/types/chrome/countdown-timer.d.ts +9 -3
  30. package/dist/types/chrome/diff.d.ts +12 -0
  31. package/dist/types/chrome/dynamic-border.d.ts +3 -0
  32. package/dist/types/chrome/format.d.ts +6 -0
  33. package/dist/types/chrome/message-divider.d.ts +13 -0
  34. package/dist/types/chrome/message-frame.d.ts +10 -0
  35. package/dist/types/chrome/message-notice.d.ts +23 -0
  36. package/dist/types/chrome/overlay-box.d.ts +11 -2
  37. package/dist/types/chrome/qrcode.d.ts +7 -0
  38. package/dist/types/chrome/segment-track.d.ts +7 -0
  39. package/dist/types/chrome/selector-helpers.d.ts +0 -6
  40. package/dist/types/chrome/status-notice.d.ts +14 -0
  41. package/dist/types/chrome/streaming-panel.d.ts +3 -0
  42. package/dist/types/chrome/tool-activity.d.ts +3 -0
  43. package/dist/types/chrome/transcript-container.d.ts +13 -0
  44. package/dist/types/chrome/transcript-status.d.ts +15 -3
  45. package/dist/types/components/box.d.ts +7 -0
  46. package/dist/types/components/disclosure.d.ts +9 -0
  47. package/dist/types/components/editor.d.ts +42 -0
  48. package/dist/types/components/form.d.ts +14 -0
  49. package/dist/types/components/image.d.ts +7 -0
  50. package/dist/types/components/input.d.ts +20 -1
  51. package/dist/types/components/key-value-list.d.ts +3 -0
  52. package/dist/types/components/layout/geometry.d.ts +16 -0
  53. package/dist/types/components/layout/row.d.ts +7 -0
  54. package/dist/types/components/layout/split-pane.d.ts +8 -0
  55. package/dist/types/components/layout/stack.d.ts +6 -0
  56. package/dist/types/components/loader.d.ts +48 -0
  57. package/dist/types/components/markdown.d.ts +8 -0
  58. package/dist/types/components/metric.d.ts +7 -0
  59. package/dist/types/components/progress-bar.d.ts +3 -0
  60. package/dist/types/components/scroll-view.d.ts +6 -0
  61. package/dist/types/components/section.d.ts +3 -0
  62. package/dist/types/components/select-list.d.ts +33 -0
  63. package/dist/types/components/settings-list.d.ts +68 -0
  64. package/dist/types/components/spacer.d.ts +4 -0
  65. package/dist/types/components/tab-bar.d.ts +9 -0
  66. package/dist/types/components/table.d.ts +7 -0
  67. package/dist/types/components/text.d.ts +6 -0
  68. package/dist/types/components/tree-view.d.ts +9 -0
  69. package/dist/types/components/truncated-text.d.ts +3 -0
  70. package/dist/types/components/wizard-step.d.ts +6 -0
  71. package/dist/types/hotkeys-markdown.d.ts +24 -0
  72. package/dist/types/index.d.ts +5 -0
  73. package/dist/types/latex-block.d.ts +7 -0
  74. package/dist/types/loop-watchdog.d.ts +9 -0
  75. package/dist/types/native/apply.d.ts +39 -0
  76. package/dist/types/native/backend.d.ts +94 -0
  77. package/dist/types/native/blobs.d.ts +29 -0
  78. package/dist/types/native/describe.d.ts +56 -0
  79. package/dist/types/native/encode.d.ts +50 -0
  80. package/dist/types/native/icons.d.ts +18 -0
  81. package/dist/types/native/memo.d.ts +27 -0
  82. package/dist/types/native/node.d.ts +107 -0
  83. package/dist/types/native/overlay.d.ts +64 -0
  84. package/dist/types/native/picker.d.ts +124 -0
  85. package/dist/types/native/reconcile.d.ts +41 -0
  86. package/dist/types/native/settle.d.ts +11 -0
  87. package/dist/types/native/spans.d.ts +21 -0
  88. package/dist/types/native/state.d.ts +6 -0
  89. package/dist/types/native/tone.d.ts +24 -0
  90. package/dist/types/overlays/advisor-config.d.ts +20 -0
  91. package/dist/types/overlays/agent-hub-renderer.d.ts +27 -11
  92. package/dist/types/overlays/agent-hub.d.ts +10 -0
  93. package/dist/types/overlays/agent-transcript-viewer.d.ts +7 -0
  94. package/dist/types/overlays/agents-hub.d.ts +8 -0
  95. package/dist/types/overlays/annotation-overlay.d.ts +3 -0
  96. package/dist/types/overlays/ask-dialog.d.ts +23 -0
  97. package/dist/types/overlays/bordered-loader.d.ts +3 -0
  98. package/dist/types/overlays/btw-history-panel.d.ts +9 -0
  99. package/dist/types/overlays/btw-panel.d.ts +3 -0
  100. package/dist/types/overlays/cleanse-panel.d.ts +5 -2
  101. package/dist/types/overlays/composer-shape-preview.d.ts +7 -1
  102. package/dist/types/overlays/copy-selector.d.ts +38 -0
  103. package/dist/types/overlays/error-banner.d.ts +9 -0
  104. package/dist/types/overlays/extensions/extension-list.d.ts +1 -0
  105. package/dist/types/overlays/history-search.d.ts +18 -0
  106. package/dist/types/overlays/hook-editor.d.ts +28 -1
  107. package/dist/types/overlays/hook-input.d.ts +3 -0
  108. package/dist/types/overlays/hook-selector.d.ts +10 -0
  109. package/dist/types/overlays/hub-frame.d.ts +18 -0
  110. package/dist/types/overlays/jobs-panel.d.ts +24 -0
  111. package/dist/types/overlays/login-dialog.d.ts +10 -0
  112. package/dist/types/overlays/logout-account-selector.d.ts +4 -0
  113. package/dist/types/overlays/mcp-add-wizard.d.ts +4 -0
  114. package/dist/types/overlays/model-browser.d.ts +69 -0
  115. package/dist/types/overlays/model-hub.d.ts +7 -0
  116. package/dist/types/overlays/model-picker.d.ts +9 -1
  117. package/dist/types/overlays/move-overlay.d.ts +3 -0
  118. package/dist/types/overlays/oauth-selector.d.ts +4 -0
  119. package/dist/types/overlays/omfg-panel.d.ts +3 -0
  120. package/dist/types/overlays/pause-screen.d.ts +12 -0
  121. package/dist/types/overlays/plan-review-overlay.d.ts +5 -0
  122. package/dist/types/overlays/plan-save-overlay.d.ts +10 -0
  123. package/dist/types/overlays/plugin-selector.d.ts +4 -0
  124. package/dist/types/overlays/plugin-settings.d.ts +30 -0
  125. package/dist/types/overlays/queue-mode-selector.d.ts +4 -0
  126. package/dist/types/overlays/reset-usage-selector.d.ts +8 -0
  127. package/dist/types/overlays/rewind-selector.d.ts +5 -0
  128. package/dist/types/overlays/session-info-overlay.d.ts +19 -1
  129. package/dist/types/overlays/session-selector.d.ts +64 -1
  130. package/dist/types/overlays/settings-defs.d.ts +14 -0
  131. package/dist/types/overlays/settings-selector.d.ts +21 -0
  132. package/dist/types/overlays/show-images-selector.d.ts +4 -0
  133. package/dist/types/overlays/snapcompact-shape-preview.d.ts +2 -0
  134. package/dist/types/overlays/stats-notice.d.ts +8 -0
  135. package/dist/types/overlays/theme-selector.d.ts +11 -0
  136. package/dist/types/overlays/thinking-selector.d.ts +4 -0
  137. package/dist/types/overlays/tree-selector.d.ts +60 -2
  138. package/dist/types/overlays/usage-dashboard.d.ts +26 -3
  139. package/dist/types/overlays/usage-row.d.ts +36 -2
  140. package/dist/types/prompt/attachment-chips.d.ts +8 -0
  141. package/dist/types/prompt/composer-attachments.d.ts +15 -3
  142. package/dist/types/prompt/composer.d.ts +18 -1
  143. package/dist/types/prompt/custom-editor.d.ts +48 -2
  144. package/dist/types/prompt/editor-top-gap.d.ts +2 -0
  145. package/dist/types/prompt/macos-spelling.d.ts +6 -0
  146. package/dist/types/prompt/magic-keywords.d.ts +9 -0
  147. package/dist/types/prompt/queued-messages.d.ts +23 -0
  148. package/dist/types/prompt/skill-tokens.d.ts +4 -2
  149. package/dist/types/prompt/welcome.d.ts +16 -0
  150. package/dist/types/render/code-cell.d.ts +10 -0
  151. package/dist/types/render/file-list.d.ts +8 -0
  152. package/dist/types/render/hyperlink.d.ts +14 -0
  153. package/dist/types/render/output-block.d.ts +43 -0
  154. package/dist/types/render/output-pane.d.ts +17 -0
  155. package/dist/types/render/tool-card.d.ts +8 -0
  156. package/dist/types/render/tree-list.d.ts +8 -0
  157. package/dist/types/render/width-aware-text.d.ts +7 -0
  158. package/dist/types/setup/scenes/outro.d.ts +7 -0
  159. package/dist/types/setup/scenes/sign-in.d.ts +16 -9
  160. package/dist/types/setup/scenes/splash.d.ts +8 -0
  161. package/dist/types/setup/scenes/types.d.ts +6 -27
  162. package/dist/types/setup/wizard-overlay.d.ts +9 -0
  163. package/dist/types/status-line/component.d.ts +49 -5
  164. package/dist/types/status-line/context-usage.d.ts +20 -0
  165. package/dist/types/status-line/footer.d.ts +8 -0
  166. package/dist/types/status-line/metrics.d.ts +1 -2
  167. package/dist/types/status-line/segments.d.ts +10 -1
  168. package/dist/types/status-line/types.d.ts +40 -1
  169. package/dist/types/stdin-buffer.d.ts +10 -7
  170. package/dist/types/terminal-capabilities.d.ts +1 -1
  171. package/dist/types/terminal.d.ts +36 -0
  172. package/dist/types/theme/defaults/index.d.ts +100 -0
  173. package/dist/types/theme/schema.d.ts +2 -0
  174. package/dist/types/theme/shimmer.d.ts +8 -0
  175. package/dist/types/theme/theme-class.d.ts +2 -0
  176. package/dist/types/theme/theme.d.ts +33 -1
  177. package/dist/types/tools/ask.d.ts +3 -0
  178. package/dist/types/tools/ast-edit.d.ts +10 -1
  179. package/dist/types/tools/ast-grep.d.ts +10 -1
  180. package/dist/types/tools/autoresearch.d.ts +21 -4
  181. package/dist/types/tools/bash.d.ts +30 -1
  182. package/dist/types/tools/cfg-render.d.ts +5 -1
  183. package/dist/types/tools/debug.d.ts +3 -1
  184. package/dist/types/tools/default-renderer.d.ts +6 -1
  185. package/dist/types/tools/edit.d.ts +14 -1
  186. package/dist/types/tools/eval.d.ts +12 -1
  187. package/dist/types/tools/fetch.d.ts +16 -1
  188. package/dist/types/tools/find.d.ts +15 -1
  189. package/dist/types/tools/github.d.ts +3 -0
  190. package/dist/types/tools/glob.d.ts +14 -1
  191. package/dist/types/tools/goal.d.ts +3 -1
  192. package/dist/types/tools/grep.d.ts +14 -1
  193. package/dist/types/tools/grouped-file-output.d.ts +58 -0
  194. package/dist/types/tools/json-tree.d.ts +20 -0
  195. package/dist/types/tools/lsp.d.ts +3 -10
  196. package/dist/types/tools/mcp.d.ts +16 -1
  197. package/dist/types/tools/memory.d.ts +26 -0
  198. package/dist/types/tools/native-view.d.ts +90 -0
  199. package/dist/types/tools/proc-render.d.ts +7 -1
  200. package/dist/types/tools/read.d.ts +23 -1
  201. package/dist/types/tools/renderer.d.ts +57 -0
  202. package/dist/types/tools/report-tool-issue.d.ts +3 -0
  203. package/dist/types/tools/resolve.d.ts +14 -1
  204. package/dist/types/tools/task.d.ts +3 -0
  205. package/dist/types/tools/think.d.ts +3 -0
  206. package/dist/types/tools/todo.d.ts +12 -0
  207. package/dist/types/tools/vibe.d.ts +3 -1
  208. package/dist/types/tools/wait.d.ts +3 -0
  209. package/dist/types/tools/web-search-types.d.ts +90 -0
  210. package/dist/types/tools/web-search.d.ts +9 -168
  211. package/dist/types/tools/write.d.ts +14 -1
  212. package/dist/types/tools/xdev.d.ts +13 -0
  213. package/dist/types/tui.d.ts +49 -0
  214. package/package.json +9 -9
  215. package/src/apps/autoresearch-dashboard.ts +362 -4
  216. package/src/apps/cleanse-board.ts +219 -1
  217. package/src/apps/cleanse-picker.ts +9 -1
  218. package/src/apps/debug/log-viewer.ts +15 -15
  219. package/src/apps/debug/protocol-probe.ts +2 -20
  220. package/src/apps/git/avatar.ts +2 -22
  221. package/src/apps/git/git-tui.ts +406 -59
  222. package/src/apps/git/help.ts +130 -0
  223. package/src/apps/git/sidebar.ts +491 -0
  224. package/src/apps/live-visualizer.ts +65 -0
  225. package/src/apps/ps-top.ts +448 -17
  226. package/src/apps/session-picker.ts +1 -0
  227. package/src/apps/setup-model-picker.ts +5 -1
  228. package/src/apps/standalone-picker.ts +55 -1
  229. package/src/autocomplete.ts +44 -1
  230. package/src/chat/advisor-message.ts +72 -3
  231. package/src/chat/assistant-message.ts +538 -51
  232. package/src/chat/background-tan-message.ts +4 -4
  233. package/src/chat/bash-execution.ts +81 -0
  234. package/src/chat/cache-invalidation-marker.ts +8 -0
  235. package/src/chat/chat-transcript-builder.ts +37 -6
  236. package/src/chat/collab-prompt-message.ts +15 -3
  237. package/src/chat/compaction-summary-message.ts +74 -1
  238. package/src/chat/custom-message.ts +3 -1
  239. package/src/chat/eval-execution.ts +57 -0
  240. package/src/chat/execution-shared.ts +153 -1
  241. package/src/chat/hook-message.ts +1 -0
  242. package/src/chat/late-diagnostics-message.ts +85 -2
  243. package/src/chat/read-tool-group.ts +341 -1
  244. package/src/chat/served-model-marker.ts +8 -0
  245. package/src/chat/skill-message.ts +63 -0
  246. package/src/chat/stripped-tool-calls-placeholder.ts +18 -10
  247. package/src/chat/thinking-display.ts +73 -11
  248. package/src/chat/todo-reminder.ts +34 -4
  249. package/src/chat/tool-execution.ts +385 -10
  250. package/src/chat/transcript-actions.ts +38 -0
  251. package/src/chat/transcript-browser.ts +2 -9
  252. package/src/chat/transcript-entry.ts +10 -1
  253. package/src/chat/transcript-render-helpers.ts +16 -15
  254. package/src/chat/ttsr-notification.ts +38 -1
  255. package/src/chat/user-message.ts +167 -4
  256. package/src/chrome/collab-qrcode.ts +27 -2
  257. package/src/chrome/context-thresholds.ts +34 -0
  258. package/src/chrome/countdown-timer.ts +24 -1
  259. package/src/chrome/diff.ts +60 -1
  260. package/src/chrome/dynamic-border.ts +7 -0
  261. package/src/chrome/format.ts +12 -0
  262. package/src/chrome/message-divider.ts +33 -1
  263. package/src/chrome/message-frame.ts +53 -10
  264. package/src/chrome/message-notice.ts +71 -0
  265. package/src/chrome/overlay-box.ts +45 -2
  266. package/src/chrome/qrcode.ts +28 -0
  267. package/src/chrome/segment-track.ts +26 -0
  268. package/src/chrome/selector-helpers.ts +1 -17
  269. package/src/chrome/status-notice.ts +46 -0
  270. package/src/chrome/streaming-panel.ts +25 -1
  271. package/src/chrome/tool-activity.ts +20 -0
  272. package/src/chrome/transcript-container.ts +139 -17
  273. package/src/chrome/transcript-status.ts +40 -11
  274. package/src/components/box.ts +51 -0
  275. package/src/components/disclosure.ts +33 -0
  276. package/src/components/editor.ts +239 -36
  277. package/src/components/form.ts +93 -9
  278. package/src/components/image.ts +30 -0
  279. package/src/components/input.ts +77 -26
  280. package/src/components/key-value-list.ts +18 -0
  281. package/src/components/layout/geometry.ts +44 -0
  282. package/src/components/layout/row.ts +30 -0
  283. package/src/components/layout/split-pane.ts +65 -0
  284. package/src/components/layout/stack.ts +27 -0
  285. package/src/components/loader.ts +183 -1
  286. package/src/components/markdown.ts +131 -56
  287. package/src/components/metric.ts +39 -0
  288. package/src/components/progress-bar.ts +28 -0
  289. package/src/components/scroll-view.ts +23 -0
  290. package/src/components/section.ts +16 -0
  291. package/src/components/select-list.ts +174 -25
  292. package/src/components/settings-list.ts +264 -7
  293. package/src/components/spacer.ts +17 -0
  294. package/src/components/tab-bar.ts +42 -0
  295. package/src/components/table.ts +34 -0
  296. package/src/components/text.ts +23 -0
  297. package/src/components/tree-view.ts +65 -0
  298. package/src/components/truncated-text.ts +14 -0
  299. package/src/components/wizard-step.ts +18 -9
  300. package/src/debug-server.ts +14 -7
  301. package/src/hotkeys-markdown.ts +251 -69
  302. package/src/index.ts +6 -0
  303. package/src/latex-block.ts +11 -0
  304. package/src/loop-watchdog.ts +38 -10
  305. package/src/native/apply.ts +275 -0
  306. package/src/native/backend.ts +682 -0
  307. package/src/native/blobs.ts +70 -0
  308. package/src/native/describe.ts +124 -0
  309. package/src/native/encode.ts +233 -0
  310. package/src/native/icons.ts +184 -0
  311. package/src/native/memo.ts +64 -0
  312. package/src/native/node.ts +112 -0
  313. package/src/native/overlay.ts +131 -0
  314. package/src/native/picker.ts +333 -0
  315. package/src/native/reconcile.ts +1063 -0
  316. package/src/native/settle.ts +23 -0
  317. package/src/native/spans.ts +308 -0
  318. package/src/native/state.ts +28 -0
  319. package/src/native/tone.ts +79 -0
  320. package/src/overlays/advisor-config.ts +420 -0
  321. package/src/overlays/agent-hub-renderer.ts +155 -18
  322. package/src/overlays/agent-hub.ts +1037 -44
  323. package/src/overlays/agent-transcript-viewer.ts +129 -0
  324. package/src/overlays/agents-hub.ts +688 -53
  325. package/src/overlays/annotation-overlay.ts +391 -52
  326. package/src/overlays/ask-dialog.ts +435 -24
  327. package/src/overlays/bordered-loader.ts +17 -0
  328. package/src/overlays/btw-history-panel.ts +182 -0
  329. package/src/overlays/btw-panel.ts +68 -2
  330. package/src/overlays/cleanse-panel.ts +105 -15
  331. package/src/overlays/codex-reset-fireworks.ts +65 -5
  332. package/src/overlays/composer-shape-preview.ts +26 -1
  333. package/src/overlays/copy-selector.ts +545 -23
  334. package/src/overlays/error-banner.ts +44 -0
  335. package/src/overlays/extensions/extension-list.ts +23 -17
  336. package/src/overlays/history-search.ts +175 -33
  337. package/src/overlays/hook-editor.ts +113 -6
  338. package/src/overlays/hook-input.ts +22 -1
  339. package/src/overlays/hook-selector.ts +311 -22
  340. package/src/overlays/hub-frame.ts +80 -0
  341. package/src/overlays/jobs-panel.ts +112 -0
  342. package/src/overlays/login-dialog.ts +183 -1
  343. package/src/overlays/logout-account-selector.ts +102 -4
  344. package/src/overlays/mcp-add-wizard.ts +232 -53
  345. package/src/overlays/model-browser.ts +681 -22
  346. package/src/overlays/model-hub.ts +1402 -78
  347. package/src/overlays/model-picker.ts +184 -7
  348. package/src/overlays/move-overlay.ts +45 -0
  349. package/src/overlays/oauth-selector.ts +227 -23
  350. package/src/overlays/omfg-panel.ts +86 -2
  351. package/src/overlays/pause-screen.ts +55 -4
  352. package/src/overlays/plan-review-overlay.ts +352 -0
  353. package/src/overlays/plan-save-overlay.ts +45 -0
  354. package/src/overlays/plugin-selector.ts +21 -1
  355. package/src/overlays/plugin-settings.ts +195 -5
  356. package/src/overlays/queue-mode-selector.ts +19 -1
  357. package/src/overlays/reset-usage-selector.ts +110 -40
  358. package/src/overlays/rewind-selector.ts +458 -44
  359. package/src/overlays/session-account-selector.ts +1 -1
  360. package/src/overlays/session-info-overlay.ts +137 -1
  361. package/src/overlays/session-selector.ts +807 -46
  362. package/src/overlays/settings-defs.ts +41 -0
  363. package/src/overlays/settings-selector.ts +585 -33
  364. package/src/overlays/show-images-selector.ts +18 -1
  365. package/src/overlays/snapcompact-shape-preview.ts +48 -3
  366. package/src/overlays/stats-notice.ts +31 -0
  367. package/src/overlays/theme-selector.ts +31 -1
  368. package/src/overlays/thinking-selector.ts +22 -1
  369. package/src/overlays/tree-selector.ts +570 -110
  370. package/src/overlays/usage-dashboard.ts +587 -12
  371. package/src/overlays/usage-row.ts +161 -25
  372. package/src/prompt/attachment-chips.ts +60 -0
  373. package/src/prompt/composer-attachments.ts +31 -9
  374. package/src/prompt/composer.ts +55 -2
  375. package/src/prompt/custom-editor.ts +302 -4
  376. package/src/prompt/editor-top-gap.ts +8 -0
  377. package/src/prompt/macos-spelling.ts +24 -9
  378. package/src/prompt/magic-keywords.ts +22 -1
  379. package/src/prompt/model-mention-autocomplete.ts +1 -0
  380. package/src/prompt/queued-messages.ts +101 -0
  381. package/src/prompt/skill-tokens.ts +5 -3
  382. package/src/prompt/welcome.ts +213 -6
  383. package/src/render/code-cell.ts +103 -2
  384. package/src/render/file-list.ts +29 -0
  385. package/src/render/hyperlink.ts +32 -0
  386. package/src/render/output-block.ts +110 -0
  387. package/src/render/output-pane.ts +37 -0
  388. package/src/render/tool-card.ts +137 -0
  389. package/src/render/tree-list.ts +39 -0
  390. package/src/render/width-aware-text.ts +20 -0
  391. package/src/setup/scenes/composer.ts +29 -0
  392. package/src/setup/scenes/glyph.ts +15 -0
  393. package/src/setup/scenes/model.ts +36 -8
  394. package/src/setup/scenes/outro.ts +28 -1
  395. package/src/setup/scenes/sign-in.ts +138 -24
  396. package/src/setup/scenes/splash.ts +28 -1
  397. package/src/setup/scenes/theme.ts +88 -3
  398. package/src/setup/scenes/types.ts +7 -28
  399. package/src/setup/startup-splash.ts +46 -9
  400. package/src/setup/wizard-overlay.ts +140 -23
  401. package/src/setup/wizard.ts +1 -1
  402. package/src/status-line/component.ts +414 -18
  403. package/src/status-line/context-usage.ts +279 -50
  404. package/src/status-line/footer.ts +94 -1
  405. package/src/status-line/metrics.ts +3 -6
  406. package/src/status-line/segments.ts +463 -97
  407. package/src/status-line/separators.ts +3 -16
  408. package/src/status-line/types.ts +43 -1
  409. package/src/stdin-buffer.ts +27 -21
  410. package/src/terminal-capabilities.ts +7 -0
  411. package/src/terminal.ts +153 -11
  412. package/src/theme/defaults/dark-neon-noir.json +100 -0
  413. package/src/theme/defaults/index.ts +2 -0
  414. package/src/theme/defaults/light-prism.json +5 -5
  415. package/src/theme/defaults/onyx.json +62 -62
  416. package/src/theme/mermaid-cache.ts +10 -1
  417. package/src/theme/schema.ts +15 -0
  418. package/src/theme/shimmer.ts +33 -0
  419. package/src/theme/theme-class.ts +24 -19
  420. package/src/theme/theme.ts +111 -9
  421. package/src/theme/tui-adapters.ts +14 -1
  422. package/src/tools/ask.ts +157 -1
  423. package/src/tools/ast-edit.ts +174 -1
  424. package/src/tools/ast-grep.ts +82 -2
  425. package/src/tools/autoresearch.ts +149 -1
  426. package/src/tools/bash.ts +233 -62
  427. package/src/tools/cfg-render.ts +66 -1
  428. package/src/tools/debug.ts +78 -1
  429. package/src/tools/default-renderer.ts +35 -1
  430. package/src/tools/edit.ts +357 -31
  431. package/src/tools/eval.ts +257 -1
  432. package/src/tools/fetch.ts +102 -3
  433. package/src/tools/find.ts +127 -1
  434. package/src/tools/github.ts +119 -10
  435. package/src/tools/glob.ts +116 -1
  436. package/src/tools/goal.ts +56 -1
  437. package/src/tools/grep.ts +83 -2
  438. package/src/tools/grouped-file-output.ts +205 -0
  439. package/src/tools/json-tree.ts +116 -1
  440. package/src/tools/lsp.ts +318 -1
  441. package/src/tools/mcp.ts +80 -1
  442. package/src/tools/memory.ts +93 -11
  443. package/src/tools/native-view.ts +292 -0
  444. package/src/tools/proc-render.ts +221 -1
  445. package/src/tools/read.ts +199 -4
  446. package/src/tools/renderer.ts +61 -0
  447. package/src/tools/report-tool-issue.ts +7 -1
  448. package/src/tools/resolve.ts +63 -1
  449. package/src/tools/streaming-output.ts +79 -16
  450. package/src/tools/task.ts +406 -4
  451. package/src/tools/think.ts +19 -1
  452. package/src/tools/todo.ts +89 -0
  453. package/src/tools/vibe.ts +230 -2
  454. package/src/tools/wait.ts +181 -1
  455. package/src/tools/web-search-types.ts +99 -0
  456. package/src/tools/web-search.ts +111 -170
  457. package/src/tools/write.ts +144 -5
  458. package/src/tools/xdev.ts +64 -1
  459. package/src/tui.ts +295 -4
  460. package/dist/types/setup/scenes/providers.d.ts +0 -3
  461. package/dist/types/setup/scenes/web-search.d.ts +0 -22
  462. package/src/setup/scenes/providers.ts +0 -110
  463. package/src/setup/scenes/web-search.ts +0 -165
@@ -3,6 +3,10 @@ import { WidthAwareText } from "../render/index";
3
3
  import { theme } from "../theme/theme";
4
4
  import { DynamicBorder } from "../chrome/dynamic-border";
5
5
  import { formatErrorBlock } from "../chrome/error-block";
6
+ import type { NativeNode, NativeUiEvent } from "../native/node";
7
+ import { node, span, text } from "../native/describe";
8
+ import { actionButton } from "../native/overlay";
9
+ import { plainText } from "../native/spans";
6
10
 
7
11
  /** Max wrapped rows of the error message shown in the pinned banner. */
8
12
  const MAX_BANNER_ROWS = 4;
@@ -17,8 +21,14 @@ const MAX_BANNER_ROWS = 4;
17
21
  * overflow row reveals the full body inline in the transcript.
18
22
  */
19
23
  export class ErrorBannerComponent extends Container {
24
+ readonly #message: string;
25
+ /** Natively the strip shows the headline; Details unfolds the whole message in place. */
26
+ #expanded = false;
27
+ #native: NativeNode | undefined;
28
+
20
29
  constructor(message: string) {
21
30
  super();
31
+ this.#message = plainText(message).trim();
22
32
  this.addChild(new Spacer(1));
23
33
  this.addChild(new DynamicBorder(str => theme.fg("error", str)));
24
34
  this.addChild(
@@ -36,4 +46,38 @@ export class ErrorBannerComponent extends Container {
36
46
  this.addChild(new Text(theme.fg("dim", "Dismissed when you send your next message."), 1, 0));
37
47
  this.addChild(new DynamicBorder(str => theme.fg("error", str)));
38
48
  }
49
+
50
+ /**
51
+ * An error-tinted strip pinned above the composer: the message's first line
52
+ * (the transcript's error frame keeps the full message and its actions)
53
+ * and a Details button that unfolds the rest here.
54
+ */
55
+ override describe(): NativeNode {
56
+ if (this.#native) return this.#native;
57
+ const headline =
58
+ this.#message
59
+ .split("\n")
60
+ .find(line => line.trim() !== "")
61
+ ?.trim() || "Error";
62
+ const multiline = this.#message !== headline;
63
+ const message = this.#expanded
64
+ ? text([span(this.#message, "error")], { wrap: "word", grow: 1 })
65
+ : text([span(headline, "error")], { truncate: "end", lines: 1, grow: 1, title: this.#message });
66
+ const children: NativeNode[] = [message];
67
+ if (multiline || headline.length > 80) {
68
+ children.push(actionButton(this.#expanded ? "Less" : "Details", "details"));
69
+ }
70
+ this.#native = node(
71
+ "row",
72
+ { role: "omp.errorBanner", tone: "error", gap: "sm", align: this.#expanded ? "start" : "center" },
73
+ children,
74
+ );
75
+ return this.#native;
76
+ }
77
+
78
+ handleNativeEvent(event: NativeUiEvent): void {
79
+ if (event.type !== "action" || event.act !== "details") return;
80
+ this.#expanded = !this.#expanded;
81
+ this.#native = undefined;
82
+ }
39
83
  }
@@ -10,7 +10,8 @@ import { matchesKey } from "../../keys";
10
10
  import { padding, truncateToWidth, visibleWidth } from "../../utils";
11
11
  import { theme } from "../../theme";
12
12
  import { matchesSelectDown, matchesSelectUp } from "../../keybinding-matchers";
13
- import { contentRowWidth, renderScrollableList, searchableChar } from "../../chrome/selector-helpers";
13
+ import { contentRowWidth, renderScrollableList } from "../../chrome/selector-helpers";
14
+ import { Input } from "../../components/input";
14
15
  import { MenuSelection } from "../../components/menu-selection";
15
16
  import { scrollOffsetForRow, viewportRange } from "../../components/scroll-viewport";
16
17
  import { sanitizeDisplayLine } from "./display-text";
@@ -102,6 +103,8 @@ export class ExtensionList implements Component {
102
103
 
103
104
  #extensions: Extension[];
104
105
  readonly #callbacks: ExtensionListCallbacks;
106
+ /** Search field; its value is pushed into the menu query whenever it changes. */
107
+ readonly #search = new Input();
105
108
 
106
109
  constructor(extensions: Extension[], callbacks: ExtensionListCallbacks = {}, maxVisible?: number) {
107
110
  this.#extensions = extensions;
@@ -110,6 +113,7 @@ export class ExtensionList implements Component {
110
113
  this.#mcpSource = callbacks.mcpSource;
111
114
  this.#toolSource = callbacks.toolSource;
112
115
  this.#maxVisible = maxVisible ?? DEFAULT_MAX_VISIBLE;
116
+ this.#search.prompt = "";
113
117
  this.#menu = new MenuSelection<ListItem>(this.#buildListItems(""), {
114
118
  getKey: getListItemKey,
115
119
  getSearchText: getListItemSearchText,
@@ -169,7 +173,9 @@ export class ExtensionList implements Component {
169
173
  return item?.type === "kind-header" ? item.kind : null;
170
174
  }
171
175
 
176
+ /** Replace the search text (caret to end) and refilter. */
172
177
  setSearchQuery(query: string): void {
178
+ if (this.#search.getValue() !== query) this.#search.setValue(query);
173
179
  this.#menu.setQuery(query, false);
174
180
  this.#scrollOffset = 0;
175
181
  this.#notifySelectionChange();
@@ -188,10 +194,14 @@ export class ExtensionList implements Component {
188
194
 
189
195
  // Search bar
190
196
  const searchPrefix = theme.fg("muted", "Search: ");
191
- const query = this.#menu.query;
192
- const searchText = query || (this.#focused ? "" : theme.fg("dim", "type to filter"));
193
- const cursor = this.#focused ? theme.fg("accent", "_") : "";
194
- lines.push(searchPrefix + searchText + cursor);
197
+ const query = this.#search.getValue();
198
+ if (this.#focused) {
199
+ const fieldWidth = Math.max(1, width - visibleWidth(searchPrefix));
200
+ const [field = ""] = this.#search.render(Math.min(fieldWidth, visibleWidth(query) + 1));
201
+ lines.push(searchPrefix + field);
202
+ } else {
203
+ lines.push(searchPrefix + (query || theme.fg("dim", "type to filter")));
204
+ }
195
205
  lines.push("");
196
206
 
197
207
  const items = this.#menu.visibleItems;
@@ -631,18 +641,14 @@ export class ExtensionList implements Component {
631
641
  return;
632
642
  }
633
643
 
634
- // Backspace: Delete from search query
635
- if (matchesKey(data, "backspace")) {
636
- if (this.#menu.query.length > 0) {
637
- this.setSearchQuery(this.#menu.query.slice(0, -1));
638
- }
639
- return;
640
- }
641
-
642
- // Printable characters -> search
643
- const char = searchableChar(data);
644
- if (char !== null) {
645
- this.setSearchQuery(this.#menu.query + char);
644
+ // Everything else edits the search field; refilter only when the text changed.
645
+ const before = this.#search.getValue();
646
+ this.#search.handleInput(data);
647
+ const after = this.#search.getValue();
648
+ if (after !== before) {
649
+ this.#menu.setQuery(after, false);
650
+ this.#scrollOffset = 0;
651
+ this.#notifySelectionChange();
646
652
  }
647
653
  }
648
654
 
@@ -21,6 +21,8 @@ import {
21
21
  export interface HistorySearchEntry {
22
22
  prompt: string;
23
23
  created_at: number;
24
+ /** Project folder the prompt was typed in. */
25
+ cwd?: string;
24
26
  }
25
27
 
26
28
  /** Searchable prompt history supplied by the host. */
@@ -28,11 +30,37 @@ export interface HistorySource {
28
30
  search(query: string, limit: number): HistorySearchEntry[];
29
31
  getRecent(limit: number): HistorySearchEntry[];
30
32
  }
31
- import { editorKeys, keyHint, rawKeyHint } from "../chrome/keybinding-hints";
33
+ import { boundKeys, editorKeys, keyHint, rawKeyHint } from "../chrome/keybinding-hints";
32
34
  import { OverlayPanel } from "../chrome/overlay-box";
33
35
  import { contentRowWidth, renderScrollableList } from "../chrome/selector-helpers";
34
36
  import { MenuSelection } from "../components/menu-selection";
35
37
  import { centeredViewportRange } from "../components/scroll-viewport";
38
+ import type { KeyName } from "../key-hint-format";
39
+ import type { TspPickerItem } from "@oh-my-pi/pi-wire";
40
+ import { col, keyed, node, span } from "../native/describe";
41
+ import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node";
42
+ import { actionHint, hintsRow, overlayCard } from "../native/overlay";
43
+ import { picker, pickerAction, pickerAge, pickerDate, pickerEvent, pickerHits, pickerQuery } from "../native/picker";
44
+ import { shortenPath } from "../render/render-utils";
45
+
46
+ const ENTER_KEYS: readonly KeyName[] = ["enter"];
47
+
48
+ /** Native item key of a history entry: key-path safe (prompts may hold `/` and newlines), stable across queries. */
49
+ function nativeEntryKey(entry: HistorySearchEntry): string {
50
+ return `${entry.created_at}-${Bun.hash(entry.prompt).toString(36)}`;
51
+ }
52
+
53
+ interface HistoryNativeMemo {
54
+ picker: boolean;
55
+ items: readonly HistorySearchEntry[];
56
+ selected: HistorySearchEntry | undefined;
57
+ query: string;
58
+ cursor: number;
59
+ node: NativeNode;
60
+ }
61
+
62
+ /** Key of the `picker` child a dock-mounted history search describes (hoisted into `layer`). */
63
+ const PICKER_KEY = "picker";
36
64
 
37
65
  /** Visible result rows; also the jump distance for PageUp/PageDown. */
38
66
  const MAX_VISIBLE = 10;
@@ -47,46 +75,31 @@ function queryTokens(query: string): string[] {
47
75
 
48
76
  /** Wrap every case-insensitive occurrence of any token in `text` with the accent color. */
49
77
  function highlightTokens(text: string, tokens: string[]): string {
50
- if (tokens.length === 0) return text;
51
-
52
- const lower = text.toLowerCase();
53
- const ranges: Array<[number, number]> = [];
54
- for (const tok of tokens) {
55
- let from = lower.indexOf(tok);
56
- while (from !== -1) {
57
- ranges.push([from, from + tok.length]);
58
- from = lower.indexOf(tok, from + tok.length);
59
- }
60
- }
78
+ const ranges = pickerHits(text, tokens);
61
79
  if (ranges.length === 0) return text;
62
-
63
- ranges.sort((a, b) => a[0] - b[0]);
64
80
  let out = "";
65
81
  let pos = 0;
66
82
  for (const [start, end] of ranges) {
67
- if (end <= pos) continue; // fully covered by a previous (merged) range
68
- const from = Math.max(start, pos);
69
- if (from > pos) out += text.slice(pos, from);
70
- out += theme.fg("accent", text.slice(from, end));
83
+ if (start > pos) out += text.slice(pos, start);
84
+ out += theme.fg("accent", text.slice(start, end));
71
85
  pos = end;
72
86
  }
73
87
  if (pos < text.length) out += text.slice(pos);
74
88
  return out;
75
89
  }
76
90
 
77
- /** Compact "time since" label (e.g. `now`, `5m`, `2h`, `3d`, `2w`, `6mo`, `1y`) from epoch seconds. */
78
- function relativeTime(epochSeconds: number): string {
79
- const seconds = Math.max(0, Math.floor(Date.now() / 1000) - epochSeconds);
80
- if (seconds < 60) return "now";
81
- const minutes = Math.floor(seconds / 60);
82
- if (minutes < 60) return `${minutes}m`;
83
- const hours = Math.floor(minutes / 60);
84
- if (hours < 24) return `${hours}h`;
85
- const days = Math.floor(hours / 24);
86
- if (days < 7) return `${days}d`;
87
- if (days < 30) return `${Math.floor(days / 7)}w`;
88
- if (days < 365) return `${Math.floor(days / 30)}mo`;
89
- return `${Math.floor(days / 365)}y`;
91
+ /** A past prompt as a picker row: its first line (with query hits), age and project folder. */
92
+ function historyPickerItem(entry: HistorySearchEntry, tokens: readonly string[]): TspPickerItem {
93
+ const label = entry.prompt.trim().split("\n", 1)[0]!.replace(/\s+/g, " ").trim();
94
+ const hits = pickerHits(label, tokens);
95
+ return {
96
+ id: nativeEntryKey(entry),
97
+ label,
98
+ ...(entry.cwd ? { detail: shortenPath(entry.cwd) } : {}),
99
+ facts: { when: pickerAge(entry.created_at * 1000) },
100
+ ...(hits.length > 0 ? { hits } : {}),
101
+ title: pickerDate(entry.created_at * 1000),
102
+ };
90
103
  }
91
104
 
92
105
  class HistoryResultsList implements Component {
@@ -133,7 +146,7 @@ class HistoryResultsList implements Component {
133
146
  if (!entry) continue;
134
147
  const isSelected = i === this.#menu.selectedIndex;
135
148
 
136
- const timeStr = relativeTime(entry.created_at);
149
+ const timeStr = pickerAge(entry.created_at * 1000);
137
150
  const timeWidth = visibleWidth(timeStr);
138
151
  const showTime = rowWidth >= gutterWidth + 12 + timeWidth;
139
152
 
@@ -170,9 +183,12 @@ export class HistorySearchComponent extends OverlayPanel {
170
183
  #onSelect: (prompt: string) => void;
171
184
  #onCancel: () => void;
172
185
  #resultLimit = 100;
186
+ #nativeHints: NativeNode | undefined;
187
+ #nativeMemo: HistoryNativeMemo | undefined;
188
+ #pickerItems: { items: readonly HistorySearchEntry[]; rows: readonly TspPickerItem[] } | undefined;
173
189
 
174
190
  constructor(historyStorage: HistorySource, onSelect: (prompt: string) => void, onCancel: () => void) {
175
- super("History");
191
+ super("History", "omp.overlay.history");
176
192
  this.#historyStorage = historyStorage;
177
193
  this.#onSelect = onSelect;
178
194
  this.#onCancel = onCancel;
@@ -257,6 +273,132 @@ export class HistorySearchComponent extends OverlayPanel {
257
273
  this.#updateResults();
258
274
  }
259
275
 
276
+ /**
277
+ * With the `picker` kind: a `size:"md"` sheet of past prompts (first line
278
+ * with hits, age, folder) as a keyed child the reconciler hoists into
279
+ * `layer`, since the selector replaces the editor in the dock. Otherwise
280
+ * the query field (the `Input`, which describes itself as `input`), the
281
+ * results as a `list` keyed by entry (filter = query, age as the item
282
+ * value), and the key hints.
283
+ */
284
+ override describe(cx: DescribeContext): NativeNode {
285
+ const items = this.#menu.visibleItems;
286
+ const selected = this.#menu.selectedItem;
287
+ const usePicker = cx.supports("picker");
288
+ const query = usePicker ? this.#searchInput.getValue() : this.#searchInput.getValue().trim();
289
+ const cursor = this.#searchInput.getCursor();
290
+ const memo = this.#nativeMemo;
291
+ if (
292
+ memo &&
293
+ memo.picker === usePicker &&
294
+ memo.items === items &&
295
+ memo.selected === selected &&
296
+ memo.query === query &&
297
+ memo.cursor === cursor
298
+ ) {
299
+ return memo.node;
300
+ }
301
+ if (usePicker) {
302
+ const root = col([keyed(this.#describePicker(items, selected, query), PICKER_KEY)]);
303
+ this.#nativeMemo = { picker: true, items, selected, query, cursor, node: root };
304
+ return root;
305
+ }
306
+
307
+ const rows = items.map(entry =>
308
+ node(
309
+ "item",
310
+ {
311
+ label: entry.prompt.replace(/\s+/g, " ").trim(),
312
+ value: [span(pickerAge(entry.created_at * 1000), "dim")],
313
+ },
314
+ undefined,
315
+ nativeEntryKey(entry),
316
+ ),
317
+ );
318
+ const list = node(
319
+ "list",
320
+ {
321
+ selected: selected ? nativeEntryKey(selected) : null,
322
+ filter: query || undefined,
323
+ empty: query ? "No matching history" : "No history yet",
324
+ max: { lines: MAX_VISIBLE },
325
+ virtual: true,
326
+ },
327
+ rows,
328
+ "list",
329
+ );
330
+ this.#nativeHints ??= hintsRow([
331
+ actionHint(["tui.select.up", "tui.select.down"], "navigate"),
332
+ { keys: ENTER_KEYS, label: "select" },
333
+ actionHint("tui.select.cancel", "cancel"),
334
+ ]);
335
+ const root = overlayCard(this.nativeRole, this.title, [this.#searchInput, list, this.#nativeHints]);
336
+ this.#nativeMemo = { picker: false, items, selected, query, cursor, node: root };
337
+ return root;
338
+ }
339
+
340
+ #describePicker(
341
+ items: readonly HistorySearchEntry[],
342
+ selected: HistorySearchEntry | undefined,
343
+ query: string,
344
+ ): NativeNode {
345
+ let rows = this.#pickerItems;
346
+ if (rows?.items !== items) {
347
+ const tokens = queryTokens(query.trim());
348
+ rows = { items, rows: items.map(entry => historyPickerItem(entry, tokens)) };
349
+ this.#pickerItems = rows;
350
+ }
351
+ return picker({
352
+ title: this.title,
353
+ icon: "history",
354
+ noun: "prompts",
355
+ size: "md",
356
+ layout: "rows",
357
+ preview: "none",
358
+ ...pickerQuery(this.#searchInput),
359
+ placeholder: "Search prompts…",
360
+ columns: [{ id: "when", format: "time" }],
361
+ items: rows.rows,
362
+ selected: selected ? nativeEntryKey(selected) : null,
363
+ empty: "No history yet",
364
+ actions: [
365
+ pickerAction("insert", "Insert", "enter", { primary: true }),
366
+ pickerAction("close", "Close", boundKeys("app.interrupt", ["escape"])[0] ?? "escape", { end: true }),
367
+ ],
368
+ });
369
+ }
370
+
371
+ /**
372
+ * Picker: a row click highlights, a second click or `Insert` inserts it
373
+ * (Enter), `Close` cancels (Esc), `Clear search` empties the query.
374
+ * List: picking a result does what highlighting it and pressing Enter does.
375
+ */
376
+ handleNativeEvent(event: NativeUiEvent): void {
377
+ const ev = pickerEvent(event, PICKER_KEY);
378
+ if (ev) {
379
+ if (ev.kind === "action") {
380
+ if (ev.act === "close") this.#onCancel();
381
+ else if (ev.act === "insert") this.handleInput("\r");
382
+ else if (ev.act === "clear") {
383
+ this.#searchInput.setValue("");
384
+ this.#updateResults();
385
+ }
386
+ return;
387
+ }
388
+ const index = this.#menu.visibleItems.findIndex(entry => nativeEntryKey(entry) === ev.item);
389
+ if (index < 0) return;
390
+ this.#menu.setSelectedIndex(index);
391
+ if (ev.kind === "activate") this.#onSelect(this.#menu.visibleItems[index]!.prompt);
392
+ return;
393
+ }
394
+ if ((event.type !== "select" && event.type !== "activate") || event.key !== "list") return;
395
+ const index = this.#menu.visibleItems.findIndex(entry => nativeEntryKey(entry) === event.item);
396
+ const target = this.#menu.visibleItems[index];
397
+ if (!target) return;
398
+ this.#menu.setSelectedIndex(index);
399
+ this.#onSelect(target.prompt);
400
+ }
401
+
260
402
  #updateResults(): void {
261
403
  const query = this.#searchInput.getValue().trim();
262
404
  const results = query
@@ -7,6 +7,9 @@
7
7
  * (Ctrl+Q / Ctrl+Enter) submits, bordered popup
8
8
  * - Prompt-style (ask): Enter submits, Shift+Enter inserts newline, legacy ask chrome
9
9
  */
10
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
11
+ import { compactImageMarkers, formatVisionMarker, PLACEHOLDER_REGEX } from "../prompt/composer-attachments";
12
+ import { extractImagePastePathsFromText } from "../prompt/custom-editor";
10
13
  import { Editor, type Focusable, matchesKey, Spacer, Text, type TUI } from "../index";
11
14
  import { BracketedPasteHandler } from "../bracketed-paste";
12
15
  import { getEditorTheme, theme } from "../theme/theme";
@@ -16,12 +19,32 @@ import { OverlayPanel } from "../chrome/overlay-box";
16
19
  import { FormField } from "../components/form";
17
20
  import { formatKeyHint, formatKeyHints } from "../app-keybindings";
18
21
  import { boundKeys, editorKey, interruptKey } from "../chrome/keybinding-hints";
22
+ import { node, span } from "../native/describe";
23
+ import type { DescribeContext, NativeChild, NativeNode } from "../native/node";
24
+ import { hintsRow, type NativeHint, overlayCard } from "../native/overlay";
25
+ import { plainText } from "../native/spans";
19
26
 
20
27
  export interface HookEditorOptions {
21
28
  /** Edit text with the host's configured external editor. */
22
29
  externalEditor?: (text: string) => Promise<string | null>;
23
30
  /** When true, use prompt-style keybindings with the legacy ask prompt chrome. */
24
31
  promptStyle?: boolean;
32
+ /** Allow clipboard images to be attached to this prompt. */
33
+ acceptImages?: boolean;
34
+ /** Images already represented by markers in the prefilled text. */
35
+ images?: readonly ImageContent[];
36
+ /**
37
+ * With `acceptImages`, called for an empty bracketed paste, which is how terminals that own
38
+ * the paste key send an image-only clipboard (#3601). Same signature as
39
+ * `CustomEditor.onPasteImage`; the host calls `beginPaste` before its first await.
40
+ */
41
+ onPasteImage?: () => Promise<boolean>;
42
+ /**
43
+ * With `acceptImages`, called for each path of a bracketed paste made only of image paths.
44
+ * Same signature as `CustomEditor.onPasteImagePath`; calls are not awaited in turn, so images
45
+ * attach as they finish loading and are renumbered in text order on submit.
46
+ */
47
+ onPasteImagePath?: (path: string) => void | Promise<void>;
25
48
  /**
26
49
  * Max rows the inner Editor may occupy. When omitted, the editor is
27
50
  * bounded to the current terminal height minus the component's chrome
@@ -35,15 +58,21 @@ export interface HookEditorOptions {
35
58
  export class HookEditorComponent extends OverlayPanel implements Focusable {
36
59
  #editor: Editor;
37
60
  #field: FormField;
38
- #onSubmitCallback: (value: string) => void;
61
+ #onSubmitCallback: (value: string, images?: ImageContent[]) => void;
39
62
  #onCancelCallback: () => void;
40
63
  #tui: TUI;
41
64
  #promptStyle: boolean;
65
+ #acceptImages: boolean;
66
+ #images: ImageContent[];
42
67
  #externalEditor: HookEditorOptions["externalEditor"];
68
+ #onPasteImage: HookEditorOptions["onPasteImage"];
69
+ #onPasteImagePath: HookEditorOptions["onPasteImagePath"];
43
70
  #pasteHandler = new BracketedPasteHandler();
44
71
  #pendingPastes: { settled: boolean; text: string | undefined }[] = [];
45
72
  #submitQueued = false;
46
73
  #disposed = false;
74
+ /** Detail lines, hints and the editor under the title; every part is fixed, so it is built once. */
75
+ #nativeRoot: NativeNode;
47
76
  /** Focus state mirrored to the nested editor during rendering. */
48
77
  focused = false;
49
78
 
@@ -51,7 +80,7 @@ export class HookEditorComponent extends OverlayPanel implements Focusable {
51
80
  tui: TUI,
52
81
  title: string,
53
82
  prefill: string | undefined,
54
- onSubmit: (value: string) => void,
83
+ onSubmit: (value: string, images?: ImageContent[]) => void,
55
84
  onCancel: () => void,
56
85
  options?: HookEditorOptions,
57
86
  ) {
@@ -59,13 +88,17 @@ export class HookEditorComponent extends OverlayPanel implements Focusable {
59
88
  // bounded ask question under "◆ Other (type your own)") stay as body rows
60
89
  // so they are never truncated into the one-row border.
61
90
  const [titleLine = "", ...detailLines] = title.split("\n");
62
- super(titleLine);
91
+ super(titleLine, "omp.overlay.hook-editor");
63
92
 
64
93
  this.#tui = tui;
65
94
  this.#onSubmitCallback = onSubmit;
66
95
  this.#onCancelCallback = onCancel;
67
96
  this.#promptStyle = options?.promptStyle ?? false;
97
+ this.#acceptImages = options?.acceptImages ?? false;
98
+ this.#images = this.#acceptImages ? [...(options?.images ?? [])] : [];
68
99
  this.#externalEditor = options?.externalEditor;
100
+ this.#onPasteImage = options?.onPasteImage;
101
+ this.#onPasteImagePath = options?.onPasteImagePath;
69
102
 
70
103
  // Editor
71
104
  this.#editor = new Editor(getEditorTheme());
@@ -74,6 +107,8 @@ export class HookEditorComponent extends OverlayPanel implements Focusable {
74
107
  this.#editor.setPromptGutter("> ");
75
108
  this.#editor.disableSubmit = true;
76
109
  }
110
+ // Image markers delete as a unit, like the main editor's attachment tokens.
111
+ if (options?.acceptImages) this.#editor.atomicTokenPattern = PLACEHOLDER_REGEX;
77
112
  // Bound the editor so long content scrolls instead of pushing the
78
113
  // submit hint off-screen. Caller may override via options.maxHeight.
79
114
  const termRows = this.#tui.terminal?.rows ?? process.stdout.rows ?? 40;
@@ -99,6 +134,41 @@ export class HookEditorComponent extends OverlayPanel implements Focusable {
99
134
  });
100
135
  this.addChild(this.#field);
101
136
  this.addChild(new Spacer(1));
137
+
138
+ const externalHint: NativeHint = {
139
+ keys: boundKeys("app.editor.external", ["ctrl+g"]).slice(0, 1),
140
+ label: "external editor",
141
+ };
142
+ const nativeHints: NativeHint[] = this.#promptStyle
143
+ ? [
144
+ { keys: ["enter", primaryFollowUpKey], label: "submit" },
145
+ { keys: ["escape"], label: "cancel" },
146
+ externalHint,
147
+ ]
148
+ : [
149
+ { keys: followUpKeys, label: "submit" },
150
+ { keys: boundKeys("app.interrupt", ["escape"]).slice(0, 1), label: "cancel" },
151
+ externalHint,
152
+ ];
153
+ const nativeChildren: NativeChild[] = [];
154
+ if (detailLines.length > 0) {
155
+ nativeChildren.push(
156
+ node(
157
+ "text",
158
+ { spans: [span(plainText(detailLines.join("\n")), "accent")], wrap: "word" },
159
+ undefined,
160
+ "detail",
161
+ ),
162
+ );
163
+ }
164
+ nativeChildren.push(this.#editor, hintsRow(nativeHints));
165
+ this.#nativeRoot = overlayCard(this.nativeRole, plainText(titleLine), nativeChildren);
166
+ }
167
+
168
+ /** The editor (which describes itself) under the title and detail lines, with the key hints below. */
169
+ override describe(_cx: DescribeContext): NativeNode {
170
+ this.#field.focused = this.focused;
171
+ return this.#nativeRoot;
102
172
  }
103
173
 
104
174
  /** Keep the nested editor's software/hardware cursor mode aligned with the dialog focus target. */
@@ -117,7 +187,20 @@ export class HookEditorComponent extends OverlayPanel implements Focusable {
117
187
  const paste = this.#pasteHandler.process(keyData);
118
188
  if (paste.handled) {
119
189
  if (paste.pasteContent === undefined) return;
120
- this.pasteText(paste.pasteContent);
190
+ // Hosts reserve ordered delivery synchronously inside these callbacks, so input
191
+ // that followed the paste in this chunk (e.g. Enter) still waits for the image.
192
+ const content = paste.pasteContent;
193
+ const acceptsImages = this.acceptsImages;
194
+ if (acceptsImages && content.length === 0 && this.#onPasteImage) {
195
+ void this.#onPasteImage();
196
+ } else {
197
+ const imagePaths = acceptsImages ? extractImagePastePathsFromText(content) : undefined;
198
+ if (imagePaths && this.#onPasteImagePath) {
199
+ for (const path of imagePaths) void this.#onPasteImagePath(path);
200
+ } else {
201
+ this.pasteText(content);
202
+ }
203
+ }
121
204
  if (paste.remaining.length > 0) this.handleInput(paste.remaining);
122
205
  return;
123
206
  }
@@ -134,10 +217,34 @@ export class HookEditorComponent extends OverlayPanel implements Focusable {
134
217
  this.#submitQueued = true;
135
218
  return;
136
219
  }
137
- const text = this.#editor.getExpandedText();
220
+ let text = this.#editor.getExpandedText();
138
221
  if (requireText && text.trim().length === 0) return;
222
+ let images: ImageContent[] | undefined;
223
+ if (this.#images.length > 0) {
224
+ // Path pastes attach as they finish loading; submit numbers images in text order.
225
+ const compacted = compactImageMarkers(text, this.#images.length, { byAppearance: true });
226
+ if (compacted) {
227
+ text = compacted.text;
228
+ images = compacted.keep.map(index => this.#images[index]);
229
+ } else {
230
+ images = this.#images;
231
+ }
232
+ }
139
233
  this.dispose();
140
- this.#onSubmitCallback(text);
234
+ if (images?.length) this.#onSubmitCallback(text, images);
235
+ else this.#onSubmitCallback(text);
236
+ }
237
+
238
+ /** Whether this prompt opted into clipboard image attachments. */
239
+ get acceptsImages(): boolean {
240
+ return this.#acceptImages && !this.#disposed;
241
+ }
242
+
243
+ /** Attach an image; returns its `[Image #N, WxH]` marker for the caller to deliver. */
244
+ attachImage(image: ImageContent, dims?: { width: number; height: number }): string | undefined {
245
+ if (!this.acceptsImages) return undefined;
246
+ this.#images.push(image);
247
+ return formatVisionMarker("image", this.#images.length, dims);
141
248
  }
142
249
 
143
250
  /** Reserve ordered clipboard delivery. Completion accepts nonempty text once, or releases on undefined. */
@@ -8,6 +8,10 @@ import { formTheme } from "../chrome/form-theme";
8
8
  import { OverlayPanel } from "../chrome/overlay-box";
9
9
  import { Form, TextFormField } from "../components/form";
10
10
  import { editorKey, interruptKey } from "../chrome/keybinding-hints";
11
+ import { node } from "../native/describe";
12
+ import type { DescribeContext, NativeNode } from "../native/node";
13
+ import { overlayCard } from "../native/overlay";
14
+ import { plainText } from "../native/spans";
11
15
 
12
16
  export interface HookInputOptions {
13
17
  tui?: TUI;
@@ -22,6 +26,7 @@ export class HookInputComponent extends OverlayPanel {
22
26
  #onCancelCallback: () => void;
23
27
  #baseTitle: string;
24
28
  #countdown: CountdownTimer | undefined;
29
+ #nativeMemo: { countdown: NativeNode | undefined; node: NativeNode } | undefined;
25
30
 
26
31
  constructor(
27
32
  title: string,
@@ -30,7 +35,7 @@ export class HookInputComponent extends OverlayPanel {
30
35
  onCancel: () => void,
31
36
  opts?: HookInputOptions,
32
37
  ) {
33
- super(title);
38
+ super(title, "omp.overlay.hook-input");
34
39
 
35
40
  this.#onSubmitCallback = onSubmit;
36
41
  this.#onCancelCallback = onCancel;
@@ -82,4 +87,20 @@ export class HookInputComponent extends OverlayPanel {
82
87
  this.#countdown?.dispose();
83
88
  super.dispose();
84
89
  }
90
+
91
+ /** A card over the form, headed by the title plus the countdown `elapsed` while a timeout runs. */
92
+ override describe(_cx: DescribeContext): NativeNode {
93
+ const countdown = this.#countdown?.describe();
94
+ const memo = this.#nativeMemo;
95
+ if (memo && memo.countdown === countdown) return memo.node;
96
+ const title = plainText(this.#baseTitle);
97
+ const root = countdown
98
+ ? overlayCard(this.nativeRole, undefined, [
99
+ node("row", { gap: "sm", align: "baseline" }, [node("text", { text: title }), countdown], "head"),
100
+ this.#form,
101
+ ])
102
+ : overlayCard(this.nativeRole, title, [this.#form]);
103
+ this.#nativeMemo = { countdown, node: root };
104
+ return root;
105
+ }
85
106
  }