@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
@@ -9,7 +9,13 @@ import { addKeyAliases, canonicalKeyId } from "../keybindings";
9
9
  import { type KeyId, parseKey } from "../keys";
10
10
  import type { Component, TUI } from "../tui";
11
11
  import { type ThemeColor, theme } from "../theme/theme";
12
- import { buildSessionModelScope, ModelBrowser, type ModelBrowserItem } from "./model-browser";
12
+ import {
13
+ buildSessionModelScope,
14
+ MODEL_PICKER_COLUMNS,
15
+ ModelBrowser,
16
+ type ModelBrowserItem,
17
+ type ModelPickerCatalogue,
18
+ } from "./model-browser";
13
19
  import type { ModelBrowserRegistry, ModelBrowserSource } from "./model-browser";
14
20
  import type { ConfiguredThinkingLevel } from "../thinking";
15
21
  import type { ScopedModelItem } from "./model-hub";
@@ -17,6 +23,11 @@ import { bottomBorder, row, topBorder } from "../chrome/overlay-box";
17
23
  import { resolveSegmentPalette } from "../chrome/segment-track";
18
24
  import { formatKeyHint } from "../app-keybindings";
19
25
  import { editorKey, editorKeys } from "../chrome/keybinding-hints";
26
+ import { card, compact, span, text } from "../native/describe";
27
+ import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
28
+ import { CLOSE_ACTION, picker, pickerAction, pickerEvent } from "../native/picker";
29
+ import type { TspPickerProps } from "@oh-my-pi/pi-wire";
30
+ import { actionHint, hintsRow, type NativeHint } from "../native/overlay";
20
31
 
21
32
  /** Configured role resolved to a concrete model. */
