@alumbwe/anvil 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. package/README.md +84 -0
  2. package/bin/anvil +2 -0
  3. package/bin/tree-sitter.wasm +0 -0
  4. package/package.json +67 -0
  5. package/src/__tests__/README.md +256 -0
  6. package/src/__tests__/bash-mode.test.ts +442 -0
  7. package/src/__tests__/cli-args.test.ts +181 -0
  8. package/src/__tests__/cli-harness.test.ts +52 -0
  9. package/src/__tests__/e2e/first-time-login.test.ts +173 -0
  10. package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
  11. package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
  12. package/src/__tests__/e2e-cli.test.ts +183 -0
  13. package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
  14. package/src/__tests__/helpers/mock-api-client.ts +64 -0
  15. package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
  16. package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
  17. package/src/__tests__/home-directory-detection.test.ts +213 -0
  18. package/src/__tests__/integration/api-integration.test.ts +288 -0
  19. package/src/__tests__/integration/credentials-storage.test.ts +486 -0
  20. package/src/__tests__/integration/local-agents.test.ts +1187 -0
  21. package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
  22. package/src/__tests__/integration/login-polling-working.test.ts +285 -0
  23. package/src/__tests__/integration/queue-editing.test.tsx +261 -0
  24. package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
  25. package/src/__tests__/integration-tmux.test.ts +188 -0
  26. package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
  27. package/src/__tests__/launcher-disconnect.test.ts +104 -0
  28. package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
  29. package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
  30. package/src/__tests__/path-completion.test.ts +243 -0
  31. package/src/__tests__/project-files-chat-id.test.ts +34 -0
  32. package/src/__tests__/release/proxy-http-get.test.ts +667 -0
  33. package/src/__tests__/release/wrapper-safety.test.ts +412 -0
  34. package/src/__tests__/renderer-cleanup.test.ts +174 -0
  35. package/src/__tests__/rerender-perf.integration.test.ts +283 -0
  36. package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
  37. package/src/__tests__/terminal-watchdog.test.ts +388 -0
  38. package/src/__tests__/test-utils.ts +271 -0
  39. package/src/__tests__/tmux-poc.ts +150 -0
  40. package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
  41. package/src/__tests__/unit/copy-button.test.ts +157 -0
  42. package/src/__tests__/unit/create-run-config.test.ts +66 -0
  43. package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
  44. package/src/__tests__/unit/segmented-control.test.ts +152 -0
  45. package/src/__tests__/utils/env.test.ts +169 -0
  46. package/src/__tests__/utils/project-picker.test.ts +39 -0
  47. package/src/agents/bundled-agents.generated.d.ts +14 -0
  48. package/src/agents/bundled-agents.generated.ts +3692 -0
  49. package/src/app.tsx +399 -0
  50. package/src/chat.tsx +1698 -0
  51. package/src/cli-args.ts +134 -0
  52. package/src/commands/__tests__/bash-command.test.ts +445 -0
  53. package/src/commands/__tests__/command-args.test.ts +349 -0
  54. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
  55. package/src/commands/__tests__/copy-conversation.test.ts +226 -0
  56. package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
  57. package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
  58. package/src/commands/__tests__/image.test.ts +99 -0
  59. package/src/commands/__tests__/init.test.ts +456 -0
  60. package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
  61. package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
  62. package/src/commands/__tests__/prompt-builders.test.ts +28 -0
  63. package/src/commands/__tests__/router-input.test.ts +298 -0
  64. package/src/commands/ads.ts +44 -0
  65. package/src/commands/command-registry.ts +726 -0
  66. package/src/commands/copy-conversation.ts +392 -0
  67. package/src/commands/help.ts +13 -0
  68. package/src/commands/image.ts +20 -0
  69. package/src/commands/init.ts +121 -0
  70. package/src/commands/process-diagnostics.ts +120 -0
  71. package/src/commands/prompt-builders.ts +100 -0
  72. package/src/commands/publish.ts +203 -0
  73. package/src/commands/router-utils.ts +80 -0
  74. package/src/commands/router.ts +487 -0
  75. package/src/commands/usage.ts +26 -0
  76. package/src/components/__tests__/ad-banner.test.tsx +83 -0
  77. package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
  78. package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
  79. package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
  80. package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
  81. package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
  82. package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
  83. package/src/components/__tests__/grid-layout.test.tsx +1051 -0
  84. package/src/components/__tests__/message-block.completion.test.tsx +104 -0
  85. package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
  86. package/src/components/__tests__/message-with-agents.test.tsx +579 -0
  87. package/src/components/__tests__/multiline-input.test.tsx +1132 -0
  88. package/src/components/__tests__/queue-panel.test.tsx +398 -0
  89. package/src/components/__tests__/selectable-list.test.ts +232 -0
  90. package/src/components/__tests__/status-bar.test.tsx +44 -0
  91. package/src/components/__tests__/status-indicator.test.tsx +204 -0
  92. package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
  93. package/src/components/__tests__/user-error-banner.test.tsx +102 -0
  94. package/src/components/ad-banner.tsx +323 -0
  95. package/src/components/agent-checklist.tsx +350 -0
  96. package/src/components/agent-mode-toggle.tsx +241 -0
  97. package/src/components/anvil-mark.tsx +52 -0
  98. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
  99. package/src/components/ask-user/__tests__/validation.test.ts +213 -0
  100. package/src/components/ask-user/components/accordion-question.tsx +153 -0
  101. package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
  102. package/src/components/ask-user/components/options-list.tsx +133 -0
  103. package/src/components/ask-user/components/question-header.tsx +80 -0
  104. package/src/components/ask-user/components/question-option.tsx +89 -0
  105. package/src/components/ask-user/constants.ts +38 -0
  106. package/src/components/ask-user/index.tsx +631 -0
  107. package/src/components/ask-user/utils/validation.ts +85 -0
  108. package/src/components/attachment-card.tsx +65 -0
  109. package/src/components/blocks/agent-block-grid.tsx +58 -0
  110. package/src/components/blocks/agent-branch-item.tsx +311 -0
  111. package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
  112. package/src/components/blocks/agent-list-branch.tsx +77 -0
  113. package/src/components/blocks/ask-user-branch.tsx +81 -0
  114. package/src/components/blocks/block-helpers.ts +11 -0
  115. package/src/components/blocks/blocks-renderer.tsx +289 -0
  116. package/src/components/blocks/content-with-markdown.tsx +33 -0
  117. package/src/components/blocks/image-block.tsx +128 -0
  118. package/src/components/blocks/implementor-row.tsx +429 -0
  119. package/src/components/blocks/single-block.tsx +201 -0
  120. package/src/components/blocks/thinking-block.tsx +67 -0
  121. package/src/components/blocks/tool-block-group.tsx +57 -0
  122. package/src/components/blocks/tool-branch.tsx +206 -0
  123. package/src/components/blocks/user-content-copy.tsx +157 -0
  124. package/src/components/bottom-banner.tsx +136 -0
  125. package/src/components/build-mode-buttons.tsx +110 -0
  126. package/src/components/button.tsx +66 -0
  127. package/src/components/chat-header.tsx +77 -0
  128. package/src/components/chat-history-screen.tsx +413 -0
  129. package/src/components/chat-input-bar.tsx +523 -0
  130. package/src/components/clickable-title-box.tsx +66 -0
  131. package/src/components/clickable.tsx +119 -0
  132. package/src/components/collapse-button.tsx +34 -0
  133. package/src/components/copy-button.tsx +203 -0
  134. package/src/components/elapsed-timer.tsx +55 -0
  135. package/src/components/error-boundary.tsx +55 -0
  136. package/src/components/feedback-container.tsx +181 -0
  137. package/src/components/feedback-icon-button.tsx +89 -0
  138. package/src/components/feedback-input-mode.tsx +344 -0
  139. package/src/components/file-attachment-card.tsx +98 -0
  140. package/src/components/freebuff-active-session-summary.tsx +63 -0
  141. package/src/components/freebuff-landing-screen.tsx +865 -0
  142. package/src/components/freebuff-model-selector.tsx +1087 -0
  143. package/src/components/freebuff-referral-banner.tsx +604 -0
  144. package/src/components/freebuff-superseded-screen.tsx +62 -0
  145. package/src/components/grid-layout.tsx +80 -0
  146. package/src/components/help-banner.tsx +127 -0
  147. package/src/components/highlighted-text.tsx +52 -0
  148. package/src/components/image-card.tsx +167 -0
  149. package/src/components/image-thumbnail.tsx +114 -0
  150. package/src/components/input-cursor.tsx +77 -0
  151. package/src/components/input-mode-banner.tsx +57 -0
  152. package/src/components/load-previous-button.tsx +48 -0
  153. package/src/components/login-modal.tsx +475 -0
  154. package/src/components/message-block.tsx +346 -0
  155. package/src/components/message-footer.tsx +258 -0
  156. package/src/components/message-with-agents.tsx +506 -0
  157. package/src/components/mode-divider.tsx +43 -0
  158. package/src/components/multiline-input.tsx +1241 -0
  159. package/src/components/out-of-credits-banner.tsx +160 -0
  160. package/src/components/pending-attachments-banner.tsx +116 -0
  161. package/src/components/pending-bash-message.tsx +59 -0
  162. package/src/components/progress-bar.tsx +81 -0
  163. package/src/components/project-picker-screen.tsx +485 -0
  164. package/src/components/publish-confirmation.tsx +493 -0
  165. package/src/components/publish-container.tsx +687 -0
  166. package/src/components/queue-panel.tsx +310 -0
  167. package/src/components/raised-pill.tsx +95 -0
  168. package/src/components/renderers/plan-box.tsx +59 -0
  169. package/src/components/review-screen.tsx +112 -0
  170. package/src/components/scroll-to-bottom-button.tsx +34 -0
  171. package/src/components/segmented-control.tsx +187 -0
  172. package/src/components/selectable-list.tsx +248 -0
  173. package/src/components/selected-chips.tsx +80 -0
  174. package/src/components/separator.tsx +40 -0
  175. package/src/components/session-ended-banner.tsx +226 -0
  176. package/src/components/shimmer-text.tsx +248 -0
  177. package/src/components/status-bar.tsx +313 -0
  178. package/src/components/subscription-limit-banner.tsx +206 -0
  179. package/src/components/suggested-prompts.tsx +184 -0
  180. package/src/components/suggestion-menu.tsx +215 -0
  181. package/src/components/terminal-command-display.tsx +151 -0
  182. package/src/components/terminal-link.tsx +97 -0
  183. package/src/components/terminal-thinking-orb.tsx +95 -0
  184. package/src/components/text-attachment-card.tsx +77 -0
  185. package/src/components/thinking.tsx +109 -0
  186. package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
  187. package/src/components/tools/__tests__/code-search.test.tsx +45 -0
  188. package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
  189. package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
  190. package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
  191. package/src/components/tools/apply-patch.tsx +95 -0
  192. package/src/components/tools/code-search.tsx +45 -0
  193. package/src/components/tools/composio.tsx +152 -0
  194. package/src/components/tools/diff-viewer.tsx +76 -0
  195. package/src/components/tools/glob.tsx +61 -0
  196. package/src/components/tools/gravity-index.tsx +77 -0
  197. package/src/components/tools/list-directory.tsx +61 -0
  198. package/src/components/tools/read-docs.tsx +37 -0
  199. package/src/components/tools/read-files.tsx +91 -0
  200. package/src/components/tools/read-subtree.tsx +44 -0
  201. package/src/components/tools/read-url.tsx +33 -0
  202. package/src/components/tools/registry.ts +126 -0
  203. package/src/components/tools/render-ui.tsx +122 -0
  204. package/src/components/tools/run-terminal-command.tsx +79 -0
  205. package/src/components/tools/skill.tsx +29 -0
  206. package/src/components/tools/str-replace.tsx +77 -0
  207. package/src/components/tools/suggest-followups.tsx +369 -0
  208. package/src/components/tools/task-completed.tsx +16 -0
  209. package/src/components/tools/tool-call-item.tsx +273 -0
  210. package/src/components/tools/types.ts +55 -0
  211. package/src/components/tools/web-search.tsx +33 -0
  212. package/src/components/tools/write-file.tsx +16 -0
  213. package/src/components/tools/write-todos.tsx +95 -0
  214. package/src/components/top-banner.tsx +178 -0
  215. package/src/components/usage-banner.tsx +244 -0
  216. package/src/components/user-error-banner.tsx +56 -0
  217. package/src/components/validation-error-popover.tsx +192 -0
  218. package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
  219. package/src/contexts/chat-runtime-context.tsx +240 -0
  220. package/src/data/slash-commands.test.ts +24 -0
  221. package/src/data/slash-commands.ts +249 -0
  222. package/src/entry.ts +12 -0
  223. package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
  224. package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
  225. package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
  226. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
  227. package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
  228. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
  229. package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
  230. package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
  231. package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
  232. package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
  233. package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
  234. package/src/hooks/__tests__/use-input-history.test.ts +699 -0
  235. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
  236. package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
  237. package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
  238. package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
  239. package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
  240. package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
  241. package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
  242. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
  243. package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
  244. package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
  245. package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
  246. package/src/hooks/__tests__/use-timeout.test.ts +330 -0
  247. package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
  248. package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
  249. package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
  250. package/src/hooks/helpers/send-message.ts +654 -0
  251. package/src/hooks/stream-state.ts +83 -0
  252. package/src/hooks/use-activity-query.ts +668 -0
  253. package/src/hooks/use-agent-validation.ts +74 -0
  254. package/src/hooks/use-ask-user-bridge.ts +92 -0
  255. package/src/hooks/use-auth-query.ts +253 -0
  256. package/src/hooks/use-auth-state.ts +152 -0
  257. package/src/hooks/use-chat-input.ts +105 -0
  258. package/src/hooks/use-chat-keyboard.ts +321 -0
  259. package/src/hooks/use-chat-messages.ts +254 -0
  260. package/src/hooks/use-chat-state.ts +165 -0
  261. package/src/hooks/use-chat-streaming.ts +180 -0
  262. package/src/hooks/use-chat-ui.ts +131 -0
  263. package/src/hooks/use-clipboard.ts +151 -0
  264. package/src/hooks/use-connection-status.ts +142 -0
  265. package/src/hooks/use-directory-browser.ts +88 -0
  266. package/src/hooks/use-elapsed-time.ts +116 -0
  267. package/src/hooks/use-event.ts +34 -0
  268. package/src/hooks/use-exit-handler.ts +66 -0
  269. package/src/hooks/use-fetch-login-url.ts +65 -0
  270. package/src/hooks/use-fingerprint.ts +61 -0
  271. package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
  272. package/src/hooks/use-freebuff-session-progress.ts +34 -0
  273. package/src/hooks/use-freebuff-session.ts +806 -0
  274. package/src/hooks/use-freebuff-streak-query.ts +61 -0
  275. package/src/hooks/use-gravity-ad.ts +656 -0
  276. package/src/hooks/use-grid-layout.ts +73 -0
  277. package/src/hooks/use-input-history.ts +173 -0
  278. package/src/hooks/use-login-keyboard-handlers.ts +75 -0
  279. package/src/hooks/use-login-polling.ts +114 -0
  280. package/src/hooks/use-logo.tsx +161 -0
  281. package/src/hooks/use-message-queue.ts +387 -0
  282. package/src/hooks/use-now.ts +20 -0
  283. package/src/hooks/use-path-tab-completion.ts +88 -0
  284. package/src/hooks/use-publish-mutation.ts +60 -0
  285. package/src/hooks/use-queue-controls.ts +68 -0
  286. package/src/hooks/use-queue-ui.ts +74 -0
  287. package/src/hooks/use-scroll-management.ts +184 -0
  288. package/src/hooks/use-searchable-list.ts +98 -0
  289. package/src/hooks/use-send-message.ts +773 -0
  290. package/src/hooks/use-sheen-animation.tsx +88 -0
  291. package/src/hooks/use-subscription-query.ts +71 -0
  292. package/src/hooks/use-suggestion-engine.ts +783 -0
  293. package/src/hooks/use-terminal-breakpoints.ts +77 -0
  294. package/src/hooks/use-terminal-dimensions.ts +46 -0
  295. package/src/hooks/use-terminal-focus.ts +29 -0
  296. package/src/hooks/use-terminal-layout.ts +189 -0
  297. package/src/hooks/use-theme.tsx +140 -0
  298. package/src/hooks/use-timeout.ts +85 -0
  299. package/src/hooks/use-update-preference.ts +66 -0
  300. package/src/hooks/use-usage-monitor.ts +55 -0
  301. package/src/hooks/use-usage-query.ts +124 -0
  302. package/src/hooks/use-user-details-query.ts +93 -0
  303. package/src/hooks/use-why-did-you-update.ts +177 -0
  304. package/src/index.tsx +441 -0
  305. package/src/init/__tests__/init-direnv.test.ts +526 -0
  306. package/src/init/init-app.ts +34 -0
  307. package/src/init/init-direnv.ts +133 -0
  308. package/src/login/constants.ts +60 -0
  309. package/src/login/login-flow.ts +204 -0
  310. package/src/login/plain-login.ts +108 -0
  311. package/src/login/utils.ts +183 -0
  312. package/src/native/ripgrep.ts +80 -0
  313. package/src/polyfills/bun-strip-ansi.ts +12 -0
  314. package/src/pre-init/tree-sitter-wasm.ts +96 -0
  315. package/src/project-files.ts +102 -0
  316. package/src/smoke/terminal-command-broker.ts +439 -0
  317. package/src/state/__tests__/chat-store-focus.test.ts +22 -0
  318. package/src/state/__tests__/feedback-store.test.ts +257 -0
  319. package/src/state/chat-history-store.ts +30 -0
  320. package/src/state/chat-store.ts +544 -0
  321. package/src/state/feedback-store.ts +160 -0
  322. package/src/state/freebuff-model-store.ts +42 -0
  323. package/src/state/freebuff-session-store.ts +53 -0
  324. package/src/state/login-store.ts +150 -0
  325. package/src/state/message-block-store.ts +149 -0
  326. package/src/state/publish-store.ts +146 -0
  327. package/src/state/queue-panel-store.ts +26 -0
  328. package/src/state/review-store.ts +24 -0
  329. package/src/testing/env.ts +46 -0
  330. package/src/types/chat-state.ts +15 -0
  331. package/src/types/chat.ts +241 -0
  332. package/src/types/contracts/send-message.ts +12 -0
  333. package/src/types/env.ts +98 -0
  334. package/src/types/freebuff-session.ts +17 -0
  335. package/src/types/function-params.ts +28 -0
  336. package/src/types/react19-compat.d.ts +19 -0
  337. package/src/types/store.ts +111 -0
  338. package/src/types/theme-system.ts +144 -0
  339. package/src/types/utils.ts +3 -0
  340. package/src/utils/__tests__/active-run.test.ts +140 -0
  341. package/src/utils/__tests__/activity-tracker.test.ts +440 -0
  342. package/src/utils/__tests__/agent-display.test.ts +139 -0
  343. package/src/utils/__tests__/analytics-client.test.ts +262 -0
  344. package/src/utils/__tests__/anonymous-id.test.ts +74 -0
  345. package/src/utils/__tests__/anvil-api.test.ts +578 -0
  346. package/src/utils/__tests__/arrays.test.ts +264 -0
  347. package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
  348. package/src/utils/__tests__/block-processor.test.ts +810 -0
  349. package/src/utils/__tests__/chat-history.test.ts +203 -0
  350. package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
  351. package/src/utils/__tests__/chat-meta.test.ts +130 -0
  352. package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
  353. package/src/utils/__tests__/clipboard.test.ts +910 -0
  354. package/src/utils/__tests__/code-search-summary.test.ts +84 -0
  355. package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
  356. package/src/utils/__tests__/error-handling.test.ts +658 -0
  357. package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
  358. package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
  359. package/src/utils/__tests__/feedback-submission.test.ts +26 -0
  360. package/src/utils/__tests__/fetch-usage.test.ts +355 -0
  361. package/src/utils/__tests__/fingerprint.test.ts +144 -0
  362. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
  363. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
  364. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
  365. package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
  366. package/src/utils/__tests__/format-timeout.test.ts +87 -0
  367. package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
  368. package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
  369. package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
  370. package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
  371. package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
  372. package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
  373. package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
  374. package/src/utils/__tests__/image-dimensions.test.ts +249 -0
  375. package/src/utils/__tests__/image-processor.test.ts +209 -0
  376. package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
  377. package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
  378. package/src/utils/__tests__/layout-helpers.test.ts +36 -0
  379. package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
  380. package/src/utils/__tests__/log-shipper.test.ts +53 -0
  381. package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
  382. package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
  383. package/src/utils/__tests__/message-updater.test.ts +645 -0
  384. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
  385. package/src/utils/__tests__/pending-attachments.test.ts +277 -0
  386. package/src/utils/__tests__/polling-backoff.test.ts +51 -0
  387. package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
  388. package/src/utils/__tests__/run-state-storage.test.ts +919 -0
  389. package/src/utils/__tests__/safe-json.test.ts +77 -0
  390. package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
  391. package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
  392. package/src/utils/__tests__/send-message-timer.test.ts +64 -0
  393. package/src/utils/__tests__/settings.test.ts +99 -0
  394. package/src/utils/__tests__/strings.test.ts +192 -0
  395. package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
  396. package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
  397. package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
  398. package/src/utils/__tests__/terminal-io.test.ts +137 -0
  399. package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
  400. package/src/utils/__tests__/text-layout.test.ts +82 -0
  401. package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
  402. package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
  403. package/src/utils/__tests__/trace-writer.test.ts +223 -0
  404. package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
  405. package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
  406. package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
  407. package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
  408. package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
  409. package/src/utils/active-run.ts +131 -0
  410. package/src/utils/activity-tracker.ts +66 -0
  411. package/src/utils/agent-display.ts +87 -0
  412. package/src/utils/agent-helpers.ts +27 -0
  413. package/src/utils/agent-id-utils.ts +14 -0
  414. package/src/utils/ai-message-id.ts +12 -0
  415. package/src/utils/analytics.ts +361 -0
  416. package/src/utils/anonymous-id.ts +87 -0
  417. package/src/utils/anvil-api.ts +612 -0
  418. package/src/utils/anvil-client.ts +217 -0
  419. package/src/utils/arrays.ts +117 -0
  420. package/src/utils/auth.ts +258 -0
  421. package/src/utils/bash-context-processor.ts +47 -0
  422. package/src/utils/bash-messages.ts +108 -0
  423. package/src/utils/block-margins.ts +35 -0
  424. package/src/utils/block-operations.ts +523 -0
  425. package/src/utils/block-processor.ts +213 -0
  426. package/src/utils/chat-history.ts +253 -0
  427. package/src/utils/chat-input-key-intercept.ts +52 -0
  428. package/src/utils/chat-meta.ts +90 -0
  429. package/src/utils/chat-scroll-accel.ts +151 -0
  430. package/src/utils/clipboard-image.ts +588 -0
  431. package/src/utils/clipboard.ts +397 -0
  432. package/src/utils/code-search-summary.ts +70 -0
  433. package/src/utils/collapse-helpers.ts +256 -0
  434. package/src/utils/config-dir.ts +24 -0
  435. package/src/utils/constants.ts +190 -0
  436. package/src/utils/create-event-handler-state.ts +73 -0
  437. package/src/utils/create-run-config.ts +121 -0
  438. package/src/utils/detect-shell.ts +116 -0
  439. package/src/utils/directory-browser.ts +72 -0
  440. package/src/utils/engagement.ts +55 -0
  441. package/src/utils/env.ts +92 -0
  442. package/src/utils/error-handling.ts +255 -0
  443. package/src/utils/error-messages.ts +62 -0
  444. package/src/utils/exit-cleanly.ts +86 -0
  445. package/src/utils/feedback-helpers.ts +103 -0
  446. package/src/utils/feedback-submission.ts +22 -0
  447. package/src/utils/fetch-usage.ts +76 -0
  448. package/src/utils/fingerprint.ts +245 -0
  449. package/src/utils/format-elapsed-time.ts +33 -0
  450. package/src/utils/format-session-units.ts +6 -0
  451. package/src/utils/format-timeout.ts +28 -0
  452. package/src/utils/format-validation-errors-for-message.ts +84 -0
  453. package/src/utils/freebuff-agent-selection.ts +33 -0
  454. package/src/utils/freebuff-instance-owner.ts +66 -0
  455. package/src/utils/freebuff-model-navigation.ts +50 -0
  456. package/src/utils/freebuff-premium-reset.ts +55 -0
  457. package/src/utils/freebuff-referral-cache.ts +51 -0
  458. package/src/utils/freebuff-session-api.ts +243 -0
  459. package/src/utils/freebuff-session-display.ts +21 -0
  460. package/src/utils/freebuff-streak-line.ts +93 -0
  461. package/src/utils/git.ts +17 -0
  462. package/src/utils/helpers.ts +47 -0
  463. package/src/utils/image-display.ts +67 -0
  464. package/src/utils/image-handler.ts +325 -0
  465. package/src/utils/image-processor.ts +133 -0
  466. package/src/utils/image-thumbnail.ts +78 -0
  467. package/src/utils/implementor-helpers.ts +787 -0
  468. package/src/utils/input-modes.ts +174 -0
  469. package/src/utils/keyboard-actions.ts +392 -0
  470. package/src/utils/keypad-keys.ts +47 -0
  471. package/src/utils/layout-helpers.ts +33 -0
  472. package/src/utils/lazy-response-ads.ts +1 -0
  473. package/src/utils/local-agent-registry.ts +474 -0
  474. package/src/utils/log-shipper.ts +126 -0
  475. package/src/utils/logger.ts +327 -0
  476. package/src/utils/markdown-renderer.tsx +1090 -0
  477. package/src/utils/math.ts +3 -0
  478. package/src/utils/message-block-helpers.ts +684 -0
  479. package/src/utils/message-history.ts +143 -0
  480. package/src/utils/message-tree-utils.ts +67 -0
  481. package/src/utils/message-updater.ts +279 -0
  482. package/src/utils/open-file.ts +146 -0
  483. package/src/utils/open-url.ts +63 -0
  484. package/src/utils/path-completion.ts +111 -0
  485. package/src/utils/path-helpers.ts +38 -0
  486. package/src/utils/pending-attachments.ts +360 -0
  487. package/src/utils/polling-backoff.ts +59 -0
  488. package/src/utils/project-picker.ts +12 -0
  489. package/src/utils/queue-panel-actions.ts +70 -0
  490. package/src/utils/recent-projects.ts +156 -0
  491. package/src/utils/renderer-cleanup.ts +233 -0
  492. package/src/utils/response-ad-positions.ts +1 -0
  493. package/src/utils/run-state-storage.ts +603 -0
  494. package/src/utils/safe-json.ts +153 -0
  495. package/src/utils/sdk-event-handlers.ts +571 -0
  496. package/src/utils/send-message-helpers.ts +210 -0
  497. package/src/utils/send-message-timer.ts +110 -0
  498. package/src/utils/settings.ts +226 -0
  499. package/src/utils/skill-registry.ts +94 -0
  500. package/src/utils/spawn-agent-matcher.ts +55 -0
  501. package/src/utils/status-indicator-state.ts +107 -0
  502. package/src/utils/stream-chunk-processor.ts +57 -0
  503. package/src/utils/strings.ts +271 -0
  504. package/src/utils/subscription.ts +31 -0
  505. package/src/utils/syntax-highlighter.tsx +19 -0
  506. package/src/utils/terminal-color-detection.ts +474 -0
  507. package/src/utils/terminal-command-broker.ts +467 -0
  508. package/src/utils/terminal-enter-detection.ts +63 -0
  509. package/src/utils/terminal-images.ts +229 -0
  510. package/src/utils/terminal-io.ts +85 -0
  511. package/src/utils/terminal-protocol-controller.ts +173 -0
  512. package/src/utils/terminal-reset-sequences.ts +33 -0
  513. package/src/utils/terminal-title.ts +84 -0
  514. package/src/utils/terminal-watchdog.ts +366 -0
  515. package/src/utils/text-layout.ts +149 -0
  516. package/src/utils/theme-config.ts +141 -0
  517. package/src/utils/theme-system.ts +1206 -0
  518. package/src/utils/think-tag-parser.ts +95 -0
  519. package/src/utils/time-format.ts +21 -0
  520. package/src/utils/trace-writer.ts +162 -0
  521. package/src/utils/ui-constants.ts +69 -0
  522. package/src/utils/update-checker.ts +110 -0
  523. package/src/utils/usage-banner-state.ts +146 -0
  524. package/src/utils/validation-error-formatting.ts +86 -0
  525. package/src/utils/validation-error-helpers.ts +19 -0
  526. package/src/utils/windows-terminal-health.ts +124 -0
  527. package/src/utils/word-wrap-utils.ts +53 -0
  528. package/src/utils/write-file-atomic.ts +54 -0
  529. package/src/utils/yield-to-event-loop.ts +9 -0
