@alumbwe/anvil 1.0.19 → 1.0.20

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 (494) hide show
  1. package/README.md +28 -30
  2. package/bin/anvil +2 -0
  3. package/package.json +71 -75
  4. package/src/agents/bundled-agents.generated.d.ts +14 -0
  5. package/src/agents/bundled-agents.generated.ts +3760 -0
  6. package/src/app.tsx +399 -0
  7. package/src/chat.tsx +1698 -0
  8. package/src/cli-args.ts +134 -0
  9. package/src/commands/__tests__/anvil-command-aliases.test.ts +56 -0
  10. package/src/commands/__tests__/bash-command.test.ts +445 -0
  11. package/src/commands/__tests__/command-args.test.ts +349 -0
  12. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
  13. package/src/commands/__tests__/copy-conversation.test.ts +226 -0
  14. package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
  15. package/src/commands/__tests__/image.test.ts +99 -0
  16. package/src/commands/__tests__/init.test.ts +456 -0
  17. package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
  18. package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
  19. package/src/commands/__tests__/prompt-builders.test.ts +28 -0
  20. package/src/commands/__tests__/router-input.test.ts +298 -0
  21. package/src/commands/ads.ts +44 -0
  22. package/src/commands/command-registry.ts +726 -0
  23. package/src/commands/copy-conversation.ts +392 -0
  24. package/src/commands/help.ts +13 -0
  25. package/src/commands/image.ts +20 -0
  26. package/src/commands/init.ts +121 -0
  27. package/src/commands/process-diagnostics.ts +120 -0
  28. package/src/commands/prompt-builders.ts +100 -0
  29. package/src/commands/publish.ts +203 -0
  30. package/src/commands/router-utils.ts +80 -0
  31. package/src/commands/router.ts +487 -0
  32. package/src/commands/usage.ts +26 -0
  33. package/src/components/__tests__/ad-banner.test.tsx +83 -0
  34. package/src/components/__tests__/anvil-landing-heading-row.test.tsx +91 -0
  35. package/src/components/__tests__/anvil-model-selector.test.tsx +519 -0
  36. package/src/components/__tests__/anvil-offer-invariants.test.ts +54 -0
  37. package/src/components/__tests__/anvil-takeover-prompt.test.tsx +212 -0
  38. package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
  39. package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
  40. package/src/components/__tests__/grid-layout.test.tsx +1051 -0
  41. package/src/components/__tests__/message-block.completion.test.tsx +104 -0
  42. package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
  43. package/src/components/__tests__/message-with-agents.test.tsx +579 -0
  44. package/src/components/__tests__/multiline-input.test.tsx +1132 -0
  45. package/src/components/__tests__/queue-panel.test.tsx +398 -0
  46. package/src/components/__tests__/selectable-list.test.ts +232 -0
  47. package/src/components/__tests__/status-bar.test.tsx +44 -0
  48. package/src/components/__tests__/status-indicator.test.tsx +204 -0
  49. package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
  50. package/src/components/__tests__/user-error-banner.test.tsx +102 -0
  51. package/src/components/ad-banner.tsx +323 -0
  52. package/src/components/agent-checklist.tsx +350 -0
  53. package/src/components/agent-mode-toggle.tsx +241 -0
  54. package/src/components/anvil-active-session-summary.tsx +63 -0
  55. package/src/components/anvil-landing-screen.tsx +865 -0
  56. package/src/components/anvil-mark.tsx +52 -0
  57. package/src/components/anvil-model-selector.tsx +1087 -0
  58. package/src/components/anvil-referral-banner.tsx +604 -0
  59. package/src/components/anvil-superseded-screen.tsx +62 -0
  60. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
  61. package/src/components/ask-user/__tests__/validation.test.ts +213 -0
  62. package/src/components/ask-user/components/accordion-question.tsx +153 -0
  63. package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
  64. package/src/components/ask-user/components/options-list.tsx +133 -0
  65. package/src/components/ask-user/components/question-header.tsx +80 -0
  66. package/src/components/ask-user/components/question-option.tsx +89 -0
  67. package/src/components/ask-user/constants.ts +38 -0
  68. package/src/components/ask-user/index.tsx +631 -0
  69. package/src/components/ask-user/utils/validation.ts +85 -0
  70. package/src/components/attachment-card.tsx +65 -0
  71. package/src/components/blocks/agent-block-grid.tsx +58 -0
  72. package/src/components/blocks/agent-branch-item.tsx +311 -0
  73. package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
  74. package/src/components/blocks/agent-list-branch.tsx +77 -0
  75. package/src/components/blocks/ask-user-branch.tsx +81 -0
  76. package/src/components/blocks/block-helpers.ts +11 -0
  77. package/src/components/blocks/blocks-renderer.tsx +289 -0
  78. package/src/components/blocks/content-with-markdown.tsx +33 -0
  79. package/src/components/blocks/image-block.tsx +128 -0
  80. package/src/components/blocks/implementor-row.tsx +429 -0
  81. package/src/components/blocks/single-block.tsx +201 -0
  82. package/src/components/blocks/thinking-block.tsx +67 -0
  83. package/src/components/blocks/tool-block-group.tsx +57 -0
  84. package/src/components/blocks/tool-branch.tsx +206 -0
  85. package/src/components/blocks/user-content-copy.tsx +157 -0
  86. package/src/components/bottom-banner.tsx +136 -0
  87. package/src/components/build-mode-buttons.tsx +110 -0
  88. package/src/components/button.tsx +66 -0
  89. package/src/components/chat-header.tsx +77 -0
  90. package/src/components/chat-history-screen.tsx +413 -0
  91. package/src/components/chat-input-bar.tsx +523 -0
  92. package/src/components/clickable-title-box.tsx +66 -0
  93. package/src/components/clickable.tsx +119 -0
  94. package/src/components/collapse-button.tsx +34 -0
  95. package/src/components/copy-button.tsx +203 -0
  96. package/src/components/elapsed-timer.tsx +55 -0
  97. package/src/components/error-boundary.tsx +55 -0
  98. package/src/components/feedback-container.tsx +181 -0
  99. package/src/components/feedback-icon-button.tsx +89 -0
  100. package/src/components/feedback-input-mode.tsx +344 -0
  101. package/src/components/file-attachment-card.tsx +98 -0
  102. package/src/components/grid-layout.tsx +80 -0
  103. package/src/components/help-banner.tsx +127 -0
  104. package/src/components/highlighted-text.tsx +52 -0
  105. package/src/components/image-card.tsx +167 -0
  106. package/src/components/image-thumbnail.tsx +114 -0
  107. package/src/components/input-cursor.tsx +77 -0
  108. package/src/components/input-mode-banner.tsx +57 -0
  109. package/src/components/load-previous-button.tsx +48 -0
  110. package/src/components/login-modal.tsx +475 -0
  111. package/src/components/message-block.tsx +346 -0
  112. package/src/components/message-footer.tsx +258 -0
  113. package/src/components/message-with-agents.tsx +506 -0
  114. package/src/components/mode-divider.tsx +43 -0
  115. package/src/components/multiline-input.tsx +1241 -0
  116. package/src/components/out-of-credits-banner.tsx +160 -0
  117. package/src/components/pending-attachments-banner.tsx +116 -0
  118. package/src/components/pending-bash-message.tsx +59 -0
  119. package/src/components/progress-bar.tsx +81 -0
  120. package/src/components/project-picker-screen.tsx +485 -0
  121. package/src/components/publish-confirmation.tsx +493 -0
  122. package/src/components/publish-container.tsx +687 -0
  123. package/src/components/queue-panel.tsx +310 -0
  124. package/src/components/raised-pill.tsx +95 -0
  125. package/src/components/renderers/plan-box.tsx +59 -0
  126. package/src/components/review-screen.tsx +112 -0
  127. package/src/components/scroll-to-bottom-button.tsx +34 -0
  128. package/src/components/segmented-control.tsx +187 -0
  129. package/src/components/selectable-list.tsx +248 -0
  130. package/src/components/selected-chips.tsx +80 -0
  131. package/src/components/separator.tsx +40 -0
  132. package/src/components/session-ended-banner.tsx +226 -0
  133. package/src/components/shimmer-text.tsx +248 -0
  134. package/src/components/status-bar.tsx +313 -0
  135. package/src/components/subscription-limit-banner.tsx +206 -0
  136. package/src/components/suggested-prompts.tsx +184 -0
  137. package/src/components/suggestion-menu.tsx +215 -0
  138. package/src/components/terminal-command-display.tsx +151 -0
  139. package/src/components/terminal-link.tsx +97 -0
  140. package/src/components/terminal-thinking-orb.tsx +95 -0
  141. package/src/components/text-attachment-card.tsx +77 -0
  142. package/src/components/thinking.tsx +109 -0
  143. package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
  144. package/src/components/tools/__tests__/code-search.test.tsx +45 -0
  145. package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
  146. package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
  147. package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
  148. package/src/components/tools/apply-patch.tsx +95 -0
  149. package/src/components/tools/code-search.tsx +45 -0
  150. package/src/components/tools/composio.tsx +152 -0
  151. package/src/components/tools/diff-viewer.tsx +76 -0
  152. package/src/components/tools/glob.tsx +61 -0
  153. package/src/components/tools/gravity-index.tsx +77 -0
  154. package/src/components/tools/list-directory.tsx +61 -0
  155. package/src/components/tools/read-docs.tsx +37 -0
  156. package/src/components/tools/read-files.tsx +91 -0
  157. package/src/components/tools/read-subtree.tsx +44 -0
  158. package/src/components/tools/read-url.tsx +33 -0
  159. package/src/components/tools/registry.ts +126 -0
  160. package/src/components/tools/render-ui.tsx +122 -0
  161. package/src/components/tools/run-terminal-command.tsx +79 -0
  162. package/src/components/tools/skill.tsx +29 -0
  163. package/src/components/tools/str-replace.tsx +77 -0
  164. package/src/components/tools/suggest-followups.tsx +369 -0
  165. package/src/components/tools/task-completed.tsx +16 -0
  166. package/src/components/tools/tool-call-item.tsx +273 -0
  167. package/src/components/tools/types.ts +55 -0
  168. package/src/components/tools/web-search.tsx +33 -0
  169. package/src/components/tools/write-file.tsx +16 -0
  170. package/src/components/tools/write-todos.tsx +95 -0
  171. package/src/components/top-banner.tsx +178 -0
  172. package/src/components/usage-banner.tsx +244 -0
  173. package/src/components/user-error-banner.tsx +56 -0
  174. package/src/components/validation-error-popover.tsx +192 -0
  175. package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
  176. package/src/contexts/chat-runtime-context.tsx +240 -0
  177. package/src/data/slash-commands.test.ts +24 -0
  178. package/src/data/slash-commands.ts +249 -0
  179. package/src/entry.ts +12 -0
  180. package/src/hooks/__tests__/holds-live-anvil-slot.test.ts +49 -0
  181. package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
  182. package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
  183. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
  184. package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
  185. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
  186. package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
  187. package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
  188. package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
  189. package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
  190. package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
  191. package/src/hooks/__tests__/use-input-history.test.ts +699 -0
  192. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
  193. package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
  194. package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
  195. package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
  196. package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
  197. package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
  198. package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
  199. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
  200. package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
  201. package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
  202. package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
  203. package/src/hooks/__tests__/use-timeout.test.ts +330 -0
  204. package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
  205. package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
  206. package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
  207. package/src/hooks/helpers/send-message.ts +654 -0
  208. package/src/hooks/stream-state.ts +83 -0
  209. package/src/hooks/use-activity-query.ts +668 -0
  210. package/src/hooks/use-agent-validation.ts +74 -0
  211. package/src/hooks/use-anvil-ctrl-c-exit.ts +23 -0
  212. package/src/hooks/use-anvil-session-progress.ts +34 -0
  213. package/src/hooks/use-anvil-session.ts +806 -0
  214. package/src/hooks/use-anvil-streak-query.ts +61 -0
  215. package/src/hooks/use-ask-user-bridge.ts +92 -0
  216. package/src/hooks/use-auth-query.ts +262 -0
  217. package/src/hooks/use-auth-state.ts +152 -0
  218. package/src/hooks/use-chat-input.ts +105 -0
  219. package/src/hooks/use-chat-keyboard.ts +321 -0
  220. package/src/hooks/use-chat-messages.ts +254 -0
  221. package/src/hooks/use-chat-state.ts +165 -0
  222. package/src/hooks/use-chat-streaming.ts +180 -0
  223. package/src/hooks/use-chat-ui.ts +131 -0
  224. package/src/hooks/use-clipboard.ts +151 -0
  225. package/src/hooks/use-connection-status.ts +142 -0
  226. package/src/hooks/use-directory-browser.ts +88 -0
  227. package/src/hooks/use-elapsed-time.ts +116 -0
  228. package/src/hooks/use-event.ts +34 -0
  229. package/src/hooks/use-exit-handler.ts +66 -0
  230. package/src/hooks/use-fetch-login-url.ts +65 -0
  231. package/src/hooks/use-fingerprint.ts +61 -0
  232. package/src/hooks/use-gravity-ad.ts +656 -0
  233. package/src/hooks/use-grid-layout.ts +73 -0
  234. package/src/hooks/use-input-history.ts +173 -0
  235. package/src/hooks/use-login-keyboard-handlers.ts +75 -0
  236. package/src/hooks/use-login-polling.ts +114 -0
  237. package/src/hooks/use-logo.tsx +161 -0
  238. package/src/hooks/use-message-queue.ts +387 -0
  239. package/src/hooks/use-now.ts +20 -0
  240. package/src/hooks/use-path-tab-completion.ts +88 -0
  241. package/src/hooks/use-publish-mutation.ts +60 -0
  242. package/src/hooks/use-queue-controls.ts +68 -0
  243. package/src/hooks/use-queue-ui.ts +74 -0
  244. package/src/hooks/use-scroll-management.ts +184 -0
  245. package/src/hooks/use-searchable-list.ts +98 -0
  246. package/src/hooks/use-send-message.ts +773 -0
  247. package/src/hooks/use-sheen-animation.tsx +88 -0
  248. package/src/hooks/use-subscription-query.ts +71 -0
  249. package/src/hooks/use-suggestion-engine.ts +783 -0
  250. package/src/hooks/use-terminal-breakpoints.ts +77 -0
  251. package/src/hooks/use-terminal-dimensions.ts +46 -0
  252. package/src/hooks/use-terminal-focus.ts +29 -0
  253. package/src/hooks/use-terminal-layout.ts +189 -0
  254. package/src/hooks/use-theme.tsx +140 -0
  255. package/src/hooks/use-timeout.ts +85 -0
  256. package/src/hooks/use-update-preference.ts +66 -0
  257. package/src/hooks/use-usage-monitor.ts +55 -0
  258. package/src/hooks/use-usage-query.ts +124 -0
  259. package/src/hooks/use-user-details-query.ts +93 -0
  260. package/src/hooks/use-why-did-you-update.ts +177 -0
  261. package/src/index.tsx +473 -0
  262. package/src/init/__tests__/init-direnv.test.ts +526 -0
  263. package/src/init/init-app.ts +34 -0
  264. package/src/init/init-direnv.ts +133 -0
  265. package/src/login/constants.ts +60 -0
  266. package/src/login/login-flow.ts +204 -0
  267. package/src/login/plain-login.ts +108 -0
  268. package/src/login/utils.ts +183 -0
  269. package/src/native/ripgrep.ts +80 -0
  270. package/src/polyfills/bun-strip-ansi.ts +12 -0
  271. package/src/pre-init/tree-sitter-wasm.ts +96 -0
  272. package/src/project-files.ts +102 -0
  273. package/src/setup/key-setup.ts +295 -0
  274. package/src/setup/load-env.ts +106 -0
  275. package/src/state/__tests__/chat-store-focus.test.ts +22 -0
  276. package/src/state/__tests__/feedback-store.test.ts +257 -0
  277. package/src/state/anvil-model-store.ts +42 -0
  278. package/src/state/anvil-session-store.ts +53 -0
  279. package/src/state/chat-history-store.ts +30 -0
  280. package/src/state/chat-store.ts +544 -0
  281. package/src/state/feedback-store.ts +160 -0
  282. package/src/state/login-store.ts +150 -0
  283. package/src/state/message-block-store.ts +149 -0
  284. package/src/state/publish-store.ts +146 -0
  285. package/src/state/queue-panel-store.ts +26 -0
  286. package/src/state/review-store.ts +24 -0
  287. package/src/types/anvil-session.ts +17 -0
  288. package/src/types/chat-state.ts +15 -0
  289. package/src/types/chat.ts +241 -0
  290. package/src/types/contracts/send-message.ts +12 -0
  291. package/src/types/env.ts +98 -0
  292. package/src/types/function-params.ts +28 -0
  293. package/src/types/react19-compat.d.ts +19 -0
  294. package/src/types/store.ts +111 -0
  295. package/src/types/theme-system.ts +144 -0
  296. package/src/types/utils.ts +3 -0
  297. package/src/utils/__tests__/active-run.test.ts +140 -0
  298. package/src/utils/__tests__/activity-tracker.test.ts +440 -0
  299. package/src/utils/__tests__/agent-display.test.ts +139 -0
  300. package/src/utils/__tests__/analytics-client.test.ts +262 -0
  301. package/src/utils/__tests__/anonymous-id.test.ts +74 -0
  302. package/src/utils/__tests__/anvil-api.test.ts +578 -0
  303. package/src/utils/__tests__/anvil-instance-owner.test.ts +69 -0
  304. package/src/utils/__tests__/anvil-model-navigation.test.ts +102 -0
  305. package/src/utils/__tests__/anvil-premium-reset.test.ts +79 -0
  306. package/src/utils/__tests__/anvil-referral-cache.test.ts +116 -0
  307. package/src/utils/__tests__/anvil-session-api.test.ts +215 -0
  308. package/src/utils/__tests__/anvil-session-display.test.ts +23 -0
  309. package/src/utils/__tests__/anvil-streak-line.test.ts +217 -0
  310. package/src/utils/__tests__/arrays.test.ts +264 -0
  311. package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
  312. package/src/utils/__tests__/block-processor.test.ts +810 -0
  313. package/src/utils/__tests__/chat-history.test.ts +203 -0
  314. package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
  315. package/src/utils/__tests__/chat-meta.test.ts +130 -0
  316. package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
  317. package/src/utils/__tests__/clipboard.test.ts +910 -0
  318. package/src/utils/__tests__/code-search-summary.test.ts +84 -0
  319. package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
  320. package/src/utils/__tests__/error-handling.test.ts +658 -0
  321. package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
  322. package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
  323. package/src/utils/__tests__/feedback-submission.test.ts +26 -0
  324. package/src/utils/__tests__/fetch-usage.test.ts +355 -0
  325. package/src/utils/__tests__/fingerprint.test.ts +144 -0
  326. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
  327. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
  328. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
  329. package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
  330. package/src/utils/__tests__/format-timeout.test.ts +87 -0
  331. package/src/utils/__tests__/image-dimensions.test.ts +249 -0
  332. package/src/utils/__tests__/image-processor.test.ts +209 -0
  333. package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
  334. package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
  335. package/src/utils/__tests__/layout-helpers.test.ts +36 -0
  336. package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
  337. package/src/utils/__tests__/log-shipper.test.ts +53 -0
  338. package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
  339. package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
  340. package/src/utils/__tests__/message-updater.test.ts +645 -0
  341. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
  342. package/src/utils/__tests__/pending-attachments.test.ts +277 -0
  343. package/src/utils/__tests__/polling-backoff.test.ts +51 -0
  344. package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
  345. package/src/utils/__tests__/run-state-storage.test.ts +919 -0
  346. package/src/utils/__tests__/safe-json.test.ts +77 -0
  347. package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
  348. package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
  349. package/src/utils/__tests__/send-message-timer.test.ts +64 -0
  350. package/src/utils/__tests__/settings.test.ts +99 -0
  351. package/src/utils/__tests__/strings.test.ts +192 -0
  352. package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
  353. package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
  354. package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
  355. package/src/utils/__tests__/terminal-io.test.ts +137 -0
  356. package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
  357. package/src/utils/__tests__/text-layout.test.ts +82 -0
  358. package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
  359. package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
  360. package/src/utils/__tests__/trace-writer.test.ts +223 -0
  361. package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
  362. package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
  363. package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
  364. package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
  365. package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
  366. package/src/utils/active-run.ts +131 -0
  367. package/src/utils/activity-tracker.ts +66 -0
  368. package/src/utils/agent-display.ts +87 -0
  369. package/src/utils/agent-helpers.ts +27 -0
  370. package/src/utils/agent-id-utils.ts +14 -0
  371. package/src/utils/ai-message-id.ts +12 -0
  372. package/src/utils/analytics.ts +361 -0
  373. package/src/utils/anonymous-id.ts +87 -0
  374. package/src/utils/anvil-agent-selection.ts +33 -0
  375. package/src/utils/anvil-api.ts +612 -0
  376. package/src/utils/anvil-client.ts +217 -0
  377. package/src/utils/anvil-instance-owner.ts +66 -0
  378. package/src/utils/anvil-model-navigation.ts +50 -0
  379. package/src/utils/anvil-premium-reset.ts +55 -0
  380. package/src/utils/anvil-referral-cache.ts +51 -0
  381. package/src/utils/anvil-session-api.ts +243 -0
  382. package/src/utils/anvil-session-display.ts +21 -0
  383. package/src/utils/anvil-streak-line.ts +93 -0
  384. package/src/utils/arrays.ts +117 -0
  385. package/src/utils/auth.ts +258 -0
  386. package/src/utils/bash-context-processor.ts +47 -0
  387. package/src/utils/bash-messages.ts +108 -0
  388. package/src/utils/block-margins.ts +35 -0
  389. package/src/utils/block-operations.ts +523 -0
  390. package/src/utils/block-processor.ts +213 -0
  391. package/src/utils/chat-history.ts +253 -0
  392. package/src/utils/chat-input-key-intercept.ts +52 -0
  393. package/src/utils/chat-meta.ts +90 -0
  394. package/src/utils/chat-scroll-accel.ts +151 -0
  395. package/src/utils/clipboard-image.ts +588 -0
  396. package/src/utils/clipboard.ts +397 -0
  397. package/src/utils/code-search-summary.ts +70 -0
  398. package/src/utils/collapse-helpers.ts +256 -0
  399. package/src/utils/config-dir.ts +24 -0
  400. package/src/utils/constants.ts +190 -0
  401. package/src/utils/create-event-handler-state.ts +73 -0
  402. package/src/utils/create-run-config.ts +121 -0
  403. package/src/utils/detect-shell.ts +116 -0
  404. package/src/utils/directory-browser.ts +72 -0
  405. package/src/utils/engagement.ts +55 -0
  406. package/src/utils/env.ts +92 -0
  407. package/src/utils/error-handling.ts +255 -0
  408. package/src/utils/error-messages.ts +62 -0
  409. package/src/utils/exit-cleanly.ts +86 -0
  410. package/src/utils/feedback-helpers.ts +103 -0
  411. package/src/utils/feedback-submission.ts +22 -0
  412. package/src/utils/fetch-usage.ts +76 -0
  413. package/src/utils/fingerprint.ts +245 -0
  414. package/src/utils/format-elapsed-time.ts +33 -0
  415. package/src/utils/format-session-units.ts +6 -0
  416. package/src/utils/format-timeout.ts +28 -0
  417. package/src/utils/format-validation-errors-for-message.ts +84 -0
  418. package/src/utils/git.ts +17 -0
  419. package/src/utils/helpers.ts +47 -0
  420. package/src/utils/image-display.ts +67 -0
  421. package/src/utils/image-handler.ts +325 -0
  422. package/src/utils/image-processor.ts +133 -0
  423. package/src/utils/image-thumbnail.ts +78 -0
  424. package/src/utils/implementor-helpers.ts +787 -0
  425. package/src/utils/input-modes.ts +174 -0
  426. package/src/utils/keyboard-actions.ts +392 -0
  427. package/src/utils/keypad-keys.ts +47 -0
  428. package/src/utils/layout-helpers.ts +33 -0
  429. package/src/utils/lazy-response-ads.ts +1 -0
  430. package/src/utils/local-agent-registry.ts +474 -0
  431. package/src/utils/log-shipper.ts +126 -0
  432. package/src/utils/logger.ts +327 -0
  433. package/src/utils/markdown-renderer.tsx +1090 -0
  434. package/src/utils/math.ts +3 -0
  435. package/src/utils/message-block-helpers.ts +684 -0
  436. package/src/utils/message-history.ts +143 -0
  437. package/src/utils/message-tree-utils.ts +67 -0
  438. package/src/utils/message-updater.ts +279 -0
  439. package/src/utils/open-file.ts +146 -0
  440. package/src/utils/open-url.ts +63 -0
  441. package/src/utils/path-completion.ts +111 -0
  442. package/src/utils/path-helpers.ts +38 -0
  443. package/src/utils/pending-attachments.ts +360 -0
  444. package/src/utils/polling-backoff.ts +59 -0
  445. package/src/utils/project-picker.ts +12 -0
  446. package/src/utils/queue-panel-actions.ts +70 -0
  447. package/src/utils/recent-projects.ts +156 -0
  448. package/src/utils/renderer-cleanup.ts +233 -0
  449. package/src/utils/response-ad-positions.ts +1 -0
  450. package/src/utils/run-state-storage.ts +603 -0
  451. package/src/utils/safe-json.ts +153 -0
  452. package/src/utils/sdk-event-handlers.ts +571 -0
  453. package/src/utils/send-message-helpers.ts +210 -0
  454. package/src/utils/send-message-timer.ts +110 -0
  455. package/src/utils/settings.ts +226 -0
  456. package/src/utils/skill-registry.ts +94 -0
  457. package/src/utils/spawn-agent-matcher.ts +55 -0
  458. package/src/utils/status-indicator-state.ts +107 -0
  459. package/src/utils/stream-chunk-processor.ts +57 -0
  460. package/src/utils/strings.ts +271 -0
  461. package/src/utils/subscription.ts +31 -0
  462. package/src/utils/syntax-highlighter.tsx +19 -0
  463. package/src/utils/terminal-color-detection.ts +474 -0
  464. package/src/utils/terminal-command-broker.ts +467 -0
  465. package/src/utils/terminal-enter-detection.ts +63 -0
  466. package/src/utils/terminal-images.ts +229 -0
  467. package/src/utils/terminal-io.ts +85 -0
  468. package/src/utils/terminal-protocol-controller.ts +173 -0
  469. package/src/utils/terminal-reset-sequences.ts +33 -0
  470. package/src/utils/terminal-title.ts +84 -0
  471. package/src/utils/terminal-watchdog.ts +366 -0
  472. package/src/utils/text-layout.ts +149 -0
  473. package/src/utils/theme-config.ts +141 -0
  474. package/src/utils/theme-system.ts +1206 -0
  475. package/src/utils/think-tag-parser.ts +95 -0
  476. package/src/utils/time-format.ts +21 -0
  477. package/src/utils/trace-writer.ts +162 -0
  478. package/src/utils/ui-constants.ts +69 -0
  479. package/src/utils/update-checker.ts +110 -0
  480. package/src/utils/usage-banner-state.ts +146 -0
  481. package/src/utils/validation-error-formatting.ts +86 -0
  482. package/src/utils/validation-error-helpers.ts +19 -0
  483. package/src/utils/windows-terminal-health.ts +124 -0
  484. package/src/utils/word-wrap-utils.ts +53 -0
  485. package/src/utils/write-file-atomic.ts +54 -0
  486. package/src/utils/yield-to-event-loop.ts +9 -0
  487. package/bin/anvil.cjs +0 -40
  488. package/dist/bundled.js +0 -123508
  489. package/dist/bundled.js.map +0 -828
  490. package/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
  491. package/dist/vendor/ripgrep/arm64-linux/rg +0 -0
  492. package/dist/vendor/ripgrep/x64-darwin/rg +0 -0
  493. package/dist/vendor/ripgrep/x64-linux/rg +0 -0
  494. package/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
