@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
@@ -40,6 +40,10 @@ import {
40
40
  import { OverlayPanel, PanelDivider, PanelRows } from "../chrome/overlay-box";
41
41
  import { formatKeyHint } from "../app-keybindings";
42
42
  import { editorKey, editorKeys } from "../chrome/keybinding-hints";
43
+ import type { TspSpan, TspTableColumn, TspText, TspTone } from "@oh-my-pi/pi-wire";
44
+ import { col, elapsed, node, span, text } from "../native/describe";
45
+ import { type DescribeContext, leafKey, type NativeChild, type NativeNode, type NativeUiEvent } from "../native/node";
46
+ import { actionButton } from "../native/overlay";
43
47
 
44
48
  /** Local calendar-day activity consumed by the usage heatmap. */
45
49
  export interface DailyActivityPoint {
@@ -281,6 +285,8 @@ export interface HeatmapLayout {
281
285
  cells: (number | null)[][];
282
286
  totalCost: number;
283
287
  totalRequests: number;
288
+ /** Local midnight of the first cell (column 0, Monday). */
289
+ start: Date;
284
290
  }
285
291
 
286
292
  const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
@@ -346,9 +352,129 @@ export function buildHeatmapLayout(points: DailyActivityPoint[], weeks: number,
346
352
  cells,
347
353
  totalCost: inRange.reduce((sum, point) => sum + point.cost, 0),
348
354
  totalRequests: inRange.reduce((sum, point) => sum + point.requests, 0),
355
+ start,
349
356
  };
350
357
  }
351
358
 
359
+ // =============================================================================
360
+ // Native description helpers
361
+ // =============================================================================
362
+
363
+ /** Weeks of history the native heatmap carries (a year, GitHub-style). */
364
+ const NATIVE_HEATMAP_WEEKS = 53;
365
+ /** Span tokens for heatmap intensity levels 1..4 (the `table` fallback). */
366
+ const HEAT_LEVEL_TOKENS = ["dim", "accent dim", "accent", "accent strong"];
367
+ /** Heatmap row labels, Monday first like omp's grid: M/W/F only. */
368
+ const HEATMAP_ROW_LABELS = ["M", "", "W", "", "F", "", ""];
369
+ const WEEKDAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
370
+
371
+ /** Meter tone of a quota window: ok is the accent fill, pressure warns, exhaustion errors. */
372
+ function statusTone(status: UsageLimit["status"]): TspTone {
373
+ if (status === "exhausted") return "error";
374
+ if (status === "warning") return "warning";
375
+ if (status === "ok") return "accent";
376
+ return "muted";
377
+ }
378
+
379
+ /** Status dot of a provider frame, toned by the card's aggregate status. */
380
+ function statusDot(status: UsageLimit["status"]): NativeNode {
381
+ const token =
382
+ status === "exhausted" ? "error" : status === "warning" ? "warning" : status === "ok" ? "success" : "dim";
383
+ const label =
384
+ status === "exhausted"
385
+ ? "Exhausted"
386
+ : status === "warning"
387
+ ? "Under pressure"
388
+ : status === "ok"
389
+ ? "OK"
390
+ : "Unknown";
391
+ return text([span("●", token)], { title: label, aria: label });
392
+ }
393
+
394
+ /**
395
+ * Used-fraction bar of a quota window, toned by status: a `meter` where the
396
+ * terminal draws one, else a `progress` bar.
397
+ */
398
+ function usageMeter(fraction: number, status: UsageLimit["status"], meter: boolean): NativeNode {
399
+ const value = Math.min(Math.max(fraction, 0), 1);
400
+ const tone = statusTone(status);
401
+ return meter ? node("meter", { value, style: "bar", size: "md", tone }) : node("progress", { value, tone, grow: 1 });
402
+ }
403
+
404
+ /** `62% left` for a used fraction (overage reads as 0%). */
405
+ function leftText(fraction: number): string {
406
+ return `${Math.max(0, Math.round((1 - fraction) * 100))}% left`;
407
+ }
408
+
409
+ /** `Mon 28 Sep` for a local date. */
410
+ function shortDate(date: Date): string {
411
+ return `${WEEKDAY_NAMES[date.getDay()]} ${date.getDate()} ${MONTH_NAMES[date.getMonth()]}`;
412
+ }
413
+
414
+ /** `18:00` for a local time. */
415
+ function clockTime(date: Date): string {
416
+ return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
417
+ }
418
+
419
+ /**
420
+ * When a window resets, relative to `nowMs`: `resets 18:00` today, `resets
421
+ * Tue 18:00` within the week, `resets 3 Oct` beyond; the title carries the
422
+ * absolute date and the countdown.
423
+ */
424
+ function resetLabel(nowMs: number, resetMs: number): { text: string; title: string } {
425
+ const at = new Date(nowMs + resetMs);
426
+ const now = new Date(nowMs);
427
+ const sameDay = at.toDateString() === now.toDateString();
428
+ const text = sameDay
429
+ ? `resets ${clockTime(at)}`
430
+ : resetMs < 6 * 86_400_000
431
+ ? `resets ${WEEKDAY_NAMES[at.getDay()]} ${clockTime(at)}`
432
+ : `resets ${at.getDate()} ${MONTH_NAMES[at.getMonth()]}`;
433
+ return {
434
+ text,
435
+ title: `Resets ${shortDate(at)} ${at.getFullYear()}, ${clockTime(at)} (in ${formatDuration(resetMs)})`,
436
+ };
437
+ }
438
+
439
+ function mutedText(content: string, wrap = false): NativeNode {
440
+ return text([span(content, "muted")], wrap ? { wrap: "word" } : { truncate: "end" });
441
+ }
442
+
443
+ /** Stable, human account name for a report in the detail view. */
444
+ function reportAccountLabel(report: UsageReport, limit: UsageLimit | undefined, index: number): string {
445
+ const metadata = report.metadata;
446
+ const pick = (value: unknown): string | undefined => (typeof value === "string" && value ? value : undefined);
447
+ const identity =
448
+ pick(metadata?.email) ??
449
+ pick(metadata?.accountId) ??
450
+ pick(metadata?.projectId) ??
451
+ pick(limit?.scope.projectId) ??
452
+ `account ${index + 1}`;
453
+ const org = pick(metadata?.orgName);
454
+ return sanitizeDisplayLine(org && org !== identity ? `${identity} (${org})` : identity);
455
+ }
456
+
457
+ /** Window qualifier worth showing after a limit title (none when the title already names it). */
458
+ function detailWindowLabel(label: string, limit: UsageLimit): string | undefined {
459
+ const windowLabel = limit.window?.label ?? limit.window?.id ?? limit.scope.windowId;
460
+ if (!windowLabel) return undefined;
461
+ const normalized = windowLabel.toLowerCase();
462
+ if (normalized === "quota window" || label.toLowerCase().includes(normalized)) return undefined;
463
+ return sanitizeDisplayLine(windowLabel);
464
+ }
465
+
466
+ /** `$1,234 · 5.6K requests` totals for the activity summary. */
467
+ function formatActivityTotals(layout: HeatmapLayout): string {
468
+ const cost =
469
+ layout.totalCost >= 1
470
+ ? `$${new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(layout.totalCost)}`
471
+ : `$${layout.totalCost.toFixed(2)}`;
472
+ const requests = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 }).format(
473
+ layout.totalRequests,
474
+ );
475
+ return `${cost} · ${requests} requests`;
476
+ }
477
+
352
478
  // =============================================================================
