@alumbwe/anvil 1.0.0 → 1.0.2

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 (530) hide show
  1. package/bin/anvil.js +2 -0
  2. package/dist/bundled.js +79559 -0
  3. package/dist/bundled.js.map +581 -0
  4. package/package.json +7 -4
  5. package/bin/anvil +0 -2
  6. package/src/__tests__/README.md +0 -256
  7. package/src/__tests__/bash-mode.test.ts +0 -442
  8. package/src/__tests__/cli-args.test.ts +0 -181
  9. package/src/__tests__/cli-harness.test.ts +0 -52
  10. package/src/__tests__/e2e/first-time-login.test.ts +0 -173
  11. package/src/__tests__/e2e/logout-relogin-flow.test.ts +0 -110
  12. package/src/__tests__/e2e/returning-user-auth.test.ts +0 -113
  13. package/src/__tests__/e2e-cli.test.ts +0 -183
  14. package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +0 -176
  15. package/src/__tests__/helpers/mock-api-client.ts +0 -64
  16. package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +0 -89
  17. package/src/__tests__/helpers/terminal-watchdog-fixture.ts +0 -75
  18. package/src/__tests__/home-directory-detection.test.ts +0 -213
  19. package/src/__tests__/integration/api-integration.test.ts +0 -288
  20. package/src/__tests__/integration/credentials-storage.test.ts +0 -486
  21. package/src/__tests__/integration/local-agents.test.ts +0 -1187
  22. package/src/__tests__/integration/login-flow-instrumentation.test.ts +0 -134
  23. package/src/__tests__/integration/login-polling-working.test.ts +0 -285
  24. package/src/__tests__/integration/queue-editing.test.tsx +0 -261
  25. package/src/__tests__/integration/usage-refresh-on-completion.test.ts +0 -168
  26. package/src/__tests__/integration-tmux.test.ts +0 -188
  27. package/src/__tests__/launcher-avx2-fallback.test.ts +0 -630
  28. package/src/__tests__/launcher-disconnect.test.ts +0 -104
  29. package/src/__tests__/mocks/hover-toggle-controller.ts +0 -71
  30. package/src/__tests__/open-tui-native-bundle.test.ts +0 -378
  31. package/src/__tests__/path-completion.test.ts +0 -243
  32. package/src/__tests__/project-files-chat-id.test.ts +0 -34
  33. package/src/__tests__/release/proxy-http-get.test.ts +0 -667
  34. package/src/__tests__/release/wrapper-safety.test.ts +0 -412
  35. package/src/__tests__/renderer-cleanup.test.ts +0 -174
  36. package/src/__tests__/rerender-perf.integration.test.ts +0 -283
  37. package/src/__tests__/terminal-reset-sequences.test.ts +0 -36
  38. package/src/__tests__/terminal-watchdog.test.ts +0 -388
  39. package/src/__tests__/test-utils.ts +0 -271
  40. package/src/__tests__/tmux-poc.ts +0 -150
  41. package/src/__tests__/unit/agent-mode-toggle.test.ts +0 -137
  42. package/src/__tests__/unit/copy-button.test.ts +0 -157
  43. package/src/__tests__/unit/create-run-config.test.ts +0 -66
  44. package/src/__tests__/unit/publish-confirmation.test.ts +0 -70
  45. package/src/__tests__/unit/segmented-control.test.ts +0 -152
  46. package/src/__tests__/utils/env.test.ts +0 -169
  47. package/src/__tests__/utils/project-picker.test.ts +0 -39
  48. package/src/agents/bundled-agents.generated.d.ts +0 -14
  49. package/src/agents/bundled-agents.generated.ts +0 -3692
  50. package/src/app.tsx +0 -399
  51. package/src/chat.tsx +0 -1698
  52. package/src/cli-args.ts +0 -134
  53. package/src/commands/__tests__/bash-command.test.ts +0 -445
  54. package/src/commands/__tests__/command-args.test.ts +0 -349
  55. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +0 -108
  56. package/src/commands/__tests__/copy-conversation.test.ts +0 -226
  57. package/src/commands/__tests__/diagnostics-command.test.ts +0 -35
  58. package/src/commands/__tests__/freebuff-command-aliases.test.ts +0 -56
  59. package/src/commands/__tests__/image.test.ts +0 -99
  60. package/src/commands/__tests__/init.test.ts +0 -456
  61. package/src/commands/__tests__/new-command-rotates-chat.test.ts +0 -91
  62. package/src/commands/__tests__/process-diagnostics.test.ts +0 -54
  63. package/src/commands/__tests__/prompt-builders.test.ts +0 -28
  64. package/src/commands/__tests__/router-input.test.ts +0 -298
  65. package/src/commands/ads.ts +0 -44
  66. package/src/commands/command-registry.ts +0 -726
  67. package/src/commands/copy-conversation.ts +0 -392
  68. package/src/commands/help.ts +0 -13
  69. package/src/commands/image.ts +0 -20
  70. package/src/commands/init.ts +0 -121
  71. package/src/commands/process-diagnostics.ts +0 -120
  72. package/src/commands/prompt-builders.ts +0 -100
  73. package/src/commands/publish.ts +0 -203
  74. package/src/commands/router-utils.ts +0 -80
  75. package/src/commands/router.ts +0 -487
  76. package/src/commands/usage.ts +0 -26
  77. package/src/components/__tests__/ad-banner.test.tsx +0 -83
  78. package/src/components/__tests__/chat-input-bar.test.tsx +0 -130
  79. package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +0 -91
  80. package/src/components/__tests__/freebuff-model-selector.test.tsx +0 -519
  81. package/src/components/__tests__/freebuff-offer-invariants.test.ts +0 -54
  82. package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +0 -212
  83. package/src/components/__tests__/grid-layout.integration.test.tsx +0 -304
  84. package/src/components/__tests__/grid-layout.test.tsx +0 -1051
  85. package/src/components/__tests__/message-block.completion.test.tsx +0 -104
  86. package/src/components/__tests__/message-block.streaming.test.tsx +0 -78
  87. package/src/components/__tests__/message-with-agents.test.tsx +0 -579
  88. package/src/components/__tests__/multiline-input.test.tsx +0 -1132
  89. package/src/components/__tests__/queue-panel.test.tsx +0 -398
  90. package/src/components/__tests__/selectable-list.test.ts +0 -232
  91. package/src/components/__tests__/status-bar.test.tsx +0 -44
  92. package/src/components/__tests__/status-indicator.test.tsx +0 -204
  93. package/src/components/__tests__/terminal-thinking-orb.test.tsx +0 -111
  94. package/src/components/__tests__/user-error-banner.test.tsx +0 -102
  95. package/src/components/ad-banner.tsx +0 -323
  96. package/src/components/agent-checklist.tsx +0 -350
  97. package/src/components/agent-mode-toggle.tsx +0 -241
  98. package/src/components/anvil-mark.tsx +0 -52
  99. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +0 -432
  100. package/src/components/ask-user/__tests__/validation.test.ts +0 -213
  101. package/src/components/ask-user/components/accordion-question.tsx +0 -153
  102. package/src/components/ask-user/components/custom-answer-input.tsx +0 -66
  103. package/src/components/ask-user/components/options-list.tsx +0 -133
  104. package/src/components/ask-user/components/question-header.tsx +0 -80
  105. package/src/components/ask-user/components/question-option.tsx +0 -89
  106. package/src/components/ask-user/constants.ts +0 -38
  107. package/src/components/ask-user/index.tsx +0 -631
  108. package/src/components/ask-user/utils/validation.ts +0 -85
  109. package/src/components/attachment-card.tsx +0 -65
  110. package/src/components/blocks/agent-block-grid.tsx +0 -58
  111. package/src/components/blocks/agent-branch-item.tsx +0 -311
  112. package/src/components/blocks/agent-branch-wrapper.tsx +0 -493
  113. package/src/components/blocks/agent-list-branch.tsx +0 -77
  114. package/src/components/blocks/ask-user-branch.tsx +0 -81
  115. package/src/components/blocks/block-helpers.ts +0 -11
  116. package/src/components/blocks/blocks-renderer.tsx +0 -289
  117. package/src/components/blocks/content-with-markdown.tsx +0 -33
  118. package/src/components/blocks/image-block.tsx +0 -128
  119. package/src/components/blocks/implementor-row.tsx +0 -429
  120. package/src/components/blocks/single-block.tsx +0 -201
  121. package/src/components/blocks/thinking-block.tsx +0 -67
  122. package/src/components/blocks/tool-block-group.tsx +0 -57
  123. package/src/components/blocks/tool-branch.tsx +0 -206
  124. package/src/components/blocks/user-content-copy.tsx +0 -157
  125. package/src/components/bottom-banner.tsx +0 -136
  126. package/src/components/build-mode-buttons.tsx +0 -110
  127. package/src/components/button.tsx +0 -66
  128. package/src/components/chat-header.tsx +0 -77
  129. package/src/components/chat-history-screen.tsx +0 -413
  130. package/src/components/chat-input-bar.tsx +0 -523
  131. package/src/components/clickable-title-box.tsx +0 -66
  132. package/src/components/clickable.tsx +0 -119
  133. package/src/components/collapse-button.tsx +0 -34
  134. package/src/components/copy-button.tsx +0 -203
  135. package/src/components/elapsed-timer.tsx +0 -55
  136. package/src/components/error-boundary.tsx +0 -55
  137. package/src/components/feedback-container.tsx +0 -181
  138. package/src/components/feedback-icon-button.tsx +0 -89
  139. package/src/components/feedback-input-mode.tsx +0 -344
  140. package/src/components/file-attachment-card.tsx +0 -98
  141. package/src/components/freebuff-active-session-summary.tsx +0 -63
  142. package/src/components/freebuff-landing-screen.tsx +0 -865
  143. package/src/components/freebuff-model-selector.tsx +0 -1087
  144. package/src/components/freebuff-referral-banner.tsx +0 -604
  145. package/src/components/freebuff-superseded-screen.tsx +0 -62
  146. package/src/components/grid-layout.tsx +0 -80
  147. package/src/components/help-banner.tsx +0 -127
  148. package/src/components/highlighted-text.tsx +0 -52
  149. package/src/components/image-card.tsx +0 -167
  150. package/src/components/image-thumbnail.tsx +0 -114
  151. package/src/components/input-cursor.tsx +0 -77
  152. package/src/components/input-mode-banner.tsx +0 -57
  153. package/src/components/load-previous-button.tsx +0 -48
  154. package/src/components/login-modal.tsx +0 -475
  155. package/src/components/message-block.tsx +0 -346
  156. package/src/components/message-footer.tsx +0 -258
  157. package/src/components/message-with-agents.tsx +0 -506
  158. package/src/components/mode-divider.tsx +0 -43
  159. package/src/components/multiline-input.tsx +0 -1241
  160. package/src/components/out-of-credits-banner.tsx +0 -160
  161. package/src/components/pending-attachments-banner.tsx +0 -116
  162. package/src/components/pending-bash-message.tsx +0 -59
  163. package/src/components/progress-bar.tsx +0 -81
  164. package/src/components/project-picker-screen.tsx +0 -485
  165. package/src/components/publish-confirmation.tsx +0 -493
  166. package/src/components/publish-container.tsx +0 -687
  167. package/src/components/queue-panel.tsx +0 -310
  168. package/src/components/raised-pill.tsx +0 -95
  169. package/src/components/renderers/plan-box.tsx +0 -59
  170. package/src/components/review-screen.tsx +0 -112
  171. package/src/components/scroll-to-bottom-button.tsx +0 -34
  172. package/src/components/segmented-control.tsx +0 -187
  173. package/src/components/selectable-list.tsx +0 -248
  174. package/src/components/selected-chips.tsx +0 -80
  175. package/src/components/separator.tsx +0 -40
  176. package/src/components/session-ended-banner.tsx +0 -226
  177. package/src/components/shimmer-text.tsx +0 -248
  178. package/src/components/status-bar.tsx +0 -313
  179. package/src/components/subscription-limit-banner.tsx +0 -206
  180. package/src/components/suggested-prompts.tsx +0 -184
  181. package/src/components/suggestion-menu.tsx +0 -215
  182. package/src/components/terminal-command-display.tsx +0 -151
  183. package/src/components/terminal-link.tsx +0 -97
  184. package/src/components/terminal-thinking-orb.tsx +0 -95
  185. package/src/components/text-attachment-card.tsx +0 -77
  186. package/src/components/thinking.tsx +0 -109
  187. package/src/components/tools/__tests__/apply-patch.test.tsx +0 -84
  188. package/src/components/tools/__tests__/code-search.test.tsx +0 -45
  189. package/src/components/tools/__tests__/gravity-index.test.ts +0 -76
  190. package/src/components/tools/__tests__/render-ui.test.tsx +0 -92
  191. package/src/components/tools/__tests__/run-terminal-command.test.ts +0 -233
  192. package/src/components/tools/apply-patch.tsx +0 -95
  193. package/src/components/tools/code-search.tsx +0 -45
  194. package/src/components/tools/composio.tsx +0 -152
  195. package/src/components/tools/diff-viewer.tsx +0 -76
  196. package/src/components/tools/glob.tsx +0 -61
  197. package/src/components/tools/gravity-index.tsx +0 -77
  198. package/src/components/tools/list-directory.tsx +0 -61
  199. package/src/components/tools/read-docs.tsx +0 -37
  200. package/src/components/tools/read-files.tsx +0 -91
  201. package/src/components/tools/read-subtree.tsx +0 -44
  202. package/src/components/tools/read-url.tsx +0 -33
  203. package/src/components/tools/registry.ts +0 -126
  204. package/src/components/tools/render-ui.tsx +0 -122
  205. package/src/components/tools/run-terminal-command.tsx +0 -79
  206. package/src/components/tools/skill.tsx +0 -29
  207. package/src/components/tools/str-replace.tsx +0 -77
  208. package/src/components/tools/suggest-followups.tsx +0 -369
  209. package/src/components/tools/task-completed.tsx +0 -16
  210. package/src/components/tools/tool-call-item.tsx +0 -273
  211. package/src/components/tools/types.ts +0 -55
  212. package/src/components/tools/web-search.tsx +0 -33
  213. package/src/components/tools/write-file.tsx +0 -16
  214. package/src/components/tools/write-todos.tsx +0 -95
  215. package/src/components/top-banner.tsx +0 -178
  216. package/src/components/usage-banner.tsx +0 -244
  217. package/src/components/user-error-banner.tsx +0 -56
  218. package/src/components/validation-error-popover.tsx +0 -192
  219. package/src/contexts/__tests__/chat-runtime-context.test.tsx +0 -149
  220. package/src/contexts/chat-runtime-context.tsx +0 -240
  221. package/src/data/slash-commands.test.ts +0 -24
  222. package/src/data/slash-commands.ts +0 -249
  223. package/src/entry.ts +0 -12
  224. package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +0 -49
  225. package/src/hooks/__tests__/session-fetch-signal.test.ts +0 -63
  226. package/src/hooks/__tests__/use-activity-query.test.ts +0 -1000
  227. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +0 -176
  228. package/src/hooks/__tests__/use-auth-query.test.ts +0 -21
  229. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +0 -66
  230. package/src/hooks/__tests__/use-clipboard.test.tsx +0 -73
  231. package/src/hooks/__tests__/use-connection-status.test.ts +0 -112
  232. package/src/hooks/__tests__/use-directory-browser.test.ts +0 -203
  233. package/src/hooks/__tests__/use-gravity-ad.test.ts +0 -73
  234. package/src/hooks/__tests__/use-grid-layout.test.ts +0 -349
  235. package/src/hooks/__tests__/use-input-history.test.ts +0 -699
  236. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +0 -209
  237. package/src/hooks/__tests__/use-message-queue.test.tsx +0 -71
  238. package/src/hooks/__tests__/use-path-tab-completion.test.ts +0 -315
  239. package/src/hooks/__tests__/use-queue-controls.test.ts +0 -68
  240. package/src/hooks/__tests__/use-queue-ui.test.tsx +0 -83
  241. package/src/hooks/__tests__/use-searchable-list.test.ts +0 -359
  242. package/src/hooks/__tests__/use-send-message-timer.test.ts +0 -146
  243. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +0 -361
  244. package/src/hooks/__tests__/use-suggestion-engine.test.ts +0 -485
  245. package/src/hooks/__tests__/use-terminal-focus.test.ts +0 -66
  246. package/src/hooks/__tests__/use-terminal-layout.test.ts +0 -675
  247. package/src/hooks/__tests__/use-timeout.test.ts +0 -330
  248. package/src/hooks/__tests__/use-usage-query.test.ts +0 -502
  249. package/src/hooks/__tests__/use-user-details-query.test.ts +0 -195
  250. package/src/hooks/helpers/__tests__/send-message.test.ts +0 -1918
  251. package/src/hooks/helpers/send-message.ts +0 -654
  252. package/src/hooks/stream-state.ts +0 -83
  253. package/src/hooks/use-activity-query.ts +0 -668
  254. package/src/hooks/use-agent-validation.ts +0 -74
  255. package/src/hooks/use-ask-user-bridge.ts +0 -92
  256. package/src/hooks/use-auth-query.ts +0 -253
  257. package/src/hooks/use-auth-state.ts +0 -152
  258. package/src/hooks/use-chat-input.ts +0 -105
  259. package/src/hooks/use-chat-keyboard.ts +0 -321
  260. package/src/hooks/use-chat-messages.ts +0 -254
  261. package/src/hooks/use-chat-state.ts +0 -165
  262. package/src/hooks/use-chat-streaming.ts +0 -180
  263. package/src/hooks/use-chat-ui.ts +0 -131
  264. package/src/hooks/use-clipboard.ts +0 -151
  265. package/src/hooks/use-connection-status.ts +0 -142
  266. package/src/hooks/use-directory-browser.ts +0 -88
  267. package/src/hooks/use-elapsed-time.ts +0 -116
  268. package/src/hooks/use-event.ts +0 -34
  269. package/src/hooks/use-exit-handler.ts +0 -66
  270. package/src/hooks/use-fetch-login-url.ts +0 -65
  271. package/src/hooks/use-fingerprint.ts +0 -61
  272. package/src/hooks/use-freebuff-ctrl-c-exit.ts +0 -23
  273. package/src/hooks/use-freebuff-session-progress.ts +0 -34
  274. package/src/hooks/use-freebuff-session.ts +0 -806
  275. package/src/hooks/use-freebuff-streak-query.ts +0 -61
  276. package/src/hooks/use-gravity-ad.ts +0 -656
  277. package/src/hooks/use-grid-layout.ts +0 -73
  278. package/src/hooks/use-input-history.ts +0 -173
  279. package/src/hooks/use-login-keyboard-handlers.ts +0 -75
  280. package/src/hooks/use-login-polling.ts +0 -114
  281. package/src/hooks/use-logo.tsx +0 -161
  282. package/src/hooks/use-message-queue.ts +0 -387
  283. package/src/hooks/use-now.ts +0 -20
  284. package/src/hooks/use-path-tab-completion.ts +0 -88
  285. package/src/hooks/use-publish-mutation.ts +0 -60
  286. package/src/hooks/use-queue-controls.ts +0 -68
  287. package/src/hooks/use-queue-ui.ts +0 -74
  288. package/src/hooks/use-scroll-management.ts +0 -184
  289. package/src/hooks/use-searchable-list.ts +0 -98
  290. package/src/hooks/use-send-message.ts +0 -773
  291. package/src/hooks/use-sheen-animation.tsx +0 -88
  292. package/src/hooks/use-subscription-query.ts +0 -71
  293. package/src/hooks/use-suggestion-engine.ts +0 -783
  294. package/src/hooks/use-terminal-breakpoints.ts +0 -77
  295. package/src/hooks/use-terminal-dimensions.ts +0 -46
  296. package/src/hooks/use-terminal-focus.ts +0 -29
  297. package/src/hooks/use-terminal-layout.ts +0 -189
  298. package/src/hooks/use-theme.tsx +0 -140
  299. package/src/hooks/use-timeout.ts +0 -85
  300. package/src/hooks/use-update-preference.ts +0 -66
  301. package/src/hooks/use-usage-monitor.ts +0 -55
  302. package/src/hooks/use-usage-query.ts +0 -124
  303. package/src/hooks/use-user-details-query.ts +0 -93
  304. package/src/hooks/use-why-did-you-update.ts +0 -177
  305. package/src/index.tsx +0 -441
  306. package/src/init/__tests__/init-direnv.test.ts +0 -526
  307. package/src/init/init-app.ts +0 -34
  308. package/src/init/init-direnv.ts +0 -133
  309. package/src/login/constants.ts +0 -60
  310. package/src/login/login-flow.ts +0 -204
  311. package/src/login/plain-login.ts +0 -108
  312. package/src/login/utils.ts +0 -183
  313. package/src/native/ripgrep.ts +0 -80
  314. package/src/polyfills/bun-strip-ansi.ts +0 -12
  315. package/src/pre-init/tree-sitter-wasm.ts +0 -96
  316. package/src/project-files.ts +0 -102
  317. package/src/smoke/terminal-command-broker.ts +0 -439
  318. package/src/state/__tests__/chat-store-focus.test.ts +0 -22
  319. package/src/state/__tests__/feedback-store.test.ts +0 -257
  320. package/src/state/chat-history-store.ts +0 -30
  321. package/src/state/chat-store.ts +0 -544
  322. package/src/state/feedback-store.ts +0 -160
  323. package/src/state/freebuff-model-store.ts +0 -42
  324. package/src/state/freebuff-session-store.ts +0 -53
  325. package/src/state/login-store.ts +0 -150
  326. package/src/state/message-block-store.ts +0 -149
  327. package/src/state/publish-store.ts +0 -146
  328. package/src/state/queue-panel-store.ts +0 -26
  329. package/src/state/review-store.ts +0 -24
  330. package/src/testing/env.ts +0 -46
  331. package/src/types/chat-state.ts +0 -15
  332. package/src/types/chat.ts +0 -241
  333. package/src/types/contracts/send-message.ts +0 -12
  334. package/src/types/env.ts +0 -98
  335. package/src/types/freebuff-session.ts +0 -17
  336. package/src/types/function-params.ts +0 -28
  337. package/src/types/react19-compat.d.ts +0 -19
  338. package/src/types/store.ts +0 -111
  339. package/src/types/theme-system.ts +0 -144
  340. package/src/types/utils.ts +0 -3
  341. package/src/utils/__tests__/active-run.test.ts +0 -140
  342. package/src/utils/__tests__/activity-tracker.test.ts +0 -440
  343. package/src/utils/__tests__/agent-display.test.ts +0 -139
  344. package/src/utils/__tests__/analytics-client.test.ts +0 -262
  345. package/src/utils/__tests__/anonymous-id.test.ts +0 -74
  346. package/src/utils/__tests__/anvil-api.test.ts +0 -578
  347. package/src/utils/__tests__/arrays.test.ts +0 -264
  348. package/src/utils/__tests__/bash-context-processor.test.ts +0 -38
  349. package/src/utils/__tests__/block-processor.test.ts +0 -810
  350. package/src/utils/__tests__/chat-history.test.ts +0 -203
  351. package/src/utils/__tests__/chat-input-key-intercept.test.ts +0 -40
  352. package/src/utils/__tests__/chat-meta.test.ts +0 -130
  353. package/src/utils/__tests__/clipboard-linux.test.ts +0 -359
  354. package/src/utils/__tests__/clipboard.test.ts +0 -910
  355. package/src/utils/__tests__/code-search-summary.test.ts +0 -84
  356. package/src/utils/__tests__/collapse-helpers.test.ts +0 -1109
  357. package/src/utils/__tests__/error-handling.test.ts +0 -658
  358. package/src/utils/__tests__/exit-cleanly.test.ts +0 -110
  359. package/src/utils/__tests__/feedback-helpers.test.ts +0 -444
  360. package/src/utils/__tests__/feedback-submission.test.ts +0 -26
  361. package/src/utils/__tests__/fetch-usage.test.ts +0 -355
  362. package/src/utils/__tests__/fingerprint.test.ts +0 -144
  363. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +0 -7
  364. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +0 -3
  365. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +0 -33
  366. package/src/utils/__tests__/format-elapsed-time.test.ts +0 -103
  367. package/src/utils/__tests__/format-timeout.test.ts +0 -87
  368. package/src/utils/__tests__/freebuff-instance-owner.test.ts +0 -69
  369. package/src/utils/__tests__/freebuff-model-navigation.test.ts +0 -102
  370. package/src/utils/__tests__/freebuff-premium-reset.test.ts +0 -79
  371. package/src/utils/__tests__/freebuff-referral-cache.test.ts +0 -116
  372. package/src/utils/__tests__/freebuff-session-api.test.ts +0 -215
  373. package/src/utils/__tests__/freebuff-session-display.test.ts +0 -23
  374. package/src/utils/__tests__/freebuff-streak-line.test.ts +0 -217
  375. package/src/utils/__tests__/image-dimensions.test.ts +0 -249
  376. package/src/utils/__tests__/image-processor.test.ts +0 -209
  377. package/src/utils/__tests__/implementor-helpers.test.ts +0 -1458
  378. package/src/utils/__tests__/keyboard-actions.test.ts +0 -660
  379. package/src/utils/__tests__/layout-helpers.test.ts +0 -36
  380. package/src/utils/__tests__/lazy-response-ads.test.ts +0 -167
  381. package/src/utils/__tests__/log-shipper.test.ts +0 -53
  382. package/src/utils/__tests__/markdown-renderer.test.tsx +0 -421
  383. package/src/utils/__tests__/message-block-helpers.test.ts +0 -1263
  384. package/src/utils/__tests__/message-updater.test.ts +0 -645
  385. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +0 -172
  386. package/src/utils/__tests__/pending-attachments.test.ts +0 -277
  387. package/src/utils/__tests__/polling-backoff.test.ts +0 -51
  388. package/src/utils/__tests__/queue-panel-actions.test.ts +0 -148
  389. package/src/utils/__tests__/run-state-storage.test.ts +0 -919
  390. package/src/utils/__tests__/safe-json.test.ts +0 -77
  391. package/src/utils/__tests__/sdk-event-handlers.test.ts +0 -685
  392. package/src/utils/__tests__/send-message-helpers.test.ts +0 -1898
  393. package/src/utils/__tests__/send-message-timer.test.ts +0 -64
  394. package/src/utils/__tests__/settings.test.ts +0 -99
  395. package/src/utils/__tests__/strings.test.ts +0 -192
  396. package/src/utils/__tests__/terminal-color-detection.test.ts +0 -425
  397. package/src/utils/__tests__/terminal-command-broker.test.ts +0 -528
  398. package/src/utils/__tests__/terminal-enter-detection.test.ts +0 -77
  399. package/src/utils/__tests__/terminal-io.test.ts +0 -137
  400. package/src/utils/__tests__/terminal-protocol-controller.test.ts +0 -119
  401. package/src/utils/__tests__/text-layout.test.ts +0 -82
  402. package/src/utils/__tests__/theme-platform-detection.test.ts +0 -125
  403. package/src/utils/__tests__/think-tag-parser.test.ts +0 -159
  404. package/src/utils/__tests__/trace-writer.test.ts +0 -223
  405. package/src/utils/__tests__/trim-chat-logs.test.ts +0 -78
  406. package/src/utils/__tests__/usage-banner-state.test.ts +0 -298
  407. package/src/utils/__tests__/validation-error-formatting.test.ts +0 -126
  408. package/src/utils/__tests__/windows-terminal-health.test.ts +0 -101
  409. package/src/utils/__tests__/write-file-atomic.test.ts +0 -127
  410. package/src/utils/active-run.ts +0 -131
  411. package/src/utils/activity-tracker.ts +0 -66
  412. package/src/utils/agent-display.ts +0 -87
  413. package/src/utils/agent-helpers.ts +0 -27
  414. package/src/utils/agent-id-utils.ts +0 -14
  415. package/src/utils/ai-message-id.ts +0 -12
  416. package/src/utils/analytics.ts +0 -361
  417. package/src/utils/anonymous-id.ts +0 -87
  418. package/src/utils/anvil-api.ts +0 -612
  419. package/src/utils/anvil-client.ts +0 -217
  420. package/src/utils/arrays.ts +0 -117
  421. package/src/utils/auth.ts +0 -258
  422. package/src/utils/bash-context-processor.ts +0 -47
  423. package/src/utils/bash-messages.ts +0 -108
  424. package/src/utils/block-margins.ts +0 -35
  425. package/src/utils/block-operations.ts +0 -523
  426. package/src/utils/block-processor.ts +0 -213
  427. package/src/utils/chat-history.ts +0 -253
  428. package/src/utils/chat-input-key-intercept.ts +0 -52
  429. package/src/utils/chat-meta.ts +0 -90
  430. package/src/utils/chat-scroll-accel.ts +0 -151
  431. package/src/utils/clipboard-image.ts +0 -588
  432. package/src/utils/clipboard.ts +0 -397
  433. package/src/utils/code-search-summary.ts +0 -70
  434. package/src/utils/collapse-helpers.ts +0 -256
  435. package/src/utils/config-dir.ts +0 -24
  436. package/src/utils/constants.ts +0 -190
  437. package/src/utils/create-event-handler-state.ts +0 -73
  438. package/src/utils/create-run-config.ts +0 -121
  439. package/src/utils/detect-shell.ts +0 -116
  440. package/src/utils/directory-browser.ts +0 -72
  441. package/src/utils/engagement.ts +0 -55
  442. package/src/utils/env.ts +0 -92
  443. package/src/utils/error-handling.ts +0 -255
  444. package/src/utils/error-messages.ts +0 -62
  445. package/src/utils/exit-cleanly.ts +0 -86
  446. package/src/utils/feedback-helpers.ts +0 -103
  447. package/src/utils/feedback-submission.ts +0 -22
  448. package/src/utils/fetch-usage.ts +0 -76
  449. package/src/utils/fingerprint.ts +0 -245
  450. package/src/utils/format-elapsed-time.ts +0 -33
  451. package/src/utils/format-session-units.ts +0 -6
  452. package/src/utils/format-timeout.ts +0 -28
  453. package/src/utils/format-validation-errors-for-message.ts +0 -84
  454. package/src/utils/freebuff-agent-selection.ts +0 -33
  455. package/src/utils/freebuff-instance-owner.ts +0 -66
  456. package/src/utils/freebuff-model-navigation.ts +0 -50
  457. package/src/utils/freebuff-premium-reset.ts +0 -55
  458. package/src/utils/freebuff-referral-cache.ts +0 -51
  459. package/src/utils/freebuff-session-api.ts +0 -243
  460. package/src/utils/freebuff-session-display.ts +0 -21
  461. package/src/utils/freebuff-streak-line.ts +0 -93
  462. package/src/utils/git.ts +0 -17
  463. package/src/utils/helpers.ts +0 -47
  464. package/src/utils/image-display.ts +0 -67
  465. package/src/utils/image-handler.ts +0 -325
  466. package/src/utils/image-processor.ts +0 -133
  467. package/src/utils/image-thumbnail.ts +0 -78
  468. package/src/utils/implementor-helpers.ts +0 -787
  469. package/src/utils/input-modes.ts +0 -174
  470. package/src/utils/keyboard-actions.ts +0 -392
  471. package/src/utils/keypad-keys.ts +0 -47
  472. package/src/utils/layout-helpers.ts +0 -33
  473. package/src/utils/lazy-response-ads.ts +0 -1
  474. package/src/utils/local-agent-registry.ts +0 -474
  475. package/src/utils/log-shipper.ts +0 -126
  476. package/src/utils/logger.ts +0 -327
  477. package/src/utils/markdown-renderer.tsx +0 -1090
  478. package/src/utils/math.ts +0 -3
  479. package/src/utils/message-block-helpers.ts +0 -684
  480. package/src/utils/message-history.ts +0 -143
  481. package/src/utils/message-tree-utils.ts +0 -67
  482. package/src/utils/message-updater.ts +0 -279
  483. package/src/utils/open-file.ts +0 -146
  484. package/src/utils/open-url.ts +0 -63
  485. package/src/utils/path-completion.ts +0 -111
  486. package/src/utils/path-helpers.ts +0 -38
  487. package/src/utils/pending-attachments.ts +0 -360
  488. package/src/utils/polling-backoff.ts +0 -59
  489. package/src/utils/project-picker.ts +0 -12
  490. package/src/utils/queue-panel-actions.ts +0 -70
  491. package/src/utils/recent-projects.ts +0 -156
  492. package/src/utils/renderer-cleanup.ts +0 -233
  493. package/src/utils/response-ad-positions.ts +0 -1
  494. package/src/utils/run-state-storage.ts +0 -603
  495. package/src/utils/safe-json.ts +0 -153
  496. package/src/utils/sdk-event-handlers.ts +0 -571
  497. package/src/utils/send-message-helpers.ts +0 -210
  498. package/src/utils/send-message-timer.ts +0 -110
  499. package/src/utils/settings.ts +0 -226
  500. package/src/utils/skill-registry.ts +0 -94
  501. package/src/utils/spawn-agent-matcher.ts +0 -55
  502. package/src/utils/status-indicator-state.ts +0 -107
  503. package/src/utils/stream-chunk-processor.ts +0 -57
  504. package/src/utils/strings.ts +0 -271
  505. package/src/utils/subscription.ts +0 -31
  506. package/src/utils/syntax-highlighter.tsx +0 -19
  507. package/src/utils/terminal-color-detection.ts +0 -474
  508. package/src/utils/terminal-command-broker.ts +0 -467
  509. package/src/utils/terminal-enter-detection.ts +0 -63
  510. package/src/utils/terminal-images.ts +0 -229
  511. package/src/utils/terminal-io.ts +0 -85
  512. package/src/utils/terminal-protocol-controller.ts +0 -173
  513. package/src/utils/terminal-reset-sequences.ts +0 -33
  514. package/src/utils/terminal-title.ts +0 -84
  515. package/src/utils/terminal-watchdog.ts +0 -366
  516. package/src/utils/text-layout.ts +0 -149
  517. package/src/utils/theme-config.ts +0 -141
  518. package/src/utils/theme-system.ts +0 -1206
  519. package/src/utils/think-tag-parser.ts +0 -95
  520. package/src/utils/time-format.ts +0 -21
  521. package/src/utils/trace-writer.ts +0 -162
  522. package/src/utils/ui-constants.ts +0 -69
  523. package/src/utils/update-checker.ts +0 -110
  524. package/src/utils/usage-banner-state.ts +0 -146
  525. package/src/utils/validation-error-formatting.ts +0 -86
  526. package/src/utils/validation-error-helpers.ts +0 -19
  527. package/src/utils/windows-terminal-health.ts +0 -124
  528. package/src/utils/word-wrap-utils.ts +0 -53
  529. package/src/utils/write-file-atomic.ts +0 -54
  530. package/src/utils/yield-to-event-loop.ts +0 -9