@@ -0,0 +1,73 @@
1
+ import { useMemo } from 'react'
2
+
3
+ import { computeSmartColumns, MIN_COLUMN_WIDTH } from '../utils/layout-helpers'
4
+
5
+ /**
6
+ * Terminal column width thresholds for responsive grid layout.
7
+ * These are character counts (not pixels) representing terminal width breakpoints:
8
+ * - Below 100 cols: 1 column (narrow terminal)
9
+ * - 100-149 cols: up to 2 columns (medium terminal)
10
+ * - 150-199 cols: up to 3 columns (large terminal)
11
+ * - 200+ cols: up to 4 columns (extra large terminal)
12
+ */
13
+ export const WIDTH_MD_THRESHOLD = 100
14
+ export const WIDTH_LG_THRESHOLD = 150
15
+ export const WIDTH_XL_THRESHOLD = 200
16
+
17
+ /** Ordered thresholds for determining max columns based on terminal width */
18
+ const WIDTH_THRESHOLDS = [WIDTH_MD_THRESHOLD, WIDTH_LG_THRESHOLD, WIDTH_XL_THRESHOLD] as const
19
+
20
+ export interface GridLayoutResult<T> {
21
+ columns: number
22
+ columnWidth: number
23
+ columnGroups: T[][]
24
+ }
25
+
26
+ /** Gap between columns in multi-column layout */
27
+ const COLUMN_GAP = 1
28
+
29
+ export function computeGridLayout<T>(
30
+ items: T[],
31
+ availableWidth: number,
32
+ ): GridLayoutResult<T> {
33
+ // Force single column for very narrow terminals where multi-column wouldn't fit
34
+ const minWidthForTwoColumns = MIN_COLUMN_WIDTH * 2 + COLUMN_GAP
35
+ if (availableWidth < minWidthForTwoColumns) {
36
+ return {
37
+ columns: 1,
38
+ columnWidth: Math.max(1, availableWidth),
39
+ columnGroups: [items],
40
+ }
41
+ }
42
+
43
+ // Determine max columns from width thresholds
44
+ const maxColumns = WIDTH_THRESHOLDS.filter(t => availableWidth >= t).length + 1
45
+
46
+ const columns = computeSmartColumns(items.length, maxColumns)
47
+
48
+ let columnWidth: number
49
+ if (columns === 1) {
50
+ columnWidth = availableWidth
51
+ } else {
52
+ const totalGap = columns - 1
53
+ const rawWidth = Math.floor((availableWidth - totalGap) / columns)
54
+ columnWidth = Math.max(MIN_COLUMN_WIDTH, rawWidth)
55
+ }
56
+
57
+ const columnGroups: T[][] = Array.from({ length: columns }, () => [])
58
+ items.forEach((item, idx) => {
59
+ columnGroups[idx % columns].push(item)
60
+ })
61
+
62
+ return { columns, columnWidth, columnGroups }
63
+ }
64
+
65
+ export function useGridLayout<T>(
66
+ items: T[],
67
+ availableWidth: number,
68
+ ): GridLayoutResult<T> {
69
+ return useMemo(
70
+ () => computeGridLayout(items, availableWidth),
71
+ [items, availableWidth],
72
+ )
73
+ }
@@ -0,0 +1,173 @@
1
+ import { useRef, useCallback, useEffect } from 'react'
2
+
3
+ import {
4
+ loadMessageHistory,
5
+ saveMessageHistory,
6
+ } from '../utils/message-history'
7
+
8
+ import type { InputValue } from '../types/store'
9
+ import type { InputMode } from '../utils/input-modes'
10
+
11
+ /**
12
+ * Determine the appropriate input mode and display text for a history item.
13
+ * Bash commands are stored with '!' prefix, so we detect that and return
14
+ * the appropriate mode and text to display.
15
+ */
16
+ function parseHistoryItem(item: string): {
17
+ mode: InputMode
18
+ displayText: string
19
+ } {
20
+ if (item.startsWith('!') && item.length > 1) {
21
+ // It's a bash command - strip the '!' prefix for display
22
+ return { mode: 'bash', displayText: item.slice(1) }
23
+ }
24
+ // Regular prompt
25
+ return { mode: 'default', displayText: item }
26
+ }
27
+
28
+ export const useInputHistory = (
29
+ inputValue: string,
30
+ setInputValue: (value: InputValue) => void,
31
+ options?: {
32
+ inputMode?: InputMode
33
+ setInputMode?: (mode: InputMode) => void
34
+ },
35
+ ) => {
36
+ const { inputMode, setInputMode } = options ?? {}
37
+ const messageHistoryRef = useRef<string[]>([])
38
+ const historyIndexRef = useRef<number>(-1)
39
+ const currentDraftRef = useRef<string>('')
40
+ const currentDraftModeRef = useRef<InputMode>('default')
41
+ const isInitializedRef = useRef<boolean>(false)
42
+ const isNavigatingRef = useRef<boolean>(false)
43
+
44
+ // Load history from disk on mount
45
+ useEffect(() => {
46
+ if (!isInitializedRef.current) {
47
+ isInitializedRef.current = true
48
+ const savedHistory = loadMessageHistory()
49
+ messageHistoryRef.current = savedHistory
50
+ }
51
+ }, [])
52
+
53
+ const resetHistoryNavigation = useCallback(() => {
54
+ historyIndexRef.current = -1
55
+ currentDraftRef.current = ''
56
+ currentDraftModeRef.current = 'default'
57
+ }, [])
58
+
59
+ useEffect(() => {
60
+ if (!isNavigatingRef.current) {
61
+ resetHistoryNavigation()
62
+ }
63
+ }, [inputMode, resetHistoryNavigation])
64
+
65
+ const saveToHistory = useCallback((message: string) => {
66
+ // Re-read from disk to pick up messages from other terminals
67
+ const diskHistory = loadMessageHistory()
68
+ const newHistory = [...diskHistory, message]
69
+ messageHistoryRef.current = newHistory
70
+ historyIndexRef.current = -1
71
+ currentDraftRef.current = ''
72
+ currentDraftModeRef.current = 'default'
73
+
74
+ // Persist to disk
75
+ saveMessageHistory(newHistory)
76
+ }, [])
77
+
78
+ const navigateUp = useCallback(() => {
79
+ const history = messageHistoryRef.current
80
+ if (history.length === 0) return
81
+
82
+ isNavigatingRef.current = true
83
+
84
+ if (historyIndexRef.current === -1) {
85
+ // Save current draft and mode before navigating
86
+ currentDraftRef.current =
87
+ inputMode === 'bash' ? '!' + inputValue : inputValue
88
+ currentDraftModeRef.current = inputMode ?? 'default'
89
+ historyIndexRef.current = history.length - 1
90
+ } else if (historyIndexRef.current > 0) {
91
+ historyIndexRef.current -= 1
92
+ }
93
+
94
+ const historyMessage = history[historyIndexRef.current]
95
+ if (historyMessage === undefined) {
96
+ isNavigatingRef.current = false
97
+ return
98
+ }
99
+
100
+ const { mode, displayText } = parseHistoryItem(historyMessage)
101
+
102
+ // Switch mode if needed
103
+ if (setInputMode && mode !== inputMode) {
104
+ setInputMode(mode)
105
+ }
106
+
107
+ setInputValue({
108
+ text: displayText,
109
+ cursorPosition: displayText.length,
110
+ lastEditDueToNav: true,
111
+ })
112
+
113
+ setTimeout(() => {
114
+ isNavigatingRef.current = false
115
+ }, 0)
116
+ }, [inputValue, inputMode, setInputValue, setInputMode])
117
+
118
+ const navigateDown = useCallback(() => {
119
+ const history = messageHistoryRef.current
120
+ if (history.length === 0) return
121
+ if (historyIndexRef.current === -1) return
122
+
123
+ isNavigatingRef.current = true
124
+
125
+ if (historyIndexRef.current < history.length - 1) {
126
+ historyIndexRef.current += 1
127
+ const historyMessage = history[historyIndexRef.current]
128
+ if (historyMessage === undefined) {
129
+ isNavigatingRef.current = false
130
+ return
131
+ }
132
+
133
+ const { mode, displayText } = parseHistoryItem(historyMessage)
134
+
135
+ // Switch mode if needed
136
+ if (setInputMode && mode !== inputMode) {
137
+ setInputMode(mode)
138
+ }
139
+
140
+ setInputValue({
141
+ text: displayText,
142
+ cursorPosition: displayText.length,
143
+ lastEditDueToNav: true,
144
+ })
145
+ } else {
146
+ // Return to draft
147
+ historyIndexRef.current = -1
148
+ const draft = currentDraftRef.current
149
+ const draftMode = currentDraftModeRef.current
150
+
151
+ // Restore the mode we were in when we started navigating
152
+ if (setInputMode && draftMode !== inputMode) {
153
+ setInputMode(draftMode)
154
+ }
155
+
156
+ // If draft was in bash mode, it was stored with '!' prefix, so strip it
157
+ const textToShow =
158
+ draftMode === 'bash' && draft.startsWith('!') ? draft.slice(1) : draft
159
+
160
+ setInputValue({
161
+ text: textToShow,
162
+ cursorPosition: textToShow.length,
163
+ lastEditDueToNav: true,
164
+ })
165
+ }
166
+
167
+ setTimeout(() => {
168
+ isNavigatingRef.current = false
169
+ }, 0)
170
+ }, [inputMode, setInputValue, setInputMode])
171
+
172
+ return { saveToHistory, navigateUp, navigateDown, resetHistoryNavigation }
173
+ }
@@ -0,0 +1,75 @@
1
+ import { useKeyboard } from '@opentui/react'
2
+ import { useCallback } from 'react'
3
+
4
+ import { exitCliCleanly } from '../utils/exit-cleanly'
5
+ import { isPlainEnterKey } from '../utils/terminal-enter-detection'
6
+
7
+ import type { KeyEvent } from '@opentui/core'
8
+
9
+ interface UseLoginKeyboardHandlersParams {
10
+ loginUrl: string | null
11
+ hasOpenedBrowser: boolean
12
+ loading: boolean
13
+ onFetchLoginUrl: () => void
14
+ onCopyUrl: (url: string) => Promise<void> | void
15
+ }
16
+
17
+ /**
18
+ * Custom hook that handles keyboard input for the login modal
19
+ * - Enter key: fetch login URL and open browser
20
+ * - 'c' key: copy URL to clipboard
21
+ * - Ctrl+C: exit the application
22
+ */
23
+ export function useLoginKeyboardHandlers({
24
+ loginUrl,
25
+ hasOpenedBrowser,
26
+ loading,
27
+ onFetchLoginUrl,
28
+ onCopyUrl,
29
+ }: UseLoginKeyboardHandlersParams) {
30
+ useKeyboard(
31
+ useCallback(
32
+ (key: KeyEvent) => {
33
+ const isEnter = isPlainEnterKey(key)
34
+
35
+ const isCKey = key.name === 'c' && !key.ctrl && !key.meta && !key.shift
36
+ const isCtrlC = key.ctrl && key.name === 'c'
37
+
38
+ if (isCtrlC) {
39
+ if (
40
+ 'preventDefault' in key &&
41
+ typeof key.preventDefault === 'function'
42
+ ) {
43
+ key.preventDefault()
44
+ }
45
+ void exitCliCleanly()
46
+ }
47
+
48
+ if (isEnter && !hasOpenedBrowser && !loading) {
49
+ if (
50
+ 'preventDefault' in key &&
51
+ typeof key.preventDefault === 'function'
52
+ ) {
53
+ key.preventDefault()
54
+ }
55
+
56
+ onFetchLoginUrl()
57
+ }
58
+
59
+ if (isCKey && loginUrl && hasOpenedBrowser) {
60
+ if (
61
+ 'preventDefault' in key &&
62
+ typeof key.preventDefault === 'function'
63
+ ) {
64
+ key.preventDefault()
65
+ }
66
+
67
+ // Fire-and-forget the async copy function with .catch() to prevent
68
+ // unhandled promise rejections if the implementation changes
69
+ void Promise.resolve(onCopyUrl(loginUrl)).catch(() => {})
70
+ }
71
+ },
72
+ [loginUrl, hasOpenedBrowser, loading, onCopyUrl, onFetchLoginUrl],
73
+ ),
74
+ )
75
+ }
@@ -0,0 +1,114 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ import { LOGIN_WEBSITE_URL } from '../login/constants'
4
+ import { pollLoginStatus } from '../login/login-flow'
5
+ import { trackEvent } from '../utils/analytics'
6
+ import { logger } from '../utils/logger'
7
+
8
+ import type { User } from '../utils/auth'
9
+
10
+ interface UseLoginPollingParams {
11
+ loginUrl: string | null
12
+ fingerprintId: string | null
13
+ fingerprintHash: string | null
14
+ expiresAt: string | null
15
+ isWaitingForEnter: boolean
16
+ onSuccess: (user: User) => void
17
+ onTimeout: () => void
18
+ onError: (error: string) => void
19
+ }
20
+
21
+ /**
22
+ * Custom hook that handles polling for login status
23
+ * Extracts the 109-line polling effect from login-modal.tsx
24
+ */
25
+ export function useLoginPolling({
26
+ loginUrl,
27
+ fingerprintId,
28
+ fingerprintHash,
29
+ expiresAt,
30
+ isWaitingForEnter,
31
+ onSuccess,
32
+ onTimeout,
33
+ onError,
34
+ }: UseLoginPollingParams) {
35
+ // Store callbacks in refs to prevent effect re-runs
36
+ const onSuccessRef = useRef(onSuccess)
37
+ const onTimeoutRef = useRef(onTimeout)
38
+ const onErrorRef = useRef(onError)
39
+
40
+ useEffect(() => {
41
+ onSuccessRef.current = onSuccess
42
+ }, [onSuccess])
43
+
44
+ useEffect(() => {
45
+ onTimeoutRef.current = onTimeout
46
+ }, [onTimeout])
47
+
48
+ useEffect(() => {
49
+ onErrorRef.current = onError
50
+ }, [onError])
51
+
52
+ useEffect(() => {
53
+ // fingerprintHash only becomes non-null after the login-URL mutation
54
+ // succeeds, and that path always sets fingerprintId first — so gating
55
+ // on fingerprintHash implicitly gates on fingerprintId.
56
+ if (!loginUrl || !fingerprintId || !fingerprintHash || !expiresAt || !isWaitingForEnter) {
57
+ return
58
+ }
59
+
60
+ let active = true
61
+
62
+ const sleep = (ms: number) =>
63
+ new Promise<void>((resolve) => {
64
+ setTimeout(resolve, ms)
65
+ })
66
+
67
+ pollLoginStatus(
68
+ {
69
+ sleep,
70
+ logger,
71
+ trackEvent,
72
+ },
73
+ {
74
+ baseUrl: LOGIN_WEBSITE_URL,
75
+ fingerprintId: fingerprintId!,
76
+ fingerprintHash,
77
+ expiresAt,
78
+ shouldContinue: () => active,
79
+ via: 'modal',
80
+ },
81
+ )
82
+ .then((result) => {
83
+ if (!active) {
84
+ return
85
+ }
86
+
87
+ if (result.status === 'success') {
88
+ const user = result.user as User
89
+ onSuccessRef.current(user)
90
+ } else if (result.status === 'timeout') {
91
+ logger.warn('Login polling timed out after configured limit')
92
+ onTimeoutRef.current()
93
+ }
94
+ })
95
+ .catch((error) => {
96
+ if (!active) {
97
+ return
98
+ }
99
+ logger.error(
100
+ {
101
+ error: error instanceof Error ? error.message : String(error),
102
+ },
103
+ '💥 Unexpected error while polling login status',
104
+ )
105
+ onErrorRef.current(
106
+ error instanceof Error ? error.message : 'Failed to complete login',
107
+ )
108
+ })
109
+
110
+ return () => {
111
+ active = false
112
+ }
113
+ }, [loginUrl, fingerprintHash, expiresAt, isWaitingForEnter, fingerprintId])
114
+ }
@@ -0,0 +1,161 @@
1
+ import React, { useMemo } from 'react'
2
+
3
+ import { LOGO, LOGO_SMALL, SHADOW_CHARS } from '../login/constants'
4
+ import { parseLogoLines } from '../login/utils'
5
+ import { IS_FREEBUFF } from '../utils/constants'
6
+
7
+ interface UseLogoOptions {
8
+ /**
9
+ * Available width for rendering the logo
10
+ */
11
+ availableWidth: number
12
+ /**
13
+ * Optional function to apply styling to each character (e.g., for sheen animation)
14
+ * If not provided, default coloring is applied (white blocks, accent shadows)
15
+ */
16
+ applySheenToChar?: (char: string, charIndex: number, lineIndex: number) => React.ReactNode
17
+ /**
18
+ * Color to apply to the text variant
19
+ */
20
+ textColor?: string
21
+ /**
22
+ * Accent color for shadow/border characters (defaults to acid green #9EFC62)
23
+ */
24
+ accentColor?: string
25
+ /**
26
+ * Block color for solid block characters (white for dark mode, black for light mode)
27
+ */
28
+ blockColor?: string
29
+ /**
30
+ * Optional vertical budget (in rows) for the logo. When fewer than the
31
+ * ASCII art's 6 lines are available, the hook downgrades to the single-line
32
+ * text variant so callers on short terminals don't have to special-case it.
33
+ */
34
+ maxHeight?: number
35
+ }
36
+
37
+ interface LogoResult {
38
+ /**
39
+ * The formatted logo as a React component ready to render in UI
40
+ */
41
+ component: React.ReactNode
42
+ /**
43
+ * The formatted logo string for plain text contexts (e.g., chat messages)
44
+ * Empty string for narrow widths, formatted ASCII art otherwise
45
+ */
46
+ textBlock: string
47
+ }
48
+
49
+ /**
50
+ * Hook to render a logo based on available width
51
+ * Returns a fully formatted React component and text block that "just work"
52
+ *
53
+ * Returns:
54
+ * - Full ASCII logo for width >= 70
55
+ * - Small ASCII logo for width >= 40
56
+ * - Text variant "ANVIL" or "Anvil CLI" for narrow widths
57
+ *
58
+ * The hook handles ALL formatting internally including:
59
+ * - Line parsing and width limiting
60
+ * - Optional character-level styling (sheen animation) for React component
61
+ * - Text wrapping and block formatting for plain text contexts
62
+ * - No consumer needs to know about parseLogoLines, split, join, etc.
63
+ */
64
+ export const useLogo = ({
65
+ availableWidth,
66
+ applySheenToChar,
67
+ textColor,
68
+ accentColor = '#9EFC62',
69
+ blockColor = '#ffffff',
70
+ maxHeight,
71
+ }: UseLogoOptions): LogoResult => {
72
+ // The ASCII art (full and small) is 6 lines tall. If the caller can't spare
73
+ // that many rows, collapse straight to the single-line text variant.
74
+ const ASCII_LOGO_LINES = 6
75
+ const rawLogoString = useMemo(() => {
76
+ if (maxHeight != null && maxHeight < ASCII_LOGO_LINES) {
77
+ return IS_FREEBUFF ? 'FREEBUFF' : 'ANVIL'
78
+ }
79
+ if (availableWidth >= 70) return LOGO
80
+ if (availableWidth >= 20) return LOGO_SMALL
81
+ return IS_FREEBUFF ? 'FREEBUFF' : 'ANVIL'
82
+ }, [availableWidth, maxHeight])
83
+
84
+ // Format text block for plain text contexts (chat messages, etc.)
85
+ const textBlock = useMemo(() => {
86
+ if (rawLogoString === 'ANVIL' || rawLogoString === 'FREEBUFF') {
87
+ return '' // Don't show ASCII art for text-only variant in plain text contexts
88
+ }
89
+ // Parse and format for plain text display
90
+ return parseLogoLines(rawLogoString)
91
+ .map((line) => line.slice(0, availableWidth))
92
+ .join('\n')
93
+ }, [rawLogoString, availableWidth])
94
+
95
+ // Format component for React contexts (login modal, etc.)
96
+ const component = useMemo(() => {
97
+ // Text-only variant for very narrow widths
98
+ if (rawLogoString === 'ANVIL' || rawLogoString === 'FREEBUFF') {
99
+ const brandName = IS_FREEBUFF ? 'Freebuff' : 'Anvil'
100
+ // When we collapsed to text purely to fit a short terminal (not because
101
+ // the terminal is narrow), keep it to the bare brand name — "Anvil CLI"
102
+ // reads as filler in that already-cramped space.
103
+ const forcedByHeight = maxHeight != null && maxHeight < ASCII_LOGO_LINES
104
+ const displayText =
105
+ availableWidth < 30 || forcedByHeight
106
+ ? brandName
107
+ : `${brandName} CLI`
108
+
109
+ return (
110
+ <text style={{ wrapMode: 'none' }}>
111
+ <b>
112
+ {textColor ? (
113
+ <span fg={textColor}>{displayText}</span>
114
+ ) : (
115
+ <>{displayText}</>
116
+ )}
117
+ </b>
118
+ </text>
119
+ )
120
+ }
121
+
122
+ // ASCII art variant
123
+ const logoLines = parseLogoLines(rawLogoString)
124
+ const displayLines = logoLines.map((line) => line.slice(0, availableWidth))
125
+
126
+ // Default coloring function: blockColor for blocks, accent color for shadows
127
+ const defaultColorChar = (char: string, charIndex: number) => {
128
+ if (char === ' ' || char === '\n') {
129
+ return <span key={charIndex}>{char}</span>
130
+ }
131
+ // Block characters use blockColor (white in dark mode, black in light mode)
132
+ if (char === '█') {
133
+ return <span key={charIndex} fg={blockColor}>{char}</span>
134
+ }
135
+ // Shadow/border characters get accent color
136
+ if (SHADOW_CHARS.has(char)) {
137
+ return <span key={charIndex} fg={accentColor}>{char}</span>
138
+ }
139
+ // Other characters use accent color
140
+ return <span key={charIndex} fg={accentColor}>{char}</span>
141
+ }
142
+
143
+ return (
144
+ <>
145
+ {displayLines.map((line, lineIndex) => (
146
+ <text key={`logo-line-${lineIndex}`} style={{ wrapMode: 'none' }}>
147
+ {line
148
+ .split('')
149
+ .map((char, charIndex) =>
150
+ applySheenToChar
151
+ ? applySheenToChar(char, charIndex, lineIndex)
152
+ : defaultColorChar(char, charIndex),
153
+ )}
154
+ </text>
155
+ ))}
156
+ </>
157
+ )
158
+ }, [rawLogoString, availableWidth, applySheenToChar, textColor, accentColor, blockColor, maxHeight])
159
+
160
+ return { component, textBlock }
161
+ }