22
33
  export interface ResolvedRoleModel {
@@ -28,7 +39,8 @@ export interface ResolvedRoleModel {
28
39
 
29
40
  /** Catalog refresh capability used by the session picker. */
30
41
  export interface ModelPickerRegistry extends ModelBrowserRegistry {
31
- refresh(strategy: "offline"): Promise<void>;
42
+ /** Bring the catalog up to date without rebuilding a current one; `true` when the picker must re-read it. */
43
+ refreshIfStale(): Promise<boolean>;
32
44
  }
33
45
 
34
46
  export interface ModelPickerCallbacks {
@@ -116,6 +128,16 @@ export class ModelPickerComponent implements Component {
116
128
  #taskMatchKeys = new Set<string>();
117
129
  #taskModeKey: KeyId | undefined;
118
130
  #taskSelector: string | undefined;
131
+ #nativeRoot: { memo: string; node: NativeNode } | undefined;
132
+ #pickerRoot:
133
+ | {
134
+ memo: string;
135
+ catalogue: ModelPickerCatalogue;
136
+ order: object;
137
+ preview: readonly NativeChild[];
138
+ node: NativeNode;
139
+ }
140
+ | undefined;
119
141
 
120
142
  constructor(
121
143
  tui: TUI,
@@ -172,13 +194,15 @@ export class ModelPickerComponent implements Component {
172
194
  this.#browser.selectSelector(options.currentSelector);
173
195
  }
174
196
 
175
- // Reconcile with cached discovery state in the background. A --models
176
- // scope is registry-independent, so the offline reload would only repeat
177
- // the synchronous hydration above.
197
+ // Re-read only if the catalog moves (startup discovery landing, a
198
+ // models.yml edit): rebuilding a current catalog on every open blocks
199
+ // the first paint for seconds. A --models scope is registry-independent.
178
200
  if (this.#scopedModels.length === 0) {
179
201
  this.#registry
180
- .refresh("offline")
181
- .then(() => this.#syncFromRegistryState())
202
+ .refreshIfStale()
203
+ .then(changed => {
204
+ if (changed) this.#syncFromRegistryState();
205
+ })
182
206
  .catch(error => {
183
207
  this.#configError = error instanceof Error ? error.message : String(error);
184
208
  })
@@ -297,4 +321,157 @@ export class ModelPickerComponent implements Component {
297
321
  out.push(bottomBorder(width, borderColor));
298
322
  return out;
299
323
  }
324
+
325
+ /** A `picker` is its own sheet: the backend mounts it in `layer` without an `overlay` wrapper. */
326
+ nativeSheet(cx: DescribeContext): boolean {
327
+ return cx.supports("picker");
328
+ }
329
+
330
+ /** Picker pointer events, each on the path of the key it stands for. */
331
+ handleNativeEvent(event: NativeUiEvent): void {
332
+ const picked = pickerEvent(event);
333
+ if (!picked) return;
334
+ if (picked.kind !== "action") {
335
+ this.#browser.routePickerItem(picked.item, picked.kind === "activate");
336
+ return;
337
+ }
338
+ switch (picked.act) {
339
+ case CLOSE_ACTION.id:
340
+ this.#browser.handleCancel();
341
+ return;
342
+ case "clear":
343
+ if (this.#browser.query.length > 0) this.#browser.handleCancel();
344
+ return;
345
+ case "use": {
346
+ const selected = this.#browser.pickerSelected;
347
+ if (selected) this.#browser.routePickerItem(selected, true);
348
+ return;
349
+ }
350
+ case "task":
351
+ if (this.#taskModeKey !== undefined && !this.#roleMode) this.#toggleTaskMode();
352
+ return;
353
+ }
354
+ }
355
+
356
+ /** The `md` picker sheet, or the fallback card when the terminal lacks the kind. */
357
+ describe(cx?: DescribeContext): NativeNode {
358
+ return cx?.supports("picker") ? this.#describePicker() : this.#describeCard();
359
+ }
360
+
361
+ #describePicker(): NativeNode {
362
+ const mode = this.#taskMode ? "task" : this.#roleMode ? "role" : "session";
363
+ const query = this.#browser.query;
364
+ const view = this.#browser.pickerOrder({
365
+ providers: false,
366
+ rest: mode === "role" ? null : query.trim() ? "Matches" : "All",
367
+ });
368
+ const catalogue = this.#browser.pickerItems(this.#roleMode ? this.#quickRoleItems : this.#modelItems);
369
+ const current =
370
+ mode === "role"
371
+ ? this.#currentQuickRoleSelector
372
+ : mode === "task"
373
+ ? this.#taskSelector
374
+ : this.#currentSelector;
375
+ const preview = this.#browser.pickerPreview("compact", current);
376
+ const selected = this.#browser.pickerSelected;
377
+ const cursor = this.#browser.cursor;
378
+ const memo = `${mode}\0${this.#configError ?? ""}\0${query}\0${cursor}\0${selected ?? ""}`;
379
+ const cached = this.#pickerRoot;
380
+ if (
381
+ cached !== undefined &&
382
+ cached.memo === memo &&
383
+ cached.catalogue === catalogue &&
384
+ cached.order === view &&
385
+ cached.preview === preview
386
+ ) {
387
+ return cached.node;
388
+ }
389
+ const subtitle: TspPickerProps["subtitle"] = this.#configError
390
+ ? [span(this.#configError, "error")]
391
+ : mode === "task"
392
+ ? [span(TASK_STATUS_HINT, "warning")]
393
+ : mode === "role"
394
+ ? QUICK_ROLE_STATUS_HINT
395
+ : STATUS_HINT;
396
+ const cycleKey = editorKey("app.model.cycleForward") || formatKeyHint("ctrl+p");
397
+ const node = picker(
398
+ {
399
+ title: mode === "task" ? "Switch task model" : "Switch model",
400
+ subtitle,
401
+ icon: "cpu",
402
+ noun: mode === "role" ? "roles" : "models",
403
+ size: "md",
404
+ layout: "rows",
405
+ preview: "below",
406
+ query,
407
+ cursor,
408
+ placeholder: mode === "task" ? "Search models…" : "Search models, @ for quick roles…",
409
+ columns: MODEL_PICKER_COLUMNS,
410
+ ...catalogue,
411
+ order: view.order,
412
+ ...(view.hits ? { hits: view.hits } : {}),
413
+ selected,
414
+ ...(current ? { current: [current] } : {}),
415
+ total: this.#browser.baseCount,
416
+ empty: mode === "role" ? `No quick roles in the ${cycleKey} cycle` : "No models available",
417
+ actions: compact([
418
+ pickerAction(
419
+ "use",
420
+ mode === "role" ? "Apply role" : mode === "task" ? "Use for Task subagents" : "Use for session",
421
+ "enter",
422
+ { primary: true },
423
+ ),
424
+ this.#taskModeKey !== undefined && mode !== "role"
425
+ ? pickerAction("task", "Task model", this.#taskModeKey, { on: mode === "task" })
426
+ : undefined,
427
+ query.length > 0 ? { ...CLOSE_ACTION, label: "Clear search" } : CLOSE_ACTION,
428
+ ]),
429
+ },
430
+ preview,
431
+ );
432
+ this.#pickerRoot = { memo, catalogue, order: view, preview, node };
433
+ return node;
434
+ }
435
+
436
+ /** Root card over the status line, the embedded browser, and the mode's key hints. */
437
+ #describeCard(): NativeNode {
438
+ const mode = this.#taskMode ? "task" : this.#roleMode ? "role" : "session";
439
+ const memo = `${mode}\0${this.#configError ?? ""}\0${this.#taskModeKey ?? ""}`;
440
+ if (this.#nativeRoot?.memo === memo) return this.#nativeRoot.node;
441
+
442
+ const status = this.#configError
443
+ ? span(this.#configError, "error")
444
+ : this.#taskMode
445
+ ? span(TASK_STATUS_HINT, "error")
446
+ : span(this.#roleMode ? QUICK_ROLE_STATUS_HINT : STATUS_HINT, "muted");
447
+ const hints: (NativeHint | undefined)[] = [
448
+ actionHint(["tui.select.up", "tui.select.down"], mode === "role" ? "roles" : "models"),
449
+ {
450
+ keys: ["enter"],
451
+ label:
452
+ mode === "role"
453
+ ? "apply role model"
454
+ : mode === "task"
455
+ ? "use for Task subagents"
456
+ : "use for this session",
457
+ },
458
+ { keys: [], label: "type to search" },
459
+ ];
460
+ if (mode === "session") hints.push({ keys: ["@"], label: "quick roles" });
461
+ hints.push(actionHint("tui.select.cancel", "close"));
462
+ if (this.#taskModeKey !== undefined && !this.#roleMode) {
463
+ hints.push({ keys: [this.#taskModeKey], label: this.#taskMode ? "session model" : "task model" });
464
+ }
465
+ // `card` directly: the task-mode tone is a common prop `overlayCard` doesn't take.
466
+ const node = card(
467
+ {
468
+ role: "omp.overlay.model-picker",
469
+ head: this.#taskMode ? "Switch Task Model" : "Switch Model",
470
+ tone: this.#taskMode ? "error" : undefined,
471
+ },
472
+ [text([status], { wrap: "word" }), this.#browser, hintsRow(hints)],
473
+ );
474
+ this.#nativeRoot = { memo, node };
475
+ return node;
476
+ }
300
477
  }
@@ -13,6 +13,9 @@ import { bottomBorder, row, topBorder } from "../chrome/overlay-box";
13
13
  import { TextFormField } from "../components/form";
14
14
  import { formatKeyHint } from "../app-keybindings";
15
15
  import { editorKey, editorKeys } from "../chrome/keybinding-hints";
16
+ import type { NativeNode, NativeUiEvent } from "../native/node";
17
+ import { node, span } from "../native/describe";
18
+ import { actionHint, overlayCard, statusHintsRow } from "../native/overlay";
16
19
 
17
20
  export interface MoveOverlayResult {
18
21
  directory: string;
@@ -51,6 +54,7 @@ export class MoveOverlay implements Component, Focusable {
51
54
  #renderMemo:
52
55
  | { width: number; fieldLines: readonly string[]; revision: number; lines: readonly string[] }
53
56
  | undefined;
57
+ #native: { revision: number; node: NativeNode } | undefined;
54
58
 
55
59
  constructor(cwd: string, done: (result: MoveOverlayResult | undefined) => void, source: MoveDirectorySource) {
56
60
  this.#cwd = cwd;
@@ -171,6 +175,47 @@ export class MoveOverlay implements Component, Focusable {
171
175
  return lines;
172
176
  }
173
177
 
178
+ describe(): NativeNode {
179
+ const memo = this.#native;
180
+ if (memo?.revision === this.#revision) return memo.node;
181
+ const items = this.#results
182
+ .slice(0, MAX_RESULTS)
183
+ .map(item => node("item", { label: item.label, detail: [span(item.value, "path")] }, undefined, item.value));
184
+ const list = node(
185
+ "list",
186
+ {
187
+ selected: this.#results[this.#selectedIndex]?.value ?? null,
188
+ empty: this.#field.getValue().length > 0 ? [span("No matching directories", "dim")] : undefined,
189
+ },
190
+ items,
191
+ "results",
192
+ );
193
+ const hints = statusHintsRow(
194
+ [span("Type to filter", "dim")],
195
+ [
196
+ actionHint(["tui.select.up", "tui.select.down"], "navigate"),
197
+ { keys: ["tab"], label: "accept" },
198
+ { keys: ["enter"], label: "confirm" },
199
+ actionHint("tui.select.cancel", "cancel"),
200
+ ],
201
+ );
202
+ const described = overlayCard("omp.dialog.move", "Move to directory", [this.#field, list, hints]);
203
+ this.#native = { revision: this.#revision, node: described };
204
+ return described;
205
+ }
206
+
207
+ handleNativeEvent(event: NativeUiEvent): void {
208
+ if (event.type !== "select" && event.type !== "activate") return;
209
+ const index = this.#results.findIndex(result => result.value === event.item);
210
+ if (index === -1) return;
211
+ if (index !== this.#selectedIndex) {
212
+ this.#selectedIndex = index;
213
+ this.#revision++;
214
+ }
215
+ // Enter submits the field, which confirms the highlighted suggestion.
216
+ if (event.type === "activate") this.#field.submit();
217
+ }
218
+
174
219
  invalidate(): void {
175
220
  this.#field.input.prompt = theme.fg("dim", "Path: ");
176
221
  this.#field.invalidate();
@@ -9,12 +9,20 @@ import {
9
9
  type SgrMouseEvent,
10
10
  Spacer,
11
11
  TruncatedText,
12
+ visibleWidth,
12
13
  } from "../index";
13
14
  import { theme } from "../theme/theme";
14
15
  import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
15
16
  import { OverlayPanel } from "../chrome/overlay-box";
17
+ import { Input } from "../components/input";
16
18
  import { MenuSelection } from "../components/menu-selection";
17
19
  import { centeredViewportRange } from "../components/scroll-viewport";
20
+ import type { TspPickerItem, TspSpan, TspTone } from "@oh-my-pi/pi-wire";
21
+ import { node, span, text } from "../native/describe";
22
+ import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node";
23
+ import { CLOSE_ACTION, dockedPicker, PICKER_KEY, pickerAction, pickerEvent, pickerQuery } from "../native/picker";
24
+ import { overlayCard } from "../native/overlay";
25
+ import { isNativeRendering } from "../native/state";
18
26
 
19
27
  const OAUTH_SELECTOR_MAX_VISIBLE = 10;
20
28
 
@@ -44,6 +52,8 @@ const ORIGIN_LABELS = {
44
52
  export class OAuthSelectorComponent extends OverlayPanel {
45
53
  #listContainer: Container;
46
54
  #menu: MenuSelection<OAuthProviderInfo>;
55
+ /** The provider search field; its value drives `#menu`'s query. */
56
+ #search = Object.assign(new Input(), { prompt: "" });
47
57
  #hoveredIndex: number | null = null;
48
58
  /** First provider index of the visible ScrollView window (last #updateList). */
49
59
  #scrollStart = 0;
@@ -61,6 +71,13 @@ export class OAuthSelectorComponent extends OverlayPanel {
61
71
  #spinnerFrame: number = 0;
62
72
  #spinnerInterval?: NodeJS.Timeout;
63
73
  #validationGeneration: number = 0;
74
+ #nativeItems: readonly NativeNode[] | undefined;
75
+ /** The `visibleItems` array {@link #nativeItems} was built from. */
76
+ #nativeItemsSource: readonly OAuthProviderInfo[] | undefined;
77
+ #nativeRoot: NativeNode | undefined;
78
+ /** Picker rows for the whole catalogue; dropped when an auth state changes. */
79
+ #pickerItems: readonly TspPickerItem[] | undefined;
80
+ #pickerRoot: NativeNode | undefined;
64
81
  constructor(
65
82
  mode: "login" | "logout",
66
83
  authStorage: OAuthSelectorAuthSource,
@@ -72,7 +89,7 @@ export class OAuthSelectorComponent extends OverlayPanel {
72
89
  requestRender?: () => void;
73
90
  },
74
91
  ) {
75
- super(mode === "login" ? "Select provider to login" : "Select provider to logout");
92
+ super(mode === "login" ? "Select provider to login" : "Select provider to logout", "omp.overlay.oauth");
76
93
  this.#mode = mode;
77
94
  this.#authStorage = authStorage;
78
95
  this.#onSelectCallback = onSelect;
@@ -155,6 +172,8 @@ export class OAuthSelectorComponent extends OverlayPanel {
155
172
  continue;
156
173
  }
157
174
  this.#authState.set(provider.id, "checking");
175
+ this.#nativeItems = undefined;
176
+ this.#pickerItems = undefined;
158
177
  pending += 1;
159
178
  void this.#validateProvider(provider.id, generation);
160
179
  }
@@ -177,6 +196,8 @@ export class OAuthSelectorComponent extends OverlayPanel {
177
196
 
178
197
  if (generation !== this.#validationGeneration) return;
179
198
  this.#authState.set(providerId, isValid ? "valid" : "invalid");
199
+ this.#nativeItems = undefined;
200
+ this.#pickerItems = undefined;
180
201
  if (![...this.#authState.values()].includes("checking")) {
181
202
  this.#stopSpinner();
182
203
  }
@@ -185,7 +206,8 @@ export class OAuthSelectorComponent extends OverlayPanel {
185
206
  }
186
207
 
187
208
  #startSpinner(): void {
188
- if (this.#spinnerInterval) return;
209
+ // Natively the "checking" status pulses on the terminal's clock.
210
+ if (this.#spinnerInterval || isNativeRendering()) return;
189
211
  this.#spinnerInterval = setInterval(() => {
190
212
  const frameCount = theme.spinnerFrames.length;
191
213
  if (frameCount > 0) {
@@ -243,8 +265,9 @@ export class OAuthSelectorComponent extends OverlayPanel {
243
265
 
244
266
  #renderStatusLine(_total: number): string {
245
267
  const query = this.#menu.query.trim();
246
- const suffix = query ? `Search: ${this.#menu.query}` : "Type to search";
247
- return theme.fg("muted", suffix);
268
+ if (!query) return theme.fg("muted", "Type to search");
269
+ const width = visibleWidth(this.#search.getValue()) + 1;
270
+ return theme.fg("muted", "Search: ") + (this.#search.render(width)[0] ?? "");
248
271
  }
249
272
 
250
273
  #getProviderSearchText(provider: OAuthProviderInfo): string {
@@ -260,32 +283,32 @@ export class OAuthSelectorComponent extends OverlayPanel {
260
283
  return text;
261
284
  }
262
285
 
263
- #setSearchQuery(query: string): void {
264
- this.#menu.setQuery(query, false);
286
+ /** Applies the search field's value to the filter; clears any stale status. */
287
+ #syncSearchQuery(): void {
288
+ this.#menu.setQuery(this.#search.getValue(), false);
265
289
  this.#statusMessage = undefined;
266
290
  this.#updateList();
267
291
  }
268
292
 
293
+ /** Feeds keys the selector does not bind to the search field. Backspace on an empty query and a leading space bubble. */
269
294
  #handleSearchInput(keyData: string): boolean {
270
295
  if (!this.#isSearchEnabled()) return false;
271
-
272
- if (matchesKey(keyData, "backspace")) {
273
- if (this.#menu.query.length === 0) return false;
274
- const chars = [...this.#menu.query];
275
- chars.pop();
276
- this.#setSearchQuery(chars.join(""));
277
- return true;
296
+ const before = this.#search.getValue();
297
+ if (before.length === 0) {
298
+ if (matchesKey(keyData, "backspace")) return false;
299
+ const printableText = extractPrintableText(keyData);
300
+ if (printableText !== undefined && printableText.trim().length === 0) return false;
278
301
  }
279
-
280
- const printableText = extractPrintableText(keyData);
281
- if (printableText === undefined) return false;
282
- if (this.#menu.query.length === 0 && printableText.trim().length === 0) return false;
283
-
284
- this.#setSearchQuery(this.#menu.query + printableText);
302
+ const cursorBefore = this.#search.getCursor();
303
+ if (!this.#search.handleInput(keyData)) return false;
304
+ if (this.#search.getValue() !== before) this.#syncSearchQuery();
305
+ else if (this.#search.getCursor() !== cursorBefore) this.#updateList();
285
306
  return true;
286
307
  }
287
308
 
288
309
  #updateList(): void {
310
+ this.#nativeRoot = undefined;
311
+ this.#pickerRoot = undefined;
289
312
  this.#listContainer.clear();
290
313
 
291
314
  const items = this.#menu.visibleItems;
@@ -356,10 +379,6 @@ export class OAuthSelectorComponent extends OverlayPanel {
356
379
  return;
357
380
  }
358
381
 
359
- if (this.#handleSearchInput(keyData)) {
360
- return;
361
- }
362
-
363
382
  // Up arrow
364
383
  if (matchesSelectUp(keyData)) {
365
384
  this.#menu.move(-1, true);
@@ -388,6 +407,8 @@ export class OAuthSelectorComponent extends OverlayPanel {
388
407
  else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
389
408
  this.#confirmSelection();
390
409
  }
410
+ // Everything else edits the search field
411
+ else this.#handleSearchInput(keyData);
391
412
  }
392
413
 
393
414
  /** Confirm the selected provider (Enter or mouse click). */
@@ -403,6 +424,189 @@ export class OAuthSelectorComponent extends OverlayPanel {
403
424
  }
404
425
  }
405
426
 
427
+ /** Credential status of a provider row: `value` (state) and `detail` (origin) spans. */
428
+ #describeStatus(providerId: string): { value?: readonly TspSpan[]; detail?: readonly TspSpan[] } {
429
+ const origin = this.#authStorage.keys.source(providerId);
430
+ const detail = origin
431
+ ? [
432
+ span(
433
+ origin.kind === "env" && origin.envVar ? `env: ${origin.envVar}` : ORIGIN_LABELS[origin.kind],
434
+ "muted",
435
+ ),
436
+ ]
437
+ : undefined;
438
+ const state = this.#authState.get(providerId);
439
+ if (state === "checking") return { value: [span("checking", "warning", { fx: "pulse" })], detail };
440
+ if (state === "invalid") return { value: [span("invalid", "error")], detail };
441
+ if (state === "valid" || this.#hasSelectableAuth(providerId)) {
442
+ return { value: [span("logged in", "success")], detail };
443
+ }
444
+ return { detail };
445
+ }
446
+
447
+ /** One provider card: initials mark, origin detail, auth-state dot, disabled reason. */
448
+ #pickerItem(provider: OAuthProviderInfo): TspPickerItem {
449
+ const origin = this.#authStorage.keys.source(provider.id);
450
+ const state = this.#authState.get(provider.id);
451
+ const dot: TspTone =
452
+ state === "checking"
453
+ ? "pending"
454
+ : state === "invalid"
455
+ ? "error"
456
+ : state === "valid" || this.#hasSelectableAuth(provider.id)
457
+ ? "success"
458
+ : "muted";
459
+ const source = origin
460
+ ? origin.kind === "env" && origin.envVar
461
+ ? `env: ${origin.envVar}`
462
+ : ORIGIN_LABELS[origin.kind]
463
+ : undefined;
464
+ const detail = !source
465
+ ? "Not configured"
466
+ : origin?.kind === "oauth"
467
+ ? `Signed in · ${source}`
468
+ : `API key · ${source}`;
469
+ const initials = provider.name
470
+ .split(/[^\p{L}\p{N}]+/u)
471
+ .filter(word => word.length > 0)
472
+ .slice(0, 2)
473
+ .map(word => word[0]!.toUpperCase())
474
+ .join("");
475
+ return {
476
+ id: provider.id,
477
+ label: provider.name,
478
+ detail,
479
+ mark: { text: initials, seed: provider.id },
480
+ dot,
481
+ ...(provider.available ? {} : { disabled: "Provider unavailable in this environment" }),
482
+ };
483
+ }
484
+
485
+ #describePicker(): NativeNode {
486
+ if (this.#pickerRoot) return this.#pickerRoot;
487
+ const all = this.#menu.items;
488
+ this.#pickerItems ??= all.map(provider => this.#pickerItem(provider));
489
+ const query = this.#menu.query;
490
+ const login = this.#mode === "login";
491
+ const search = this.#shouldRenderSearchStatus() ? pickerQuery(this.#search) : pickerQuery(null);
492
+ this.#pickerRoot = dockedPicker({
493
+ title: login ? "Sign in" : "Sign out",
494
+ subtitle: login ? "Pick a provider" : "Remove stored credentials",
495
+ icon: "key-round",
496
+ noun: "providers",
497
+ size: "md",
498
+ layout: "cards",
499
+ preview: "none",
500
+ ...search,
501
+ placeholder: "Search providers…",
502
+ items: this.#pickerItems,
503
+ ...(query.length > 0 ? { order: this.#menu.visibleItems.map(provider => provider.id) } : {}),
504
+ selected: this.#menu.selectedKey ?? null,
505
+ total: all.length,
506
+ empty: login ? "No OAuth providers available" : "No stored provider credentials to log out",
507
+ ...(this.#statusMessage ? { message: this.#statusMessage } : {}),
508
+ actions: [
509
+ pickerAction(
510
+ "confirm",
511
+ login ? "Sign in" : "Sign out",
512
+ "enter",
513
+ login ? { primary: true } : { primary: true, danger: true },
514
+ ),
515
+ CLOSE_ACTION,
516
+ ],
517
+ });
518
+ return this.#pickerRoot;
519
+ }
520
+
521
+ override describe(cx: DescribeContext): NativeNode {
522
+ if (cx.supports("picker")) return this.#describePicker();
523
+ if (this.#nativeRoot) return this.#nativeRoot;
524
+ const items = this.#menu.visibleItems;
525
+ if (!this.#nativeItems || this.#nativeItemsSource !== items) {
526
+ this.#nativeItemsSource = items;
527
+ this.#nativeItems = items.map(provider =>
528
+ node(
529
+ "item",
530
+ {
531
+ label: provider.name,
532
+ tone: provider.available ? undefined : "muted",
533
+ ...this.#describeStatus(provider.id),
534
+ },
535
+ undefined,
536
+ provider.id,
537
+ ),
538
+ );
539
+ }
540
+ const query = this.#menu.query;
541
+ const children: NativeNode[] = [];
542
+ if (this.#shouldRenderSearchStatus()) {
543
+ children.push(
544
+ node(
545
+ "input",
546
+ { text: this.#search.getValue(), cursor: this.#search.getCursor(), placeholder: "Type to search" },
547
+ undefined,
548
+ "search",
549
+ ),
550
+ );
551
+ }
552
+ const empty =
553
+ this.#menu.items.length === 0
554
+ ? this.#mode === "login"
555
+ ? "No OAuth providers available"
556
+ : "No stored provider credentials to log out"
557
+ : "No matching providers";
558
+ children.push(
559
+ node(
560
+ "list",
561
+ {
562
+ selected: this.#menu.selectedKey ?? null,
563
+ filter: query.trim() || undefined,
564
+ empty: [span(empty, "muted")],
565
+ max: { lines: OAUTH_SELECTOR_MAX_VISIBLE },
566
+ },
567
+ this.#nativeItems,
568
+ "list",
569
+ ),
570
+ );
571
+ if (this.#statusMessage) children.push(text([span(this.#statusMessage, "warning")]));
572
+ this.#nativeRoot = overlayCard(this.nativeRole, this.title, children);
573
+ return this.#nativeRoot;
574
+ }
575
+
576
+ /** A click (or double-click) on a provider selects and confirms it, like the SGR click in {@link routeMouse}. */
577
+ handleNativeEvent(event: NativeUiEvent): void {
578
+ const ev = pickerEvent(event, PICKER_KEY);
579
+ if (ev?.kind === "action") {
580
+ if (ev.act === "confirm") this.#confirmSelection();
581
+ else if (ev.act === "clear") {
582
+ this.#search.setValue("");
583
+ this.#syncSearchQuery();
584
+ } else if (ev.act === "close" || ev.act === "cancel") {
585
+ this.stopValidation();
586
+ this.#onCancelCallback();
587
+ }
588
+ return;
589
+ }
590
+ if (ev?.kind === "select") {
591
+ const index = this.#menu.visibleItems.findIndex(provider => provider.id === ev.item);
592
+ if (index < 0 || index === this.#menu.selectedIndex) return;
593
+ this.#menu.setSelectedIndex(index);
594
+ this.#statusMessage = undefined;
595
+ this.#updateList();
596
+ return;
597
+ }
598
+ if ((event.type !== "select" && event.type !== "activate") || (event.key !== "list" && ev?.kind !== "activate"))
599
+ return;
600
+ const index = this.#menu.visibleItems.findIndex(provider => provider.id === event.item);
601
+ if (index < 0) return;
602
+ if (index !== this.#menu.selectedIndex) {
603
+ this.#menu.setSelectedIndex(index);
604
+ this.#statusMessage = undefined;
605
+ this.#updateList();
606
+ }
607
+ this.#confirmSelection();
608
+ }
609
+
406
610
  /** Move the selection one step for a wheel notch (clamped, no wrap). */
407
611
  handleWheel(delta: -1 | 1): void {
408
612
  if (this.#menu.visibleItems.length === 0) return;