@@ -0,0 +1,344 @@
1
+ import { TextAttributes } from '@opentui/core'
2
+ import React, { useRef, useState } from 'react'
3
+
4
+ import { Button } from './button'
5
+ import { MultilineInput, type MultilineInputHandle } from './multiline-input'
6
+ import { Separator } from './separator'
7
+ import { useTheme } from '../hooks/use-theme'
8
+ import { useChatStore } from '../state/chat-store'
9
+ import { IS_ANVIL } from '../utils/constants'
10
+ import { createTextPasteHandler } from '../utils/strings'
11
+ import { isPlainEnterKey } from '../utils/terminal-enter-detection'
12
+ import { BORDER_CHARS } from '../utils/ui-constants'
13
+
14
+ import type { FeedbackCategory } from '@anvil/common/constants/feedback'
15
+
16
+ type CategoryHighlightKey = 'success' | 'error' | 'warning' | 'info'
17
+
18
+ type CategoryOption = {
19
+ id: FeedbackCategory
20
+ label: string
21
+ shortLabel: string
22
+ highlightKey: CategoryHighlightKey
23
+ placeholder: string
24
+ }
25
+
26
+ const CATEGORY_OPTIONS = [
27
+ {
28
+ id: 'good_result',
29
+ label: 'Good result',
30
+ shortLabel: 'Good',
31
+ highlightKey: 'success',
32
+ placeholder:
33
+ 'What did you like? (e.g., "Fast and accurate", "Great explanation")',
34
+ },
35
+ {
36
+ id: 'bad_result',
37
+ label: 'Bad result',
38
+ shortLabel: 'Bad',
39
+ highlightKey: 'error',
40
+ placeholder:
41
+ 'What went wrong? (e.g., "Incorrect changes", "Missed the requirement")',
42
+ },
43
+ {
44
+ id: 'app_bug',
45
+ label: 'App bug',
46
+ shortLabel: 'Bug',
47
+ highlightKey: 'warning',
48
+ placeholder: IS_ANVIL
49
+ ? 'Report a problem with Anvil (crashes, errors, UI issues, etc.)'
50
+ : 'Report a problem with Anvil (crashes, errors, UI issues, etc.)',
51
+ },
52
+ {
53
+ id: 'other',
54
+ label: 'Other',
55
+ shortLabel: 'Other',
56
+ highlightKey: 'info',
57
+ placeholder: 'Tell us more (what happened, what you expected)...',
58
+ },
59
+ ] as const satisfies readonly CategoryOption[]
60
+
61
+ // Compile-time exhaustiveness: ensures every FeedbackCategory has a CATEGORY_OPTIONS entry.
62
+ // If a new category is added to FEEDBACK_CATEGORIES, TypeScript will error here until
63
+ // a corresponding entry is added to CATEGORY_OPTIONS above.
64
+ type CoveredCategories = (typeof CATEGORY_OPTIONS)[number]['id']
65
+ type _AssertAllCategoriesCovered = [FeedbackCategory] extends [CoveredCategories] ? true : never
66
+ const _exhaustiveCheck: _AssertAllCategoriesCovered = true
67
+ void _exhaustiveCheck
68
+
69
+ const FEEDBACK_CONTAINER_HORIZONTAL_INSET = 4 // border + padding on each side
70
+ const CATEGORY_BUTTON_EXTRA_WIDTH = 6 // indicator + padding + border
71
+ const CATEGORY_BUTTON_GAP_WIDTH = 1
72
+
73
+ const calculateCategoryRowWidth = (labels: readonly string[]): number =>
74
+ labels.reduce((total, label, idx) => {
75
+ const buttonWidth = label.length + CATEGORY_BUTTON_EXTRA_WIDTH
76
+ const gapWidth = idx === 0 ? 0 : CATEGORY_BUTTON_GAP_WIDTH
77
+ return total + buttonWidth + gapWidth
78
+ }, 0)
79
+
80
+ const FULL_CATEGORY_ROW_WIDTH = calculateCategoryRowWidth(
81
+ CATEGORY_OPTIONS.map((opt) => opt.label),
82
+ )
83
+
84
+ interface FeedbackTextSectionProps {
85
+ value: string
86
+ cursor: number
87
+ onChange: (text: string) => void
88
+ onCursorChange: (cursor: number) => void
89
+ onSubmit: () => void
90
+ placeholder: string
91
+ inputRef?: React.MutableRefObject<MultilineInputHandle | null>
92
+ width: number
93
+ isSubmitting?: boolean
94
+ }
95
+
96
+ const FeedbackTextSection: React.FC<FeedbackTextSectionProps> = ({
97
+ value,
98
+ cursor,
99
+ onChange,
100
+ onCursorChange,
101
+ onSubmit,
102
+ placeholder,
103
+ inputRef,
104
+ width,
105
+ isSubmitting = false,
106
+ }) => {
107
+ const inputFocused = useChatStore((state) => state.inputFocused)
108
+
109
+ return (
110
+ <>
111
+ {/* Top separator */}
112
+ <Separator width={width} widthOffset={4} />
113
+
114
+ {/* Feedback input */}
115
+ <box style={{ paddingTop: 0, paddingBottom: 0 }}>
116
+ <MultilineInput
117
+ value={value}
118
+ onChange={({ text, cursorPosition }) => {
119
+ onChange(text)
120
+ onCursorChange(cursorPosition)
121
+ }}
122
+ onSubmit={onSubmit}
123
+ onKeyIntercept={(key) => {
124
+ if (!isPlainEnterKey(key)) return false
125
+ // Just add newline on Enter
126
+ const newText = value.slice(0, cursor) + '\n' + value.slice(cursor)
127
+ onChange(newText)
128
+ onCursorChange(cursor + 1)
129
+ return true
130
+ }}
131
+ onPaste={createTextPasteHandler(value, cursor, ({ text, cursorPosition }) => {
132
+ onChange(text)
133
+ onCursorChange(cursorPosition)
134
+ })}
135
+ placeholder={placeholder}
136
+ focused={inputFocused && !isSubmitting}
137
+ maxHeight={5}
138
+ minHeight={3}
139
+ ref={inputRef}
140
+ cursorPosition={cursor}
141
+ />
142
+ </box>
143
+
144
+ {/* Bottom separator */}
145
+ <Separator width={width} widthOffset={4} />
146
+ </>
147
+ )
148
+ }
149
+
150
+ interface FeedbackInputModeProps {
151
+ value: string
152
+ cursor: number
153
+ feedbackCategory: FeedbackCategory
154
+ onChange: (text: string) => void
155
+ onCursorChange: (cursor: number) => void
156
+ onCategoryChange: (category: FeedbackCategory) => void
157
+ onSubmit: () => void
158
+ onCancel: () => void
159
+ inputRef?: React.MutableRefObject<any>
160
+ width: number
161
+ footerMessage?: string | null
162
+ isSubmitting?: boolean
163
+ }
164
+
165
+ export const FeedbackInputMode: React.FC<FeedbackInputModeProps> = ({
166
+ value,
167
+ cursor,
168
+ feedbackCategory,
169
+ onChange,
170
+ onCursorChange,
171
+ onCategoryChange,
172
+ onSubmit,
173
+ onCancel,
174
+ inputRef: externalInputRef,
175
+ width,
176
+ footerMessage,
177
+ isSubmitting = false,
178
+ }) => {
179
+ const theme = useTheme()
180
+ const internalInputRef = useRef<MultilineInputHandle | null>(null)
181
+ const inputRef = externalInputRef || internalInputRef
182
+ const canSubmit = value.trim().length > 0 && !isSubmitting
183
+ const [closeButtonHovered, setCloseButtonHovered] = useState(false)
184
+ const availableWidth = Math.max(
185
+ 0,
186
+ width - FEEDBACK_CONTAINER_HORIZONTAL_INSET,
187
+ )
188
+ const shouldUseShortLabels = FULL_CATEGORY_ROW_WIDTH > availableWidth
189
+
190
+ // Keyboard shortcuts are handled by useChatKeyboard in chat.tsx
191
+
192
+ return (
193
+ <box
194
+ border
195
+ borderStyle="single"
196
+ borderColor={theme.primary}
197
+ customBorderChars={BORDER_CHARS}
198
+ style={{
199
+ flexDirection: 'column',
200
+ gap: 0,
201
+ paddingLeft: 1,
202
+ paddingRight: 1,
203
+ paddingTop: 0,
204
+ paddingBottom: 0,
205
+ }}
206
+ >
207
+ {/* Header: helper text + close X */}
208
+ <box
209
+ style={{
210
+ flexDirection: 'row',
211
+ alignItems: 'center',
212
+ justifyContent: 'space-between',
213
+ marginTop: 1,
214
+ }}
215
+ >
216
+ <text style={{ wrapMode: 'none', marginLeft: 1, marginRight: 1 }}>
217
+ <span fg={theme.secondary}>
218
+ Share your feedback — thanks for helping us improve!
219
+ </span>
220
+ </text>
221
+ <box
222
+ style={{ paddingRight: 1 }}
223
+ onMouseDown={onCancel}
224
+ onMouseOver={() => setCloseButtonHovered(true)}
225
+ onMouseOut={() => setCloseButtonHovered(false)}
226
+ >
227
+ <text style={{ wrapMode: 'none' }} selectable={false}>
228
+ <span fg={closeButtonHovered ? theme.foreground : theme.secondary}>
229
+ [x]
230
+ </span>
231
+ </text>
232
+ </box>
233
+ </box>
234
+
235
+ {/* Category buttons */}
236
+ <box
237
+ style={{
238
+ flexDirection: 'row',
239
+ gap: 1,
240
+ paddingTop: 0,
241
+ paddingBottom: 0,
242
+ }}
243
+ >
244
+ {CATEGORY_OPTIONS.map((option) => {
245
+ const optionHighlight = theme[option.highlightKey]
246
+ const isSelected = feedbackCategory === option.id
247
+ const label = shouldUseShortLabels ? option.shortLabel : option.label
248
+ return (
249
+ <Button
250
+ key={option.id}
251
+ onClick={() => onCategoryChange(option.id)}
252
+ style={{
253
+ flexDirection: 'row',
254
+ alignItems: 'center',
255
+ gap: 1,
256
+ paddingLeft: 1,
257
+ paddingRight: 1,
258
+ paddingTop: 0,
259
+ paddingBottom: 0,
260
+ borderStyle: 'single',
261
+ borderColor: isSelected ? optionHighlight : theme.border,
262
+ customBorderChars: BORDER_CHARS,
263
+ backgroundColor: 'transparent',
264
+ }}
265
+ >
266
+ <text style={{ wrapMode: 'none' }}>
267
+ <span fg={isSelected ? optionHighlight : theme.muted}>
268
+ {isSelected ? '◉' : '◯'}
269
+ </span>
270
+ <span fg={isSelected ? theme.foreground : theme.secondary}>
271
+ {' '}
272
+ {label}
273
+ </span>
274
+ </text>
275
+ </Button>
276
+ )
277
+ })}
278
+ </box>
279
+
280
+ {/* Feedback text section with separators */}
281
+ <FeedbackTextSection
282
+ value={value}
283
+ cursor={cursor}
284
+ onChange={isSubmitting ? () => {} : onChange}
285
+ onCursorChange={isSubmitting ? () => {} : onCursorChange}
286
+ onSubmit={onSubmit}
287
+ placeholder={
288
+ isSubmitting
289
+ ? 'Sending feedback...'
290
+ : CATEGORY_OPTIONS.find((opt) => opt.id === feedbackCategory)
291
+ ?.placeholder ||
292
+ 'Tell us more (what happened, what you expected)...'
293
+ }
294
+ inputRef={inputRef}
295
+ width={width}
296
+ isSubmitting={isSubmitting}
297
+ />
298
+
299
+ {/* Footer with auto-attached info and submit button */}
300
+ <box
301
+ style={{
302
+ flexDirection: 'row',
303
+ justifyContent: 'space-between',
304
+ alignItems: 'center',
305
+ paddingTop: 0,
306
+ paddingBottom: 0,
307
+ gap: 2,
308
+ }}
309
+ >
310
+ <text style={{ wrapMode: 'none' }}>
311
+ <span fg={theme.muted}>
312
+ {footerMessage || 'Session details are auto-attached'}
313
+ </span>
314
+ </text>
315
+ <Button
316
+ onClick={() => {
317
+ if (canSubmit) onSubmit()
318
+ }}
319
+ style={{
320
+ paddingLeft: 1,
321
+ paddingRight: 1,
322
+ paddingTop: 0,
323
+ paddingBottom: 0,
324
+ borderStyle: 'single',
325
+ borderColor: canSubmit ? theme.foreground : theme.border,
326
+ customBorderChars: BORDER_CHARS,
327
+ backgroundColor: 'transparent',
328
+ }}
329
+ >
330
+ <text
331
+ style={{ wrapMode: 'none' }}
332
+ attributes={
333
+ canSubmit ? undefined : TextAttributes.DIM | TextAttributes.ITALIC
334
+ }
335
+ >
336
+ <span fg={canSubmit ? theme.foreground : theme.muted}>
337
+ {isSubmitting ? 'SENDING...' : 'SUBMIT'}
338
+ </span>
339
+ </text>
340
+ </Button>
341
+ </box>
342
+ </box>
343
+ )
344
+ }
@@ -0,0 +1,98 @@
1
+ import { AttachmentCard } from './attachment-card'
2
+ import { useTheme } from '../hooks/use-theme'
3
+
4
+ import type { FileAttachment } from '../types/chat'
5
+ import type { PendingFileAttachment } from '../types/store'
6
+
7
+ const FILE_CARD_WIDTH = 20
8
+ const MAX_FILENAME_LENGTH = 16
9
+
10
+ const FILE_ICON_LINES = [
11
+ ' ┌───╮',
12
+ ' │ ≡ │',
13
+ ' └───╯',
14
+ ]
15
+
16
+ const FOLDER_ICON_LINES = [
17
+ ' ╭──╮ ',
18
+ ' │ ╰──╮',
19
+ ' ╰─────╯',
20
+ ]
21
+
22
+ const truncateFilename = (filename: string): string => {
23
+ if (filename.length <= MAX_FILENAME_LENGTH) return filename
24
+ // Find extension — ignore leading dot (dotfiles like .gitignore)
25
+ const lastDot = filename.lastIndexOf('.')
26
+ const hasExtension = lastDot > 0
27
+ const ext = hasExtension ? filename.slice(lastDot) : ''
28
+ const baseName = hasExtension ? filename.slice(0, lastDot) : filename
29
+ const maxBaseLength = MAX_FILENAME_LENGTH - ext.length - 1 // -1 for ellipsis
30
+ if (maxBaseLength <= 0) return filename.slice(0, MAX_FILENAME_LENGTH - 1) + '…'
31
+ return baseName.slice(0, maxBaseLength) + '…' + ext
32
+ }
33
+
34
+ interface FileAttachmentCardProps {
35
+ attachment: PendingFileAttachment | FileAttachment
36
+ onRemove?: () => void
37
+ showRemoveButton?: boolean
38
+ }
39
+
40
+ export const FileAttachmentCard = ({
41
+ attachment,
42
+ onRemove,
43
+ showRemoveButton = true,
44
+ }: FileAttachmentCardProps) => {
45
+ const theme = useTheme()
46
+ const iconLines = attachment.isDirectory ? FOLDER_ICON_LINES : FILE_ICON_LINES
47
+ const truncatedName = truncateFilename(attachment.filename)
48
+ const status = 'status' in attachment ? attachment.status : undefined
49
+
50
+ return (
51
+ <AttachmentCard
52
+ width={FILE_CARD_WIDTH}
53
+ onRemove={onRemove}
54
+ showRemoveButton={showRemoveButton}
55
+ >
56
+ {/* ASCII art icon area */}
57
+ <box
58
+ style={{
59
+ height: 3,
60
+ justifyContent: 'center',
61
+ alignItems: 'center',
62
+ }}
63
+ >
64
+ <text style={{ fg: theme.info }}>
65
+ {iconLines.join('\n')}
66
+ </text>
67
+ </box>
68
+
69
+ {/* Filename and note */}
70
+ <box
71
+ style={{
72
+ paddingLeft: 1,
73
+ paddingRight: 1,
74
+ flexDirection: 'column',
75
+ }}
76
+ >
77
+ <text
78
+ style={{
79
+ fg: theme.foreground,
80
+ wrapMode: 'none',
81
+ }}
82
+ >
83
+ {truncatedName}
84
+ </text>
85
+ {(status === 'processing' || attachment.note) && (
86
+ <text
87
+ style={{
88
+ fg: status === 'error' ? theme.error : theme.muted,
89
+ wrapMode: 'none',
90
+ }}
91
+ >
92
+ {status === 'processing' ? 'reading…' : attachment.note}
93
+ </text>
94
+ )}
95
+ </box>
96
+ </AttachmentCard>
97
+ )
98
+ }
@@ -0,0 +1,80 @@
1
+ import React, { memo, type ReactNode } from 'react'
2
+
3
+ import { useGridLayout } from '../hooks/use-grid-layout'
4
+ import { MIN_COLUMN_WIDTH } from '../utils/layout-helpers'
5
+
6
+ export interface GridLayoutProps<T> {
7
+ items: T[]
8
+ availableWidth: number
9
+ getItemKey: (item: T) => string
10
+ renderItem: (item: T, index: number, columnWidth: number) => ReactNode
11
+ footer?: ReactNode
12
+ marginTop?: number
13
+ }
14
+
15
+ function GridLayoutInner<T>({
16
+ items,
17
+ availableWidth,
18
+ getItemKey,
19
+ renderItem,
20
+ footer,
21
+ marginTop = 0,
22
+ }: GridLayoutProps<T>): ReactNode {
23
+ const { columns, columnWidth, columnGroups } = useGridLayout(items, availableWidth)
24
+
25
+ if (items.length === 0) return null
26
+
27
+ // Unified structure for both single and multi-column layouts
28
+ // Using a consistent DOM structure prevents reconciliation issues during resize transitions
29
+ const isMultiColumn = columns > 1
30
+
31
+ return (
32
+ <box
33
+ style={{
34
+ flexDirection: 'column',
35
+ gap: isMultiColumn ? 1 : 0,
36
+ width: '100%',
37
+ marginTop,
38
+ }}
39
+ >
40
+ <box
41
+ style={{
42
+ flexDirection: 'row',
43
+ gap: isMultiColumn ? 1 : 0,
44
+ width: '100%',
45
+ alignItems: 'flex-start',
46
+ }}
47
+ >
48
+ {columnGroups.map((columnItems, colIdx) => {
49
+ const columnKey = columnItems[0]
50
+ ? getItemKey(columnItems[0])
51
+ : `col-${colIdx}`
52
+ return (
53
+ <box
54
+ key={columnKey}
55
+ style={{
56
+ flexDirection: 'column',
57
+ gap: 0,
58
+ flexGrow: 1,
59
+ flexShrink: 1,
60
+ flexBasis: 0,
61
+ // Use MIN_COLUMN_WIDTH instead of 0 to prevent columns from collapsing
62
+ // to zero during resize transitions (prevents 2→1 column transition bug)
63
+ minWidth: MIN_COLUMN_WIDTH,
64
+ }}
65
+ >
66
+ {columnItems.map((item, idx) => (
67
+ <box key={getItemKey(item)} style={{ width: '100%' }}>
68
+ {renderItem(item, idx, columnWidth)}
69
+ </box>
70
+ ))}
71
+ </box>
72
+ )
73
+ })}
74
+ </box>
75
+ {footer}
76
+ </box>
77
+ )
78
+ }
79
+
80
+ export const GridLayout = memo(GridLayoutInner) as typeof GridLayoutInner
@@ -0,0 +1,127 @@
1
+ import React from 'react'
2
+
3
+ import { BottomBanner } from './bottom-banner'
4
+ import { useSubscriptionQuery } from '../hooks/use-subscription-query'
5
+ import { useTheme } from '../hooks/use-theme'
6
+ import { IS_ANVIL } from '../utils/constants'
7
+ import { useChatStore } from '../state/chat-store'
8
+
9
+ const HELP_TIMEOUT = 60 * 1000 // 60 seconds
10
+
11
+ /** Section header component for consistent styling */
12
+ const SectionHeader = ({ children }: { children: React.ReactNode }) => {
13
+ const theme = useTheme()
14
+ return <text style={{ fg: theme.muted }}>{children}</text>
15
+ }
16
+
17
+ /** Keyboard shortcut item */
18
+ const Shortcut = ({
19
+ keys,
20
+ action,
21
+ }: {
22
+ keys: string
23
+ action: string
24
+ }) => {
25
+ const theme = useTheme()
26
+ return (
27
+ <box style={{ flexDirection: 'row', gap: 1 }}>
28
+ <text style={{ fg: theme.foreground }}>{keys}</text>
29
+ <text style={{ fg: theme.muted }}>{action}</text>
30
+ </box>
31
+ )
32
+ }
33
+
34
+ /** Help banner showing keyboard shortcuts and tips in an organized layout. */
35
+ export const HelpBanner = () => {
36
+ const setInputMode = useChatStore((state) => state.setInputMode)
37
+ const theme = useTheme()
38
+ const { data: subscriptionData } = useSubscriptionQuery()
39
+ const hasSubscription = subscriptionData?.hasSubscription ?? false
40
+
41
+ // Auto-hide after timeout
42
+ React.useEffect(() => {
43
+ const timer = setTimeout(() => {
44
+ setInputMode('default')
45
+ }, HELP_TIMEOUT)
46
+ return () => clearTimeout(timer)
47
+ }, [setInputMode])
48
+
49
+ return (
50
+ <BottomBanner
51
+ borderColorKey="info"
52
+ onClose={() => setInputMode('default')}
53
+ >
54
+ <box style={{ flexDirection: 'column', gap: 1, flexGrow: 1 }}>
55
+ {/* Shortcuts Section */}
56
+ <box style={{ flexDirection: 'column', gap: 0 }}>
57
+ <SectionHeader>Shortcuts</SectionHeader>
58
+ <box style={{ flexDirection: 'row', flexWrap: 'wrap', columnGap: 2, paddingLeft: 2 }}>
59
+ <Shortcut keys="Ctrl+C / Esc" action="stop" />
60
+ <Shortcut keys="Ctrl+J / Opt+Enter" action="newline" />
61
+ <Shortcut keys="↑↓" action="history" />
62
+ <Shortcut keys="Ctrl+T" action="collapse/expand agents" />
63
+ <Shortcut keys="Ctrl+Q" action="edit queued messages" />
64
+ </box>
65
+ </box>
66
+
67
+ {/* Features Section */}
68
+ <box style={{ flexDirection: 'column', gap: 0 }}>
69
+ <SectionHeader>Features</SectionHeader>
70
+ <box style={{ flexDirection: 'row', flexWrap: 'wrap', columnGap: 2, paddingLeft: 2 }}>
71
+ <Shortcut keys="/" action="commands" />
72
+ <Shortcut keys="@files" action="mention" />
73
+ <Shortcut keys="@agents" action="use agent" />
74
+ <Shortcut keys="!bash" action="run command" />
75
+ </box>
76
+ </box>
77
+
78
+ {/* Tips Section */}
79
+ <box style={{ flexDirection: 'column', gap: 0 }}>
80
+ <SectionHeader>Tips</SectionHeader>
81
+ <box style={{ flexDirection: 'column', paddingLeft: 2 }}>
82
+ {IS_ANVIL && (
83
+ <text style={{ fg: theme.muted }}>
84
+ Try workflow: /interview → /plan → implement → /review
85
+ </text>
86
+ )}
87
+ <text style={{ fg: theme.muted }}>
88
+ Use @ to reference agents to spawn or files to read
89
+ </text>
90
+ <text style={{ fg: theme.muted }}>
91
+ Drag to select text — it copies automatically (or click ⎘ on a
92
+ message)
93
+ </text>
94
+ <text style={{ fg: theme.muted }}>
95
+ Esc to cancel the current response
96
+ </text>
97
+ </box>
98
+ </box>
99
+
100
+ {/* Credits Section — hidden in Anvil */}
101
+ {!IS_ANVIL && (
102
+ <box style={{ flexDirection: 'column', gap: 0 }}>
103
+ <SectionHeader>Credits</SectionHeader>
104
+ <box style={{ flexDirection: 'column', paddingLeft: 2 }}>
105
+ <box style={{ flexDirection: 'row', flexWrap: 'wrap', columnGap: 1 }}>
106
+ <text style={{ fg: theme.foreground }}>1 credit = 1 cent</text>
107
+ <text style={{ fg: theme.muted }}>·</text>
108
+ <text style={{ fg: theme.foreground }}>/subscribe</text>
109
+ <text style={{ fg: theme.muted }}>·</text>
110
+ <text style={{ fg: theme.foreground }}>/usage</text>
111
+ {!hasSubscription && (
112
+ <>
113
+ <text style={{ fg: theme.muted }}>·</text>
114
+ <text style={{ fg: theme.foreground }}>/ads:enable</text>
115
+ </>
116
+ )}
117
+ </box>
118
+ <text style={{ fg: theme.muted }}>
119
+ Subscribe for the best credit rates — /subscribe
120
+ </text>
121
+ </box>
122
+ </box>
123
+ )}
124
+ </box>
125
+ </BottomBanner>
126
+ )
127
+ }
@@ -0,0 +1,52 @@
1
+ import React from 'react'
2
+
3
+ interface HighlightedSubsequenceTextProps {
4
+ text: string
5
+ indices?: number[] | null
6
+ color: string
7
+ highlightColor: string
8
+ }
9
+
10
+ export const HighlightedSubsequenceText = ({
11
+ text,
12
+ indices,
13
+ color,
14
+ highlightColor,
15
+ }: HighlightedSubsequenceTextProps) => {
16
+ if (!indices || indices.length === 0) {
17
+ return <span fg={color}>{text}</span>
18
+ }
19
+
20
+ const parts: React.ReactNode[] = []
21
+ const highlightSet = new Set(indices)
22
+ let currentSegment = ''
23
+ let isHighlighted = false
24
+
25
+ for (let i = 0; i < text.length; i++) {
26
+ const shouldHighlight = highlightSet.has(i)
27
+
28
+ if (shouldHighlight !== isHighlighted) {
29
+ if (currentSegment) {
30
+ parts.push(
31
+ <span key={parts.length} fg={isHighlighted ? highlightColor : color}>
32
+ {currentSegment}
33
+ </span>,
34
+ )
35
+ }
36
+ currentSegment = text[i]
37
+ isHighlighted = shouldHighlight
38
+ } else {
39
+ currentSegment += text[i]
40
+ }
41
+ }
42
+
43
+ if (currentSegment) {
44
+ parts.push(
45
+ <span key={parts.length} fg={isHighlighted ? highlightColor : color}>
46
+ {currentSegment}
47
+ </span>,
48
+ )
49
+ }
50
+
51
+ return <>{parts}</>
52
+ }