353
479
  // Component
354
480
  // =============================================================================
@@ -358,10 +484,10 @@ export interface UsageDashboardOptions {
358
484
  reports: UsageReport[];
359
485
  unavailableAccounts?: readonly UnavailableUsageAccount[];
360
486
  /**
361
- * Full classic `/usage` report for the expanded detail view; re-invoked per
362
- * terminal width.
487
+ * Full classic `/usage` report of `reports` (the latest refresh) for the
488
+ * expanded detail view; re-invoked per terminal width.
363
489
  */
364
- renderDetail: (width: number) => string;
490
+ renderDetail: (width: number, reports: UsageReport[]) => string;
365
491
  /**
366
492
  * Stream daily activity into the heatmap: push cached DB rows immediately,
367
493
  * then push again after an incremental session sync. Resolves when the sync
@@ -369,6 +495,8 @@ export interface UsageDashboardOptions {
369
495
  * the dashboard closes so an in-flight sync can stop early.
370
496
  */
371
497
  loadActivity: (push: (points: DailyActivityPoint[]) => void, signal: AbortSignal) => Promise<void>;
498
+ /** Re-fetch the usage reports (`r`); omitted when the host can't. Resolves null when nothing came back. */
499
+ refresh?: () => Promise<UsageReport[] | null>;
372
500
  requestRender: () => void;
373
501
  onClose: () => void;
374
502
  }
@@ -403,9 +531,16 @@ interface CardRowLayout {
403
531
  }
404
532
 