@@ -1,865 +0,0 @@
1
- import { TextAttributes } from '@opentui/core'
2
- import { useKeyboard, useRenderer } from '@opentui/react'
3
- import React, { useCallback, useEffect, useRef, useState } from 'react'
4
-
5
- import { Button } from './button'
6
- import { ChoiceAdBanner, AD_CARD_HEIGHT } from './ad-banner'
7
- import { FreebuffModelSelector } from './freebuff-model-selector'
8
- import { ShimmerText } from './shimmer-text'
9
- import {
10
- refreshFreebuffLandingMetadata,
11
- takeOverFreebuffSession,
12
- } from '../hooks/use-freebuff-session'
13
- import { useFreebuffCtrlCExit } from '../hooks/use-freebuff-ctrl-c-exit'
14
- import { useFreebuffStreakQuery } from '../hooks/use-freebuff-streak-query'
15
- import { useGravityAd } from '../hooks/use-gravity-ad'
16
- import { useLogo } from '../hooks/use-logo'
17
- import { useNow } from '../hooks/use-now'
18
- import { useSheenAnimation } from '../hooks/use-sheen-animation'
19
- import { useTerminalDimensions } from '../hooks/use-terminal-dimensions'
20
- import { useTheme } from '../hooks/use-theme'
21
- import { exitCliCleanly } from '../utils/exit-cleanly'
22
- import {
23
- formatFreebuffPremiumResetCountdown,
24
- getFreebuffPremiumResetAt,
25
- } from '../utils/freebuff-premium-reset'
26
- import {
27
- FREEBUFF_STREAK_INLINE_GAP,
28
- FREEBUFF_STREAK_LABEL_GAP,
29
- fitsFreebuffStreakOnHeadingRow,
30
- getFreebuffStreakBonusNoteForLayout,
31
- getFreebuffStreakLine,
32
- } from '../utils/freebuff-streak-line'
33
- import { formatSessionUnits } from '../utils/format-session-units'
34
- import { isPlainEnterKey } from '../utils/terminal-enter-detection'
35
- import { getLogoAccentColor, getLogoBlockColor } from '../utils/theme-system'
36
- import { INVERTED_CTA_FG } from '../utils/ui-constants'
37
- import {
38
- FREEBUFF_ENABLE_STREAK_IN_UI,
39
- FREEBUFF_LIMITED_SESSION_LIMIT,
40
- FREEBUFF_PREMIUM_SESSION_LIMIT,
41
- } from '@anvil/common/constants/freebuff-models'
42
- import {
43
- getRateLimitsByModel,
44
- getReferralInfo,
45
- } from '@anvil/common/types/freebuff-session'
46
- import { getFreebuffModelAvailabilityNotice } from '@anvil/common/util/freebuff-model-availability'
47
- import { formatFreebuffHardBlockedPrivacySignals } from '@anvil/common/util/freebuff-privacy'
48
-
49
- import type { FreebuffStreakLine } from '../utils/freebuff-streak-line'
50
- import type { FreebuffSessionFailure } from '../state/freebuff-session-store'
51
- import type { FreebuffSessionResponse } from '../types/freebuff-session'
52
- import type { KeyEvent } from '@opentui/core'
53
-
54
- interface FreebuffLandingScreenProps {
55
- session: FreebuffSessionResponse | null
56
- failure: FreebuffSessionFailure | null
57
- }
58
-
59
- /** Landing-screen heading. Referenced both as rendered text and by the
60
- * picker's height-budget math (wrappedRows), so it lives in one place to keep
61
- * the two from drifting. */
62
- const LANDING_HEADING = 'Start coding for free'
63
- const COLLAPSED_LOGO_MIN_HEIGHT = 26
64
-
65
- /** "in ~3h 20m" / "in ~45 min" / "in under a minute". Used on the
66
- * rate-limited screen so users know when they can try again. */
67
- const formatRetryAfter = (ms: number): string => {
68
- if (!Number.isFinite(ms) || ms <= 0) return 'any moment now'
69
- const minutes = Math.round(ms / 60_000)
70
- if (minutes < 1) return 'under a minute'
71
- if (minutes < 60) return `${minutes} min`
72
- const hours = Math.floor(minutes / 60)
73
- const rem = minutes % 60
74
- return rem === 0 ? `${hours}h` : `${hours}h ${rem}m`
75
- }
76
-
77
- // Rendered directly under the model list — that's where "why these models?"
78
- // gets asked. The copy itself is shared with Freebuff Desktop's model menu; see
79
- // `getFreebuffModelAvailabilityNotice` for the tone rules it follows.
80
- const getLimitedModeNotice = (
81
- session: FreebuffSessionResponse | null,
82
- ): string | null =>
83
- getFreebuffModelAvailabilityNotice(
84
- session && 'countryBlockReason' in session ? session : null,
85
- )
86
-
87
- function getTakeoverErrorMessage(failure: FreebuffSessionFailure): string {
88
- if (failure.type === 'http' && failure.statusCode === 503) {
89
- return "Freebuff is busy and couldn't complete the takeover yet."
90
- }
91
- if (failure.type === 'timeout') {
92
- return failure.outcomeUnknown
93
- ? 'The takeover request timed out and may have succeeded. Check the warning, then retry if you still want to take over.'
94
- : failure.retry
95
- ? 'The takeover request timed out while Freebuff was busy.'
96
- : 'The takeover request timed out.'
97
- }
98
- if (failure.outcomeUnknown) {
99
- return "Freebuff couldn't confirm whether the takeover succeeded. Check the warning, then retry if you still want to take over."
100
- }
101
- return failure.message.trim()
102
- ? `Takeover failed: ${failure.message}`
103
- : 'The takeover failed unexpectedly.'
104
- }
105
-
106
- export const TakeoverPrompt: React.FC<{
107
- failure: FreebuffSessionFailure | null
108
- onTakeOver?: () => Promise<void>
109
- }> = ({ failure, onTakeOver = takeOverFreebuffSession }) => {
110
- const theme = useTheme()
111
- const [pending, setPending] = useState(false)
112
- const [focusedIndex, setFocusedIndex] = useState(0) // 0 = Take over, 1 = Exit
113
- const takeoverInFlightRef = useRef(false)
114
- const retry = failure?.retry ?? null
115
- const retryTick = useNow(1_000, retry !== null)
116
- // `useNow` freezes while disabled. Use the current time when a retry first
117
- // appears so a long-open prompt cannot render a stale countdown for a frame.
118
- const retryNow = retry ? Math.max(retryTick, Date.now()) : retryTick
119
- const retrySeconds = retry
120
- ? Math.max(0, Math.ceil((retry.retryAtMs - retryNow) / 1_000))
121
- : 0
122
- const outcomeUnknown = failure?.outcomeUnknown ?? false
123
- const blocked = pending
124
- const displayError = failure ? getTakeoverErrorMessage(failure) : null
125
-
126
- const handleTakeover = useCallback(async () => {
127
- // `pending` updates on the next render. The ref closes the gap where two
128
- // keyboard/mouse events arrive in the same frame and would both POST.
129
- if (takeoverInFlightRef.current) return
130
- takeoverInFlightRef.current = true
131
- setPending(true)
132
- try {
133
- await onTakeOver()
134
- } finally {
135
- takeoverInFlightRef.current = false
136
- setPending(false)
137
- }
138
- }, [onTakeOver])
139
-
140
- useKeyboard(
141
- useCallback(
142
- (key: KeyEvent) => {
143
- const name = key.name ?? ''
144
- const isConfirm = isPlainEnterKey(key)
145
- const isExit = name === 'escape' || name === 'esc'
146
- const isTab = name === 'tab'
147
- const isShiftTab = key.shift === true && isTab
148
- const isRight = name === 'right'
149
- const isLeft = name === 'left'
150
-
151
- if (isExit) {
152
- key.preventDefault?.()
153
- void exitCliCleanly()
154
- return
155
- }
156
-
157
- if (isConfirm) {
158
- key.preventDefault?.()
159
- if (focusedIndex === 0) {
160
- void handleTakeover()
161
- } else {
162
- void exitCliCleanly()
163
- }
164
- return
165
- }
166
-
167
- if (isRight || isTab) {
168
- key.preventDefault?.()
169
- setFocusedIndex((prev) => (prev + 1) % 2)
170
- return
171
- }
172
-
173
- if (isLeft || isShiftTab) {
174
- key.preventDefault?.()
175
- setFocusedIndex((prev) => (prev - 1 + 2) % 2)
176
- return
177
- }
178
- },
179
- [focusedIndex, handleTakeover],
180
- ),
181
- )
182
-
183
- const isTakeoverFocused = focusedIndex === 0
184
- const isExitFocused = focusedIndex === 1
185
- const takeoverLabel = pending
186
- ? 'Taking over...'
187
- : outcomeUnknown
188
- ? 'Try takeover again'
189
- : retry
190
- ? 'Retry now'
191
- : 'Take over'
192
- const takeoverForeground = blocked
193
- ? theme.muted
194
- : isTakeoverFocused
195
- ? INVERTED_CTA_FG
196
- : theme.foreground
197
-
198
- return (
199
- <box
200
- style={{
201
- flexDirection: 'column',
202
- alignItems: 'center',
203
- gap: 1,
204
- width: '100%',
205
- }}
206
- >
207
- <text style={{ fg: theme.foreground }} attributes={TextAttributes.BOLD}>
208
- Freebuff is already running
209
- </text>
210
-
211
- <text style={{ fg: theme.muted }}>
212
- Only one freebuff instance is allowed at a time.
213
- </text>
214
-
215
- {displayError && (
216
- <text style={{ fg: theme.secondary, wrapMode: 'word' }}>
217
- ⚠ {displayError}
218
- </text>
219
- )}
220
-
221
- {retry && (
222
- <text style={{ fg: theme.muted }}>
223
- {retrySeconds > 0
224
- ? `Retrying automatically in ${retrySeconds}s (attempt ${retry.attempt}).`
225
- : `Retrying automatically now (attempt ${retry.attempt}).`}
226
- </text>
227
- )}
228
-
229
- <box style={{ flexDirection: 'row', gap: 2, marginTop: 1 }}>
230
- <Button
231
- onClick={blocked ? undefined : handleTakeover}
232
- onMouseOver={() => setFocusedIndex(0)}
233
- style={{ paddingLeft: 1, paddingRight: 1 }}
234
- border={['top', 'bottom', 'left', 'right']}
235
- borderStyle="single"
236
- borderColor={blocked ? theme.muted : theme.primary}
237
- >
238
- <text
239
- style={{
240
- // theme.background is 'transparent' and can't serve as inverted
241
- // text — on the green fill it renders the label invisible.
242
- fg: takeoverForeground,
243
- bg: isTakeoverFocused && !blocked ? theme.primary : undefined,
244
- }}
245
- attributes={TextAttributes.BOLD}
246
- >
247
- {takeoverLabel}
248
- </text>
249
- </Button>
250
- <Button
251
- onClick={() => exitCliCleanly()}
252
- onMouseOver={() => setFocusedIndex(1)}
253
- style={{ paddingLeft: 1, paddingRight: 1 }}
254
- border={['top', 'bottom', 'left', 'right']}
255
- borderStyle="single"
256
- borderColor={isExitFocused ? theme.foreground : theme.muted}
257
- >
258
- <text
259
- style={{ fg: isExitFocused ? theme.foreground : theme.muted }}
260
- attributes={
261
- isExitFocused ? TextAttributes.BOLD : TextAttributes.NONE
262
- }
263
- >
264
- Exit
265
- </text>
266
- </Button>
267
- </box>
268
- </box>
269
- )
270
- }
271
-
272
- /** "N day streak" then its progress dots, as spans so both placements (beside
273
- * the heading, or its own line under it) draw the streak identically. */
274
- const streakSpans = (
275
- line: FreebuffStreakLine,
276
- theme: ReturnType<typeof useTheme>,
277
- ) => [
278
- <span key="label" fg={theme.foreground}>
279
- {line.label}
280
- </span>,
281
- <span key="dots" fg={theme.primary}>
282
- {`${' '.repeat(FREEBUFF_STREAK_LABEL_GAP)}${line.dots}`}
283
- </span>,
284
- ]
285
-
286
- /** Streak on its own line under the heading, for terminals too narrow to
287
- * share the row. For streak === 0 the line is rendered blank so new / lapsed
288
- * users are nudged to start using the product rather than shown an empty
289
- * streak (and so the picker doesn't jump once they earn their first day). */
290
- const StreakInlineLine: React.FC<{
291
- line: FreebuffStreakLine | null
292
- }> = ({ line }) => {
293
- const theme = useTheme()
294
-
295
- if (!line) {
296
- return <text style={{ flexShrink: 0 }}> </text>
297
- }
298
-
299
- return (
300
- <text style={{ flexShrink: 0, wrapMode: 'none' }}>
301
- {streakSpans(line, theme)}
302
- </text>
303
- )
304
- }
305
-
306
- /** Heading row: the title, with the streak beside it when the caller passes
307
- * one (only when it fits — see fitsFreebuffStreakOnHeadingRow).
308
- *
309
- * Deliberately ONE text rather than a space-between flex row. The row this
310
- * replaces put the two in a stretched row inside a shrink-to-fit column,
311
- * which had two problems: with nothing wider on screen there was no free
312
- * space to distribute, so the two rendered flush ("Start coding for
313
- * free18 day streak"), and a space-between row whose content overflows its
314
- * container segfaults the renderer — reachable by dragging the terminal
315
- * narrower, since the native layout runs against the old tree before React
316
- * re-renders. A single text just wraps, and the gap is explicit.
317
- *
318
- * Exported for the layout test. */
319
- export const LandingHeadingRow: React.FC<{
320
- streakLine: FreebuffStreakLine | null
321
- marginBottom: number
322
- }> = ({ streakLine, marginBottom }) => {
323
- const theme = useTheme()
324
-
325
- return (
326
- <text style={{ marginBottom, wrapMode: 'word' }}>
327
- <span fg={theme.foreground} attributes={TextAttributes.BOLD}>
328
- {LANDING_HEADING}
329
- </span>
330
- {streakLine && [
331
- <span key="gap">{' '.repeat(FREEBUFF_STREAK_INLINE_GAP)}</span>,
332
- ...streakSpans(streakLine, theme),
333
- ]}
334
- </text>
335
- )
336
- }
337
-
338
- export const FreebuffLandingScreen: React.FC<FreebuffLandingScreenProps> = ({
339
- session,
340
- failure,
341
- }) => {
342
- const theme = useTheme()
343
- const renderer = useRenderer()
344
- const { terminalWidth, terminalHeight, contentMaxWidth } =
345
- useTerminalDimensions()
346
-
347
- // Progressive disclosure as the terminal gets shorter. The picker is the
348
- // only thing the user must be able to reach, so chrome is shed first:
349
- // tall (>=40): full 6-line ASCII logo + roomy spacing, content anchored low
350
- // shorter : no logo — the heading already identifies the experience
351
- // tiny (<18) : also drop the ad banner
352
- // The big logo is reserved for genuinely tall windows; at the common ~30-row
353
- // height we hide the branding so more models fit without scrolling.
354
- // Section headers always show — the picker scrolls within whatever rows
355
- // remain (see selectorMaxHeight below), so there's no need to hide them.
356
- //
357
- // Exception: when the picker is collapsed it shrinks to ~5 rows, freeing the
358
- // ~6 rows the big logo needs. So on a mid-height window with a collapsed,
359
- // referral-free picker we still show the full ASCII logo — it fills what
360
- // would otherwise be dead space above the card. A referral card or expanded
361
- // list gives those rows back to the scrollable menu. 26 is the smallest
362
- // window where the logo block,
363
- // heading, collapsed picker, streak, and ad all coexist without scrolling.
364
- //
365
- // The picker (rendered below) owns this and reports it via onExpandedChange;
366
- // we default to collapsed so the first paint reserves logo space correctly.
367
- const [selectorExpanded, setSelectorExpanded] = useState(false)
368
- const hasReferralMenu =
369
- session?.status === 'none' && Boolean(getReferralInfo(session))
370
- const logoHeightFits =
371
- terminalHeight >= 40 ||
372
- (!selectorExpanded &&
373
- !hasReferralMenu &&
374
- terminalHeight >= COLLAPSED_LOGO_MIN_HEIGHT)
375
- const compact = terminalHeight < 22
376
- const showAds = terminalHeight >= 18
377
- const textMarginBottom = 1
378
-
379
- const [sheenPosition, setSheenPosition] = useState(0)
380
- const blockColor = getLogoBlockColor(theme.name)
381
- const accentColor = getLogoAccentColor(theme.name)
382
- const { applySheenToChar } = useSheenAnimation({
383
- logoColor: theme.foreground,
384
- accentColor,
385
- blockColor,
386
- terminalWidth: renderer?.width ?? terminalWidth,
387
- sheenPosition,
388
- setSheenPosition,
389
- })
390
- const { component: logoComponent, textBlock: logoTextBlock } = useLogo({
391
- availableWidth: contentMaxWidth,
392
- accentColor,
393
- blockColor,
394
- applySheenToChar,
395
- })
396
- // useLogo falls back to a one-line brand label when the ASCII variants do
397
- // not fit horizontally. This landing screen intentionally hides that label.
398
- const showFullLogo = logoHeightFits && logoTextBlock.length > 0
399
-
400
- // Always enable ads on the landing screen — this is where monetization lives.
401
- // forceStart bypasses the "wait for first user message" gate inside the hook,
402
- // which would otherwise block ads here since no conversation exists yet.
403
- // The server tries Gravity first, then falls back to ZeroClick and Carbon.
404
- const { ads, recordClick, recordImpression } = useGravityAd({
405
- enabled: true,
406
- forceStart: true,
407
- provider: 'gravity',
408
- // Legacy wire name for this surface — the ads API maps it to placements,
409
- // so it must not change with the component rename.
410
- surface: 'waiting_room',
411
- })
412
-
413
- useFreebuffCtrlCExit()
414
-
415
- const [exitHover, setExitHover] = useState(false)
416
-
417
- const accessTier =
418
- session && 'accessTier' in session ? session.accessTier : 'full'
419
- // Hidden in compact terminals: the notice is nice-to-have context, and
420
- // below 22 rows every line competes with the picker itself.
421
- const limitedModeNotice =
422
- accessTier === 'limited' && !compact ? getLimitedModeNotice(session) : null
423
- // 'none' = user hasn't started a session yet. We're in the pre-chat landing
424
- // state: show the picker with a prompt. Picking a model triggers
425
- // startFreebuffSession, which POSTs and transitions straight to 'active' (chat).
426
- const isLanding = session?.status === 'none'
427
- const streakQuery = useFreebuffStreakQuery({
428
- enabled: FREEBUFF_ENABLE_STREAK_IN_UI && isLanding,
429
- })
430
- const streak = streakQuery.data?.streak ?? 0
431
- // The indicator normally shares the heading row, so keep its slot even in
432
- // compact-height terminals. It renders blank space until a streak is active.
433
- const showStreakIndicator = FREEBUFF_ENABLE_STREAK_IN_UI && isLanding
434
- // Once a full week is earned, explain the recurring perk under the picker so
435
- // the streak reads as worth keeping. Pre-milestone countdown copy stays
436
- // hidden; this dedicated row is only for a bonus the user has already earned.
437
- const streakBonusNote = showStreakIndicator
438
- ? getFreebuffStreakBonusNoteForLayout({
439
- streak,
440
- accessTier: accessTier === 'limited' ? 'limited' : 'full',
441
- terminalHeight,
442
- availableWidth: contentMaxWidth,
443
- })
444
- : null
445
- // On the landing screen the streak rides on the heading row, a fixed gap
446
- // after the title. Measured against the real strings (the label grows with
447
- // the day count), so
448
- // a streak that can't clear the inline gap drops to its own line under the
449
- // heading instead of colliding with it. With no streak yet, the day-one line
450
- // stands in, reserving the slot where the streak will land.
451
- const streakLine = showStreakIndicator ? getFreebuffStreakLine(streak) : null
452
- const streakOnHeadingRow =
453
- showStreakIndicator &&
454
- fitsFreebuffStreakOnHeadingRow({
455
- line: streakLine,
456
- headingWidth: LANDING_HEADING.length,
457
- availableWidth: contentMaxWidth,
458
- })
459
- // On the landing picker we tick once a minute so the session reset countdown
460
- // stays fresh.
461
- const now = useNow(60_000, isLanding)
462
-
463
- // Free-session quota counter for the title line. All free models share one
464
- // pool; the server replicates the same snapshot under each free model
465
- // id, so any entry has the right count. Renders amber when exhausted so
466
- // the limit reads as "you've hit it" rather than just another count.
467
- const rateLimitsByModel = getRateLimitsByModel(session)
468
- const sessionRateLimit = rateLimitsByModel
469
- ? Object.values(rateLimitsByModel)[0]
470
- : undefined
471
- const sharedSessionUsed = sessionRateLimit?.recentCount ?? 0
472
- // Hide the "0 of N … used" line entirely for a fresh user — a zeroed counter
473
- // is noise on the landing screen. It appears once any session is consumed.
474
- //
475
- // For the regular tiers the PREMIUM section header inside the expanded
476
- // picker carries this quota inline, so the below-picker line survives for
477
- // the limited tier (which has no premium section to host it) and for the
478
- // collapsed picker. When the collapsed recommended hero is a premium model
479
- // (getRecommendedFreebuffModelId, while the pool has sessions left) the count
480
- // is exactly what Enter is about to spend.
481
- const showSessionCounter = sharedSessionUsed > 0
482
- const showBelowPickerCounter =
483
- showSessionCounter && (accessTier === 'limited' || !selectorExpanded)
484
- // Prefer the server-sent limit (base + streak/referral bonuses) so the
485
- // counter and its amber exhausted cue match what admission will enforce;
486
- // the static constants only cover the pre-snapshot fallback.
487
- const sessionLimit =
488
- sessionRateLimit?.limit ??
489
- (accessTier === 'limited'
490
- ? FREEBUFF_LIMITED_SESSION_LIMIT
491
- : FREEBUFF_PREMIUM_SESSION_LIMIT)
492
- const isSessionExhausted = sharedSessionUsed >= sessionLimit
493
- const sessionUsedColor = isSessionExhausted ? theme.secondary : theme.muted
494
- const sessionLabel =
495
- accessTier === 'limited' ? 'sessions' : 'premium sessions'
496
- const formattedSharedSessionUsed = formatSessionUnits(sharedSessionUsed)
497
- const sessionResetAt = getFreebuffPremiumResetAt({
498
- rateLimitsByModel,
499
- nowMs: now,
500
- })
501
- const sessionResetAtMs = sessionResetAt.getTime()
502
- const sessionResetCountdown = formatFreebuffPremiumResetCountdown(
503
- sessionResetAt,
504
- now,
505
- )
506
-
507
- // Rows the picker may occupy = terminal height minus the fixed chrome
508
- // around it. Each term mirrors the real layout exactly (no padded
509
- // estimate, no blanket safety row) so the scrollbox fills the available
510
- // space with no dead band below it:
511
- // - top bar: paddingTop 1 + the ✕ row = 2
512
- // - ad banner: AD_CARD_HEIGHT, only when shown
513
- // - main box: paddingBottom 1
514
- // - logo block: lines + marginBottom 1 (always, when shown) + gap (full)
515
- // - the prompt/counter (landing)
516
- // Line wrapping is derived from the actual strings vs contentMaxWidth, so
517
- // a wrapped counter is accounted for precisely instead of guessed at.
518
- const wrappedRows = (text: string) =>
519
- Math.max(1, Math.ceil(text.length / contentMaxWidth))
520
- const logoBlockRows = showFullLogo
521
- ? 8 /* 6 logo lines + marginBottom + gap */
522
- : 0
523
- const adRows = showAds ? AD_CARD_HEIGHT : 0
524
- // Status lines render below the picker, each with marginTop 1: the
525
- // limited-mode notice, then the streak. They still eat into the picker's
526
- // height budget regardless of being above or below it. (The session counter
527
- // used to sit here too; it now rides inside the selector — see below.)
528
- // Placement varies: on a wide landing screen the streak shares
529
- // the heading row (0 extra rows, already counted in landingTextRows); on a
530
- // narrow landing screen it drops to its own line under the heading (1 row,
531
- // no top margin).
532
- const streakRows = !showStreakIndicator ? 0 : streakOnHeadingRow ? 0 : 1
533
- const noticeRows = limitedModeNotice
534
- ? 1 /* marginTop */ + wrappedRows(limitedModeNotice)
535
- : 0
536
- // Earned streak perk note: one marginTop row + wrap.
537
- const streakBonusRows = streakBonusNote
538
- ? 1 /* marginTop */ + wrappedRows(streakBonusNote)
539
- : 0
540
- // The referral/GLM card and the session counter both live inside the model
541
- // selector's scrollbox now (the counter rides `belowToggle`, between the
542
- // expand toggle and the referral pitch), so only genuinely fixed status
543
- // lines below the selector reduce its viewport. Anything inside the
544
- // scrollbox is measured by the selector itself and must NOT be reserved
545
- // here as well, or the viewport shrinks while the content grows.
546
- const belowPickerRows = streakRows + noticeRows + streakBonusRows
547
- const reservedChrome = 2 + adRows + 1 /* main paddingBottom */ + logoBlockRows
548
- const landingTextRows =
549
- wrappedRows(LANDING_HEADING) + textMarginBottom + belowPickerRows
550
- // Floor = one whole recommended card: 2 border rows + its 2 text lines (name
551
- // + tagline, then the AI-training warning on its own line). Rows grew from
552
- // one text line to two when the warning stopped inlining, so the old floor of
553
- // 3 left the card's bottom border clipped on a very short terminal. (The
554
- // warning itself stays visible either way — scrollTop starts at 0, so it is
555
- // the last row that gets cut, not the first.)
556
- const MIN_SELECTOR_ROWS = 4
557
- const selectorMaxHeight = Math.max(
558
- MIN_SELECTOR_ROWS,
559
- terminalHeight - reservedChrome - landingTextRows,
560
- )
561
-
562
- useEffect(() => {
563
- if (!isLanding || !sessionRateLimit) return
564
-
565
- const delayMs = Math.max(0, sessionResetAtMs - Date.now() + 1_000)
566
- const timer = setTimeout(() => {
567
- refreshFreebuffLandingMetadata().catch(() => {})
568
- }, delayMs)
569
-
570
- return () => clearTimeout(timer)
571
- }, [isLanding, sessionRateLimit, sessionResetAtMs])
572
-
573
- return (
574
- <box
575
- style={{
576
- width: '100%',
577
- height: '100%',
578
- flexDirection: 'column',
579
- backgroundColor: theme.background,
580
- }}
581
- >
582
- {/* Top-right exit affordance so mouse users have a clear way out even
583
- when they don't know Ctrl+C works. width: '100%' is required for
584
- justifyContent to actually push the X to the right. */}
585
- <box
586
- style={{
587
- width: '100%',
588
- flexDirection: 'row',
589
- justifyContent: 'space-between',
590
- paddingTop: 1,
591
- paddingLeft: 2,
592
- paddingRight: 2,
593
- flexShrink: 0,
594
- }}
595
- >
596
- {/* Empty spacer: justifyContent space-between needs a left sibling to
597
- keep the ✕ pushed to the right. */}
598
- <box />
599
- <Button
600
- onClick={() => exitCliCleanly()}
601
- onMouseOver={() => setExitHover(true)}
602
- onMouseOut={() => setExitHover(false)}
603
- style={{ paddingLeft: 1, paddingRight: 1 }}
604
- >
605
- <text
606
- style={{ fg: exitHover ? theme.foreground : theme.muted }}
607
- attributes={TextAttributes.BOLD}
608
- >
609
-
610
- </text>
611
- </Button>
612
- </box>
613
-
614
- <box
615
- style={{
616
- flexGrow: 1,
617
- flexDirection: 'column',
618
- alignItems: 'center',
619
- // Full logo: anchor the clump low (flex-end), matching how chat pins
620
- // its header/messages to the input bar. Without a logo, hug the top
621
- // so the freed rows remain available to the picker.
622
- justifyContent: showFullLogo ? 'flex-end' : 'flex-start',
623
- paddingLeft: 2,
624
- paddingRight: 2,
625
- paddingBottom: 1,
626
- gap: showFullLogo ? 1 : 0,
627
- }}
628
- >
629
- {showFullLogo && (
630
- <box style={{ marginBottom: 1, flexShrink: 0 }}>{logoComponent}</box>
631
- )}
632
-
633
- <box
634
- style={{
635
- flexDirection: 'column',
636
- alignItems: 'center',
637
- gap: 0,
638
- maxWidth: contentMaxWidth,
639
- }}
640
- >
641
- {failure && (!session || session.status === 'none') && (
642
- <text style={{ fg: theme.secondary, wrapMode: 'word' }}>
643
- ⚠ {failure.message}
644
- </text>
645
- )}
646
-
647
- {!session && !failure && (
648
- <text style={{ fg: theme.muted }}>
649
- <ShimmerText text="Connecting…" />
650
- </text>
651
- )}
652
-
653
- {isLanding && (
654
- <box
655
- style={{
656
- flexDirection: 'column',
657
- alignItems: 'flex-start',
658
- gap: 0,
659
- }}
660
- >
661
- <LandingHeadingRow
662
- streakLine={streakOnHeadingRow ? streakLine : null}
663
- marginBottom={textMarginBottom}
664
- />
665
- {showStreakIndicator && !streakOnHeadingRow && (
666
- <StreakInlineLine line={streakLine} />
667
- )}
668
- <FreebuffModelSelector
669
- maxHeight={selectorMaxHeight}
670
- onExpandedChange={setSelectorExpanded}
671
- belowToggle={
672
- showBelowPickerCounter ? (
673
- <text
674
- style={{
675
- fg: theme.muted,
676
- marginTop: 1,
677
- wrapMode: 'word',
678
- }}
679
- >
680
- <span fg={sessionUsedColor}>
681
- {formattedSharedSessionUsed} of {sessionLimit}{' '}
682
- {sessionLabel} used
683
- </span>
684
- <span fg={theme.muted}>
685
- {', '}
686
- resets in {sessionResetCountdown}
687
- </span>
688
- </text>
689
- ) : null
690
- }
691
- />
692
- {limitedModeNotice && (
693
- <text
694
- style={{ fg: theme.muted, wrapMode: 'word', marginTop: 1 }}
695
- >
696
- {limitedModeNotice}
697
- </text>
698
- )}
699
- {streakBonusNote && (
700
- <text
701
- style={{ fg: theme.primary, wrapMode: 'word', marginTop: 1 }}
702
- >
703
- {streakBonusNote}
704
- </text>
705
- )}
706
- </box>
707
- )}
708
-
709
- {session?.status === 'takeover_prompt' && (
710
- <TakeoverPrompt failure={failure} />
711
- )}
712
-
713
- {/* Country outside the free-mode allowlist. Terminal — polling has
714
- stopped. Tell the user up front rather than letting them send a
715
- request that the chat/completions gate would reject. */}
716
- {session?.status === 'country_blocked' && (
717
- <>
718
- <text style={{ fg: theme.secondary, marginBottom: 1 }}>
719
- ⚠ Free mode isn't available in your region
720
- </text>
721
- <text style={{ fg: theme.muted, wrapMode: 'word' }}>
722
- {session.countryBlockReason === 'anonymous_network' ? (
723
- <>
724
- We detected{' '}
725
- {formatFreebuffHardBlockedPrivacySignals(
726
- session.ipPrivacySignals,
727
- )}{' '}
728
- traffic
729
- {session.countryCode === 'UNKNOWN' ? (
730
- ''
731
- ) : (
732
- <>
733
- {' '}
734
- from{' '}
735
- <span fg={theme.foreground}>{session.countryCode}</span>
736
- </>
737
- )}
738
- . Freebuff can't be used from VPN, proxy, or Tor traffic.
739
- Disable it and restart Freebuff to try again.
740
- </>
741
- ) : session.countryCode === 'UNKNOWN' ? (
742
- <>
743
- We couldn't verify an eligible location for this request.
744
- VPN, Tor, proxy, or unknown-location traffic can't use
745
- freebuff. Press Ctrl+C to exit.
746
- </>
747
- ) : (
748
- <>
749
- We detected your location as{' '}
750
- <span fg={theme.foreground}>{session.countryCode}</span>,
751
- which is outside the countries where freebuff is currently
752
- offered. Press Ctrl+C to exit.
753
- </>
754
- )}
755
- </text>
756
- </>
757
- )}
758
-
759
- {/* Account banned. Terminal — polling has stopped. Blocking here
760
- stops banned bots from re-entering free mode. */}
761
- {session?.status === 'banned' && (
762
- <>
763
- <text style={{ fg: theme.secondary, marginBottom: 1 }}>
764
- ⚠ Account unavailable
765
- </text>
766
- <text style={{ fg: theme.muted, wrapMode: 'word' }}>
767
- This account has been suspended and can't use freebuff. If you
768
- think this is a mistake, contact support@codebuff.com. Press
769
- Ctrl+C to exit.
770
- </text>
771
- </>
772
- )}
773
-
774
- {/* Shared free-session quota exhausted. Terminal for this run —
775
- the user can exit and return after its daily or weekly reset. */}
776
- {session?.status === 'rate_limited' && (
777
- <>
778
- <text style={{ fg: theme.secondary, marginBottom: 1 }}>
779
- ⚠ Session limit reached
780
- </text>
781
- <text style={{ fg: theme.muted, wrapMode: 'word' }}>
782
- You've used{' '}
783
- <span fg={theme.foreground}>
784
- {formatSessionUnits(session.recentCount)} of {session.limit}
785
- </span>{' '}
786
- sessions{' '}
787
- {session.period === 'pacific_week' ? 'this week' : 'today'}.
788
- Try again in{' '}
789
- <span fg={theme.foreground}>
790
- {formatRetryAfter(session.retryAfterMs)}
791
- </span>
792
- . Press Ctrl+C to exit.
793
- </text>
794
- </>
795
- )}
796
-
797
- {/* Daily provider-spend admission budget reached. Existing sessions
798
- are never interrupted; this screen only follows a rejected fresh
799
- admission and gives the user a friendly, concrete return time. */}
800
- {session?.status === 'spend_limited' && (
801
- <>
802
- <text style={{ fg: theme.secondary, marginBottom: 1 }}>
803
- ☕ Daily Freebuff limit reached
804
- </text>
805
- <text style={{ fg: theme.muted, wrapMode: 'word' }}>
806
- {session.message} Come back in{' '}
807
- <span fg={theme.foreground}>
808
- {formatRetryAfter(session.retryAfterMs)}
809
- </span>
810
- {' — '}your free usage resets automatically at midnight Pacific.
811
- Press Ctrl+C to exit.
812
- </text>
813
- </>
814
- )}
815
-
816
- {/* Too many distinct users hold a free session on this egress IP.
817
- Unlike the quota screens above there is no reset time — a slot
818
- frees as soon as any session on the IP ends — so this offers a
819
- short retry rather than a "come back tomorrow". */}
820
- {session?.status === 'ip_capped' && (
821
- <>
822
- <text style={{ fg: theme.secondary, marginBottom: 1 }}>
823
- 🚦 Too many Freebuff sessions on this network
824
- </text>
825
- <text style={{ fg: theme.muted, wrapMode: 'word' }}>
826
- {session.activeUsersForIp} other people are already using
827
- Freebuff from your network, which is the most we allow at once.
828
- Try again in{' '}
829
- <span fg={theme.foreground}>
830
- {formatRetryAfter(session.retryAfterMs)}
831
- </span>
832
- {' — '}a slot opens as soon as one of them finishes. Press
833
- Ctrl+C to exit.
834
- </text>
835
- </>
836
- )}
837
- </box>
838
- </box>
839
-
840
- {/* Reserve the ad banner slot before the async ad fetch resolves so the
841
- landing content does not jump when the banner fills. On very
842
- short terminals the banner is dropped entirely to give the picker
843
- back its 5 rows. */}
844
- {showAds && (
845
- <box
846
- style={{
847
- width: '100%',
848
- flexShrink: 0,
849
- height: AD_CARD_HEIGHT,
850
- }}
851
- >
852
- {ads ? (
853
- <ChoiceAdBanner
854
- ads={ads}
855
- onClick={recordClick}
856
- onImpression={recordImpression}
857
- />
858
- ) : (
859
- <text style={{ fg: theme.muted }}>{'─'.repeat(terminalWidth)}</text>
860
- )}
861
- </box>
862
- )}
863
- </box>
864
- )
865
- }