405
533
  export class UsageDashboardComponent implements Component {
534
+ /** The terminal draws the sheet: a large glass overlay titled Usage. */
535
+ readonly nativeOverlay = { role: "omp.overlay.usage", size: "lg", anchor: "center", head: "Usage" } as const;
406
536
  #options: UsageDashboardOptions;
537
+ #reports: UsageReport[];
407
538
  #cards: ProviderCard[];
408
539
  #nowMs: number;
540
+ #refreshing = false;
541
+ #refreshError: string | null = null;
542
+ /** Bumped whenever anything the native description shows changes. */
543
+ #revision = 0;
409
544
  #view: "overview" | "detail" = "overview";
410
545
  #scroll = 0;
411
546
  #activity: DailyActivityPoint[] | null = null;
@@ -419,12 +554,14 @@ export class UsageDashboardComponent implements Component {
419
554
  readonly #body: PanelRows;
420
555
  readonly #footer: PanelRows;
421
556
  readonly #closeController = new AbortController();
557
+ #nativeCache: { revision: number; meter: boolean; chart: boolean; node: NativeNode } | undefined;
422
558
 
423
559
  constructor(options: UsageDashboardOptions) {
424
560
  ensureThemeSync();
425
561
  this.#options = options;
562
+ this.#reports = options.reports;
426
563
  this.#nowMs = Date.now();
427
- this.#cards = buildProviderCards(options.reports, this.#nowMs, options.unavailableAccounts);
564
+ this.#cards = buildProviderCards(this.#reports, this.#nowMs, options.unavailableAccounts);
428
565
  this.#panel = new OverlayPanel("Usage");
429
566
  this.#header = new PanelRows();
430
567
  this.#header.setHeight(1);
@@ -443,13 +580,13 @@ export class UsageDashboardComponent implements Component {
443
580
  await this.#options.loadActivity(points => {
444
581
  if (this.#closed) return;
445
582
  this.#activity = points;
446
- this.#options.requestRender();
583
+ this.#changed();
447
584
  }, this.#closeController.signal);
448
585
  } catch (error) {
449
586
  this.#activityError = error instanceof Error ? error.message : String(error);
450
587
  } finally {
451
588
  this.#syncing = false;
452
- if (!this.#closed) this.#options.requestRender();
589
+ if (!this.#closed) this.#changed();
453
590
  }
454
591
  }
455
592
 
@@ -748,7 +885,10 @@ export class UsageDashboardComponent implements Component {
748
885
 
749
886
  #detailLines(innerWidth: number): string[] {
750
887
  if (this.#detailCache?.width !== innerWidth) {
751
- this.#detailCache = { width: innerWidth, lines: this.#options.renderDetail(innerWidth).split("\n") };
888
+ this.#detailCache = {
889
+ width: innerWidth,
890
+ lines: this.#options.renderDetail(innerWidth, this.#reports).split("\n"),
891
+ };
752
892
  }
753
893
  return this.#detailCache.lines;
754
894
  }
@@ -764,16 +904,21 @@ export class UsageDashboardComponent implements Component {
764
904
  const maxScroll = Math.max(0, contentSource.length - contentRows);
765
905
  if (this.#scroll > maxScroll) this.#scroll = maxScroll;
766
906
 
767
- const latestFetchedAt = Math.max(0, ...this.#options.reports.map(report => report.fetchedAt ?? 0));
768
- const checkedText = latestFetchedAt ? `checked ${formatDuration(this.#nowMs - latestFetchedAt)} ago` : "";
907
+ const latestFetchedAt = Math.max(0, ...this.#reports.map(report => report.fetchedAt ?? 0));
908
+ const checkedText = this.#refreshing
909
+ ? "refreshing…"
910
+ : latestFetchedAt
911
+ ? `checked ${formatDuration(this.#nowMs - latestFetchedAt)} ago`
912
+ : "";
769
913
  const title = this.#view === "detail" ? "Usage · Details" : "Usage";
770
914
 
771
915
  const scrollHint = maxScroll > 0 ? `${editorKeys("tui.select.up", "tui.select.down")} scroll · ` : "";
772
916
  const cancel = editorKey("tui.select.cancel");
917
+ const refreshHint = this.#options.refresh ? "r refresh · " : "";
773
918
  const hint =
774
919
  this.#view === "detail"
775
- ? `${scrollHint}${cancel} back`
776
- : `${scrollHint}${formatKeyHint("enter")} details · ${cancel} close`;
920
+ ? `${scrollHint}${refreshHint}${cancel} back`
921
+ : `${scrollHint}${refreshHint}${formatKeyHint("enter")} details · ${cancel} close`;
777
922
  this.#panel.title = title;
778
923
  this.#header.setLines([checkedText ? theme.fg("dim", checkedText) : ""]);
779
924
  this.#body.setLines(contentSource.slice(this.#scroll, this.#scroll + contentRows));
@@ -782,6 +927,432 @@ export class UsageDashboardComponent implements Component {
782
927
  return this.#panel.render(width);
783
928
  }
784
929
 
930
+ // ---------------------------------------------------------------------------
931
+ // Native description
932
+ // ---------------------------------------------------------------------------
933
+
934
+ /**
935
+ * The sheet body (the terminal's `lg` overlay is the frame): a head row
936
+ * (checked ago, Overview/Details tabs, Refresh), then either the provider
937
+ * grid of frames with window meters and the activity heatmap, or the
938
+ * per-provider detail tables. `meter`/`chart` fall back to
939
+ * `progress`/`table` on terminals without them. Rebuilt only when its
940
+ * inputs change.
941
+ */
942
+ describe(cx: DescribeContext): NativeNode {
943
+ const meter = cx.supports("meter");
944
+ const chart = cx.supports("chart");
945
+ const cache = this.#nativeCache;
946
+ if (cache && cache.revision === this.#revision && cache.meter === meter && cache.chart === chart) {
947
+ return cache.node;
948
+ }
949
+ const body = this.#view === "detail" ? this.#describeDetail() : this.#describeOverview(meter, chart);
950
+ const root = col([this.#describeHead(), node("col", { gap: "lg" }, body, this.#view)], { gap: "lg" });
951
+ this.#nativeCache = { revision: this.#revision, meter, chart, node: root };
952
+ return root;
953
+ }
954
+
955
+ /** Tab clicks switch views like Enter/Esc; the Refresh button runs `r`. */
956
+ handleNativeEvent(event: NativeUiEvent): void {
957
+ if (event.type === "action") {
958
+ if (event.act === "refresh") void this.#refresh();
959
+ return;
960
+ }
961
+ if (event.type !== "select" && event.type !== "activate") return;
962
+ if (leafKey(event.key) !== "tabs") return;
963
+ if ((event.item === "overview" || event.item === "detail") && event.item !== this.#view) {
964
+ this.#setView(event.item);
965
+ }
966
+ }
967
+
968
+ #describeHead(): NativeNode {
969
+ const checked: NativeChild[] = [];
970
+ const latestFetchedAt = Math.max(0, ...this.#reports.map(report => report.fetchedAt ?? 0));
971
+ if (this.#refreshing) {
972
+ checked.push(node("spinner", { label: [span("Refreshing…", "dim")] }));
973
+ } else if (this.#refreshError) {
974
+ checked.push(
975
+ text([span(`Refresh failed: ${sanitizeDisplayLine(this.#refreshError)}`, "warning")], {
976
+ truncate: "end",
977
+ }),
978
+ );
979
+ } else if (latestFetchedAt) {
980
+ checked.push(
981
+ text([span("checked", "dim")]),
982
+ elapsed(Date.now() - latestFetchedAt),
983
+ text([span("ago", "dim")]),
984
+ );
985
+ }
986
+ const children: NativeChild[] = [
987
+ node("row", { gap: "xs", align: "baseline", shrink: 1 }, checked, "checked"),
988
+ node("spacer", { grow: 1 }),
989
+ node(
990
+ "tabs",
991
+ {
992
+ items: [
993
+ { id: "overview", label: "Overview" },
994
+ { id: "detail", label: "Details" },
995
+ ],
996
+ active: this.#view,
997
+ },
998
+ undefined,
999
+ "tabs",
1000
+ ),
1001
+ ];
1002
+ if (this.#options.refresh) children.push(actionButton("Refresh", "refresh", { keys: "r" }));
1003
+ return node("row", { role: "omp.usage.head", gap: "sm", align: "center" }, children, "head");
1004
+ }
1005
+
1006
+ #describeOverview(meter: boolean, chart: boolean): NativeChild[] {
1007
+ const children: NativeChild[] = [];
1008
+ if (this.#cards.length === 0) {
1009
+ children.push(
1010
+ node("text", { spans: [span("No usage data available.")], role: "omp.usage.untouched" }, undefined, "none"),
1011
+ );
1012
+ } else {
1013
+ // Unlimited providers keep a frame reading "No limits"; only untouched ones collapse.
1014
+ const active = this.#cards.filter(entry => !entry.idle || entry.unlimited);
1015
+ const idle = this.#cards.filter(entry => entry.idle && !entry.unlimited);
1016
+ if (active.length > 0) {
1017
+ children.push(
1018
+ node(
1019
+ "row",
1020
+ { wrap: true, gap: "md", role: "omp.usage.grid" },
1021
+ active.map(entry => this.#describeCard(entry, meter)),
1022
+ "providers",
1023
+ ),
1024
+ );
1025
+ }
1026
+ if (idle.length > 0) {
1027
+ children.push(
1028
+ node(
1029
+ "text",
1030
+ {
1031
+ spans: [span(`Untouched: ${idle.map(entry => entry.name).join(", ")}`)],
1032
+ wrap: "word",
1033
+ role: "omp.usage.untouched",
1034
+ },
1035
+ undefined,
1036
+ "idle",
1037
+ ),
1038
+ );
1039
+ }
1040
+ }
1041
+ children.push(this.#describeActivity(chart));
1042
+ return children;
1043
+ }
1044
+
1045
+ #describeCard(entry: ProviderCard, meter: boolean): NativeNode {
1046
+ const cardStatus =
1047
+ entry.unavailableAccounts.length > 0
1048
+ ? statusWithUnavailableAccounts(entry.windows)
1049
+ : entry.unlimited
1050
+ ? "ok"
1051
+ : aggregateStatus(entry.windows);
1052
+ const head: NativeChild[] = [text([span(entry.name, "strong")], { truncate: "end" })];
1053
+ if (entry.accounts > 1) head.push(text([span(`${entry.accounts} accounts`, "muted")]));
1054
+ head.push(node("spacer", { grow: 1 }), statusDot(cardStatus));
1055
+ const children: NativeChild[] = [
1056
+ node("row", { role: "omp.usage.provider.head", gap: "sm", align: "center" }, head, "title"),
1057
+ ];
1058
+ for (const account of entry.daybreakAccounts ?? []) {
1059
+ children.push(text([span(`Daybreak · ${sanitizeDisplayLine(account)}`, "success")], { truncate: "end" }));
1060
+ }
1061
+ if (entry.resetCredits) {
1062
+ const resets = entry.resetCredits;
1063
+ const details: string[] = [];
1064
+ if (resets.redeemableCount !== resets.bankedCount) details.push(`${resets.redeemableCount} usable now`);
1065
+ if (resets.soonestExpiryMs !== undefined) {
1066
+ details.push(
1067
+ resets.soonestExpiryMs > 0 ? `expires in ${formatDuration(resets.soonestExpiryMs)}` : "expired",
1068
+ );
1069
+ }
1070
+ if (resets.redeemableCount === 0 && resets.unavailableReasons.length > 0) {
1071
+ details.push(`unavailable: ${sanitizeDisplayLine(resets.unavailableReasons.join(" • "))}`);
1072
+ }
1073
+ const label = `${resets.bankedCount} reset${resets.bankedCount === 1 ? "" : "s"} banked`;
1074
+ children.push(
1075
+ node(
1076
+ "row",
1077
+ { gap: "sm", align: "center" },
1078
+ [
1079
+ node("badge", {
1080
+ text: label,
1081
+ tone: resets.redeemableCount > 0 ? "success" : "warning",
1082
+ title: details.length > 0 ? `${label} · ${details.join(" · ")}` : label,
1083
+ }),
1084
+ ],
1085
+ "resets",
1086
+ ),
1087
+ );
1088
+ }
1089
+ for (const account of entry.unavailableAccounts) {
1090
+ children.push(mutedText(`${sanitizeDisplayLine(account)}: usage unavailable`, true));
1091
+ }
1092
+ if (entry.unlimited) {
1093
+ children.push(mutedText("No limits"));
1094
+ } else {
1095
+ for (const [index, window] of entry.windows.slice(0, CARD_MAX_WINDOWS).entries()) {
1096
+ const label: TspSpan[] = [span(sanitizeDisplayLine(window.label))];
1097
+ if (window.windowTag) label.push(span(` ${sanitizeDisplayLine(window.windowTag)}`, "dim"));
1098
+ const cells: NativeChild[] = [
1099
+ text(label, { role: "omp.usage.label", truncate: "middle", title: sanitizeDisplayLine(window.label) }),
1100
+ ];
1101
+ if (window.fraction === undefined) {
1102
+ cells.push(text([span(window.usedText ?? "No data", "muted")], { truncate: "end" }));
1103
+ } else {
1104
+ const token =
1105
+ window.status === "exhausted" ? "error" : window.status === "warning" ? "warning" : undefined;
1106
+ cells.push(
1107
+ usageMeter(window.fraction, window.status, meter),
1108
+ text([span(leftText(window.fraction), token)], { role: "omp.usage.pct" }),
1109
+ );
1110
+ if (window.resetMs !== undefined) {
1111
+ const reset = resetLabel(this.#nowMs, window.resetMs);
1112
+ cells.push(text([span(reset.text, "dim")], { role: "omp.usage.reset", title: reset.title }));
1113
+ }
1114
+ }
1115
+ children.push(node("row", { role: "omp.usage.window", gap: "sm", align: "center" }, cells, `w${index}`));
1116
+ }
1117
+ const hidden = entry.windows.length - CARD_MAX_WINDOWS;
1118
+ if (hidden > 0) children.push(mutedText(`+${hidden} more`));
1119
+ }
1120
+ return node(
1121
+ "card",
1122
+ { role: "omp.usage.provider", grow: 1, min: { w: `${CARD_MIN_WIDTH}ch` } },
1123
+ children,
1124
+ entry.provider,
1125
+ );
1126
+ }
1127
+
1128
+ #describeActivity(chart: boolean): NativeNode {
1129
+ const head: NativeChild[] = [text([span("Activity", "strong")])];
1130
+ if (this.#syncing && this.#activity) head.push(node("spinner", { label: [span("Syncing", "dim")] }));
1131
+ const children: NativeChild[] = [node("row", { gap: "sm", align: "center" }, head, "head")];
1132
+ const points = this.#activity;
1133
+ if (this.#activityError) {
1134
+ const detail = formatActivityErrorDetail(this.#activityError);
1135
+ children.push(
1136
+ mutedText(detail ? `Usage history unavailable (${detail}).` : "Usage history unavailable.", true),
1137
+ );
1138
+ } else if (!points) {
1139
+ children.push(node("spinner", { label: [span("Loading usage history…", "muted")] }));
1140
+ } else {
1141
+ const layout = buildHeatmapLayout(points, NATIVE_HEATMAP_WEEKS);
1142
+ const summary =
1143
+ layout.totalRequests > 0 || layout.totalCost > 0
1144
+ ? `${formatActivityTotals(layout)} · last ${NATIVE_HEATMAP_WEEKS} weeks`
1145
+ : `No activity in the last ${NATIVE_HEATMAP_WEEKS} weeks`;
1146
+ children.push(chart ? this.#heatmapChart(layout, points, summary) : this.#heatmapTable(layout, summary));
1147
+ }
1148
+ return node("col", { role: "omp.usage.activity", gap: "sm" }, children, "activity");
1149
+ }
1150
+
1151
+ /** The heatmap as a `chart`: 0–1 intensities, month columns, M/W/F rows, per-day tooltips. */
1152
+ #heatmapChart(layout: HeatmapLayout, points: readonly DailyActivityPoint[], summary: string): NativeNode {
1153
+ const byDay = new Map(points.map(point => [point.day, point]));
1154
+ const cells = layout.cells.map(days => days.map(level => (level === null ? null : level / 4)));
1155
+ const tips = layout.cells.map((days, day) =>
1156
+ days.map((level, week) => {
1157
+ if (level === null) return null;
1158
+ const date = addDays(layout.start, week * 7 + day);
1159
+ const point = byDay.get(localIso(date));
1160
+ if (!point || (point.cost <= 0 && point.requests <= 0)) return `${shortDate(date)} · No activity`;
1161
+ const requests = `${point.requests} request${point.requests === 1 ? "" : "s"}`;
1162
+ const cost = point.cost >= 100 ? `$${Math.round(point.cost)}` : `$${point.cost.toFixed(2)}`;
1163
+ return `${shortDate(date)} · ${cost} · ${requests}`;
1164
+ }),
1165
+ );
1166
+ // A month label needs room: drop one whose successor starts within two columns (GitHub's rule).
1167
+ const starts = layout.monthLabels.flatMap((label, at) => (label ? [{ at, label }] : []));
1168
+ const cols = starts.filter((entry, index) => {
1169
+ const next = starts[index + 1];
1170
+ return !next || next.at - entry.at > 2;
1171
+ });
1172
+ return node(
1173
+ "chart",
1174
+ { kind: "heatmap", cells, cols, rows: HEATMAP_ROW_LABELS, tips, token: "accent", summary, size: "md" },
1175
+ undefined,
1176
+ "heatmap",
1177
+ );
1178
+ }
1179
+
1180
+ /** The heatmap as a `table` of glyph cells, for terminals without `chart`. */
1181
+ #heatmapTable(layout: HeatmapLayout, summary: string): NativeNode {
1182
+ const cols: TspTableColumn[] = [{ id: "day", priority: NATIVE_HEATMAP_WEEKS }];
1183
+ for (let week = 0; week < NATIVE_HEATMAP_WEEKS; week++) {
1184
+ cols.push({ id: `w${week}`, align: "center", priority: week });
1185
+ }
1186
+ const rows = HEATMAP_DAY_LABELS.map((label, day) => {
1187
+ const cells: Record<string, TspText> = { day: [span(label, "dim")] };
1188
+ for (let week = 0; week < NATIVE_HEATMAP_WEEKS; week++) {
1189
+ const level = layout.cells[day][week];
1190
+ if (level === null) continue;
1191
+ cells[`w${week}`] = level === 0 ? [span("·", "dim")] : [span("■", HEAT_LEVEL_TOKENS[level - 1])];
1192
+ }
1193
+ return { id: `d${day}`, cells };
1194
+ });
1195
+ return node(
1196
+ "col",
1197
+ { gap: "sm" },
1198
+ [mutedText(summary), node("table", { cols, rows }, undefined, "table")],
1199
+ "heatmap",
1200
+ );
1201
+ }
1202
+
1203
+ /** Per provider: notes and saved resets as a `kv`, the limit buckets as a `table` (one row per account). */
1204
+ #describeDetail(): NativeChild[] {
1205
+ const nowMs = this.#nowMs;
1206
+ const grouped = new Map<string, UsageReport[]>();
1207
+ for (const report of collapseSharedUsageReports(this.#reports)) {
1208
+ const list = grouped.get(report.provider) ?? [];
1209
+ list.push(report);
1210
+ grouped.set(report.provider, list);
1211
+ }
1212
+ const unavailable = this.#options.unavailableAccounts ?? [];
1213
+ const sections: NativeChild[] = [];
1214
+ for (const entry of this.#cards) {
1215
+ const reports = grouped.get(entry.provider) ?? [];
1216
+ const children: NativeChild[] = [];
1217
+ for (const account of unavailable) {
1218
+ if (account.provider !== entry.provider) continue;
1219
+ children.push(mutedText(`${sanitizeDisplayLine(account.label)}: usage unavailable`, true));
1220
+ }
1221
+
1222
+ const facts: { k: TspText; v: TspText }[] = [];
1223
+ const notes = [...new Set(reports.flatMap(report => report.notes ?? []))];
1224
+ if (notes.length > 0) facts.push({ k: [span("Notes", "muted")], v: sanitizeDisplayLine(notes.join(" • ")) });
1225
+ for (const [index, report] of reports.entries()) {
1226
+ const resets = summarizeUsageResetCredits(report.resetCredits, nowMs);
1227
+ if (!resets || resets.bankedCount <= 0) continue;
1228
+ const value: TspSpan[] = [
1229
+ span(
1230
+ `${resets.bankedCount} saved reset${resets.bankedCount === 1 ? "" : "s"}`,
1231
+ resets.redeemableCount > 0 ? "success" : "warning",
1232
+ ),
1233
+ ];
1234
+ if (resets.redeemableCount !== resets.bankedCount) {
1235
+ value.push(span(` · ${resets.redeemableCount} usable now`, "muted"));
1236
+ }
1237
+ if (resets.soonestExpiry) {
1238
+ const remaining = Date.parse(resets.soonestExpiry) - nowMs;
1239
+ const date = resets.soonestExpiry.slice(0, 10);
1240
+ value.push(
1241
+ span(
1242
+ remaining > 0 ? ` · expires in ${formatDuration(remaining)} (${date})` : ` · expired (${date})`,
1243
+ "muted",
1244
+ ),
1245
+ );
1246
+ }
1247
+ if (resets.redeemableCount === 0 && resets.unavailableReason) {
1248
+ value.push(span(` · unavailable: ${sanitizeDisplayLine(resets.unavailableReason)}`, "muted"));
1249
+ }
1250
+ facts.push({
1251
+ k: [span(`Resets · ${reportAccountLabel(report, report.limits[0], index)}`, "muted")],
1252
+ v: value,
1253
+ });
1254
+ }
1255
+ if (facts.length > 0) children.push(node("kv", { items: facts, layout: "grid" }, undefined, "facts"));
1256
+
1257
+ const buckets = new Map<string, { label: string; window?: string; rows: Record<string, TspText>[] }>();
1258
+ const limitNotes: string[] = [];
1259
+ for (const [index, report] of reports.entries()) {
1260
+ for (const limit of report.limits) {
1261
+ const label = sanitizeDisplayLine(formatLimitTitle(limit));
1262
+ const key = `${label}|${limit.window?.id ?? limit.scope.windowId ?? "default"}`;
1263
+ let bucket = buckets.get(key);
1264
+ if (!bucket) {
1265
+ bucket = { label, window: detailWindowLabel(label, limit), rows: [] };
1266
+ buckets.set(key, bucket);
1267
+ }
1268
+ const fraction = resolveUsedFraction(limit);
1269
+ const token =
1270
+ limit.status === "exhausted" ? "error" : limit.status === "warning" ? "warning" : undefined;
1271
+ const resetsAt = limit.window?.resetsAt;
1272
+ bucket.rows.push({
1273
+ account: [span(reportAccountLabel(report, limit, index), "muted")],
1274
+ left:
1275
+ fraction === undefined
1276
+ ? [span(formatAbsoluteOnlyAmount([limit]) ?? "No data", "muted")]
1277
+ : [span(leftText(fraction), token)],
1278
+ reset:
1279
+ resetsAt !== undefined && resetsAt > nowMs
1280
+ ? [
1281
+ span(
1282
+ `${limit.window?.resetLabel ?? "resets"} in ${formatDuration(resetsAt - nowMs)}`,
1283
+ "dim",
1284
+ ),
1285
+ ]
1286
+ : "",
1287
+ });
1288
+ for (const note of limit.notes ?? []) limitNotes.push(sanitizeDisplayLine(note));
1289
+ }
1290
+ }
1291
+ if (buckets.size > 0) {
1292
+ const rows: { id: string; cells: Record<string, TspText> }[] = [];
1293
+ for (const bucket of buckets.values()) {
1294
+ for (const [index, cells] of bucket.rows.entries()) {
1295
+ const limitCell: TspSpan[] =
1296
+ index === 0
1297
+ ? [span(bucket.label), ...(bucket.window ? [span(` ${bucket.window}`, "dim")] : [])]
1298
+ : [];
1299
+ rows.push({ id: `r${rows.length}`, cells: { limit: limitCell, ...cells } });
1300
+ }
1301
+ }
1302
+ const cols: TspTableColumn[] = [
1303
+ { id: "limit", head: "Limit", grow: 1, priority: 4 },
1304
+ { id: "account", head: "Account", truncate: "middle", priority: 1 },
1305
+ { id: "left", head: "Left", align: "end", priority: 3 },
1306
+ { id: "reset", head: "Resets", align: "end", priority: 2 },
1307
+ ];
1308
+ children.push(node("table", { cols, rows }, undefined, "limits"));
1309
+ for (const note of new Set(limitNotes)) children.push(mutedText(note, true));
1310
+ } else if (unavailable.every(account => account.provider !== entry.provider)) {
1311
+ children.push(mutedText("No limits"));
1312
+ }
1313
+ sections.push(
1314
+ node(
1315
+ "section",
1316
+ { head: [span(entry.name, "strong")], role: "omp.usage.report" },
1317
+ [col(children, { gap: "sm" })],
1318
+ entry.provider,
1319
+ ),
1320
+ );
1321
+ }
1322
+ if (sections.length === 0) sections.push(mutedText("No usage data available."));
1323
+ return sections;
1324
+ }
1325
+
1326
+ /** Re-fetch the reports (`r` / Refresh); the last reports stay on failure, with the error in the head. */
1327
+ async #refresh(): Promise<void> {
1328
+ const refresh = this.#options.refresh;
1329
+ if (!refresh || this.#refreshing || this.#closed) return;
1330
+ this.#refreshing = true;
1331
+ this.#refreshError = null;
1332
+ this.#changed();
1333
+ try {
1334
+ const reports = await refresh();
1335
+ if (this.#closed) return;
1336
+ if (reports) {
1337
+ this.#reports = reports;
1338
+ this.#nowMs = Date.now();
1339
+ this.#cards = buildProviderCards(reports, this.#nowMs, this.#options.unavailableAccounts);
1340
+ this.#detailCache = null;
1341
+ }
1342
+ } catch (error) {
1343
+ this.#refreshError = error instanceof Error ? error.message : String(error);
1344
+ } finally {
1345
+ this.#refreshing = false;
1346
+ if (!this.#closed) this.#changed();
1347
+ }
1348
+ }
1349
+
1350
+ /** Invalidate the native description and repaint. */
1351
+ #changed(): void {
1352
+ this.#revision++;
1353
+ this.#options.requestRender();
1354
+ }
1355
+
785
1356
  #scrollBy(delta: number): void {
786
1357
  this.#scroll = Math.max(0, this.#scroll + delta);
787
1358
  this.#options.requestRender();
@@ -790,7 +1361,7 @@ export class UsageDashboardComponent implements Component {
790
1361
  #setView(view: "overview" | "detail"): void {
791
1362
  this.#view = view;
792
1363
  this.#scroll = 0;
793
- this.#options.requestRender();
1364
+ this.#changed();
794
1365
  }
795
1366
 
796
1367
  handleInput(data: string): void {
@@ -812,6 +1383,10 @@ export class UsageDashboardComponent implements Component {
812
1383
  this.#options.onClose();
813
1384
  return;
814
1385
  }
1386
+ if (matchesKey(data, "r")) {
1387
+ void this.#refresh();
1388
+ return;
1389
+ }
815
1390
  if (
816
1391
  this.#view === "overview" &&
817
1392
  (matchesKey(data, "return") || matchesKey(data, "tab") || matchesKey(data, "d"))