@alumbwe/anvil 1.0.0 → 1.0.1

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/bin/anvil +1 -1
  2. package/dist/bundled.js +79559 -0
  3. package/dist/bundled.js.map +581 -0
  4. package/package.json +5 -2
  5. package/src/__tests__/README.md +0 -256
  6. package/src/__tests__/bash-mode.test.ts +0 -442
  7. package/src/__tests__/cli-args.test.ts +0 -181
  8. package/src/__tests__/cli-harness.test.ts +0 -52
  9. package/src/__tests__/e2e/first-time-login.test.ts +0 -173
  10. package/src/__tests__/e2e/logout-relogin-flow.test.ts +0 -110
  11. package/src/__tests__/e2e/returning-user-auth.test.ts +0 -113
  12. package/src/__tests__/e2e-cli.test.ts +0 -183
  13. package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +0 -176
  14. package/src/__tests__/helpers/mock-api-client.ts +0 -64
  15. package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +0 -89
  16. package/src/__tests__/helpers/terminal-watchdog-fixture.ts +0 -75
  17. package/src/__tests__/home-directory-detection.test.ts +0 -213
  18. package/src/__tests__/integration/api-integration.test.ts +0 -288
  19. package/src/__tests__/integration/credentials-storage.test.ts +0 -486
  20. package/src/__tests__/integration/local-agents.test.ts +0 -1187
  21. package/src/__tests__/integration/login-flow-instrumentation.test.ts +0 -134
  22. package/src/__tests__/integration/login-polling-working.test.ts +0 -285
  23. package/src/__tests__/integration/queue-editing.test.tsx +0 -261
  24. package/src/__tests__/integration/usage-refresh-on-completion.test.ts +0 -168
  25. package/src/__tests__/integration-tmux.test.ts +0 -188
  26. package/src/__tests__/launcher-avx2-fallback.test.ts +0 -630
  27. package/src/__tests__/launcher-disconnect.test.ts +0 -104
  28. package/src/__tests__/mocks/hover-toggle-controller.ts +0 -71
  29. package/src/__tests__/open-tui-native-bundle.test.ts +0 -378
  30. package/src/__tests__/path-completion.test.ts +0 -243
  31. package/src/__tests__/project-files-chat-id.test.ts +0 -34
  32. package/src/__tests__/release/proxy-http-get.test.ts +0 -667
  33. package/src/__tests__/release/wrapper-safety.test.ts +0 -412
  34. package/src/__tests__/renderer-cleanup.test.ts +0 -174
  35. package/src/__tests__/rerender-perf.integration.test.ts +0 -283
  36. package/src/__tests__/terminal-reset-sequences.test.ts +0 -36
  37. package/src/__tests__/terminal-watchdog.test.ts +0 -388
  38. package/src/__tests__/test-utils.ts +0 -271
  39. package/src/__tests__/tmux-poc.ts +0 -150
  40. package/src/__tests__/unit/agent-mode-toggle.test.ts +0 -137
  41. package/src/__tests__/unit/copy-button.test.ts +0 -157
  42. package/src/__tests__/unit/create-run-config.test.ts +0 -66
  43. package/src/__tests__/unit/publish-confirmation.test.ts +0 -70
  44. package/src/__tests__/unit/segmented-control.test.ts +0 -152
  45. package/src/__tests__/utils/env.test.ts +0 -169
  46. package/src/__tests__/utils/project-picker.test.ts +0 -39
  47. package/src/agents/bundled-agents.generated.d.ts +0 -14
  48. package/src/agents/bundled-agents.generated.ts +0 -3692
  49. package/src/app.tsx +0 -399
  50. package/src/chat.tsx +0 -1698
  51. package/src/cli-args.ts +0 -134
  52. package/src/commands/__tests__/bash-command.test.ts +0 -445
  53. package/src/commands/__tests__/command-args.test.ts +0 -349
  54. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +0 -108
  55. package/src/commands/__tests__/copy-conversation.test.ts +0 -226
  56. package/src/commands/__tests__/diagnostics-command.test.ts +0 -35
  57. package/src/commands/__tests__/freebuff-command-aliases.test.ts +0 -56
  58. package/src/commands/__tests__/image.test.ts +0 -99
  59. package/src/commands/__tests__/init.test.ts +0 -456
  60. package/src/commands/__tests__/new-command-rotates-chat.test.ts +0 -91
  61. package/src/commands/__tests__/process-diagnostics.test.ts +0 -54
  62. package/src/commands/__tests__/prompt-builders.test.ts +0 -28
  63. package/src/commands/__tests__/router-input.test.ts +0 -298
  64. package/src/commands/ads.ts +0 -44
  65. package/src/commands/command-registry.ts +0 -726
  66. package/src/commands/copy-conversation.ts +0 -392
  67. package/src/commands/help.ts +0 -13
  68. package/src/commands/image.ts +0 -20
  69. package/src/commands/init.ts +0 -121
  70. package/src/commands/process-diagnostics.ts +0 -120
  71. package/src/commands/prompt-builders.ts +0 -100
  72. package/src/commands/publish.ts +0 -203
  73. package/src/commands/router-utils.ts +0 -80
  74. package/src/commands/router.ts +0 -487
  75. package/src/commands/usage.ts +0 -26
  76. package/src/components/__tests__/ad-banner.test.tsx +0 -83
  77. package/src/components/__tests__/chat-input-bar.test.tsx +0 -130
  78. package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +0 -91
  79. package/src/components/__tests__/freebuff-model-selector.test.tsx +0 -519
  80. package/src/components/__tests__/freebuff-offer-invariants.test.ts +0 -54
  81. package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +0 -212
  82. package/src/components/__tests__/grid-layout.integration.test.tsx +0 -304
  83. package/src/components/__tests__/grid-layout.test.tsx +0 -1051
  84. package/src/components/__tests__/message-block.completion.test.tsx +0 -104
  85. package/src/components/__tests__/message-block.streaming.test.tsx +0 -78
  86. package/src/components/__tests__/message-with-agents.test.tsx +0 -579
  87. package/src/components/__tests__/multiline-input.test.tsx +0 -1132
  88. package/src/components/__tests__/queue-panel.test.tsx +0 -398
  89. package/src/components/__tests__/selectable-list.test.ts +0 -232
  90. package/src/components/__tests__/status-bar.test.tsx +0 -44
  91. package/src/components/__tests__/status-indicator.test.tsx +0 -204
  92. package/src/components/__tests__/terminal-thinking-orb.test.tsx +0 -111
  93. package/src/components/__tests__/user-error-banner.test.tsx +0 -102
  94. package/src/components/ad-banner.tsx +0 -323
  95. package/src/components/agent-checklist.tsx +0 -350
  96. package/src/components/agent-mode-toggle.tsx +0 -241
  97. package/src/components/anvil-mark.tsx +0 -52
  98. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +0 -432
  99. package/src/components/ask-user/__tests__/validation.test.ts +0 -213
  100. package/src/components/ask-user/components/accordion-question.tsx +0 -153
  101. package/src/components/ask-user/components/custom-answer-input.tsx +0 -66
  102. package/src/components/ask-user/components/options-list.tsx +0 -133
  103. package/src/components/ask-user/components/question-header.tsx +0 -80
  104. package/src/components/ask-user/components/question-option.tsx +0 -89
  105. package/src/components/ask-user/constants.ts +0 -38
  106. package/src/components/ask-user/index.tsx +0 -631
  107. package/src/components/ask-user/utils/validation.ts +0 -85
  108. package/src/components/attachment-card.tsx +0 -65
  109. package/src/components/blocks/agent-block-grid.tsx +0 -58
  110. package/src/components/blocks/agent-branch-item.tsx +0 -311
  111. package/src/components/blocks/agent-branch-wrapper.tsx +0 -493
  112. package/src/components/blocks/agent-list-branch.tsx +0 -77
  113. package/src/components/blocks/ask-user-branch.tsx +0 -81
  114. package/src/components/blocks/block-helpers.ts +0 -11
  115. package/src/components/blocks/blocks-renderer.tsx +0 -289
  116. package/src/components/blocks/content-with-markdown.tsx +0 -33
  117. package/src/components/blocks/image-block.tsx +0 -128
  118. package/src/components/blocks/implementor-row.tsx +0 -429
  119. package/src/components/blocks/single-block.tsx +0 -201
  120. package/src/components/blocks/thinking-block.tsx +0 -67
  121. package/src/components/blocks/tool-block-group.tsx +0 -57
  122. package/src/components/blocks/tool-branch.tsx +0 -206
  123. package/src/components/blocks/user-content-copy.tsx +0 -157
  124. package/src/components/bottom-banner.tsx +0 -136
  125. package/src/components/build-mode-buttons.tsx +0 -110
  126. package/src/components/button.tsx +0 -66
  127. package/src/components/chat-header.tsx +0 -77
  128. package/src/components/chat-history-screen.tsx +0 -413
  129. package/src/components/chat-input-bar.tsx +0 -523
  130. package/src/components/clickable-title-box.tsx +0 -66
  131. package/src/components/clickable.tsx +0 -119
  132. package/src/components/collapse-button.tsx +0 -34
  133. package/src/components/copy-button.tsx +0 -203
  134. package/src/components/elapsed-timer.tsx +0 -55
  135. package/src/components/error-boundary.tsx +0 -55
  136. package/src/components/feedback-container.tsx +0 -181
  137. package/src/components/feedback-icon-button.tsx +0 -89
  138. package/src/components/feedback-input-mode.tsx +0 -344
  139. package/src/components/file-attachment-card.tsx +0 -98
  140. package/src/components/freebuff-active-session-summary.tsx +0 -63
  141. package/src/components/freebuff-landing-screen.tsx +0 -865
  142. package/src/components/freebuff-model-selector.tsx +0 -1087
  143. package/src/components/freebuff-referral-banner.tsx +0 -604
  144. package/src/components/freebuff-superseded-screen.tsx +0 -62
  145. package/src/components/grid-layout.tsx +0 -80
  146. package/src/components/help-banner.tsx +0 -127
  147. package/src/components/highlighted-text.tsx +0 -52
  148. package/src/components/image-card.tsx +0 -167
  149. package/src/components/image-thumbnail.tsx +0 -114
  150. package/src/components/input-cursor.tsx +0 -77
  151. package/src/components/input-mode-banner.tsx +0 -57
  152. package/src/components/load-previous-button.tsx +0 -48
  153. package/src/components/login-modal.tsx +0 -475
  154. package/src/components/message-block.tsx +0 -346
  155. package/src/components/message-footer.tsx +0 -258
  156. package/src/components/message-with-agents.tsx +0 -506
  157. package/src/components/mode-divider.tsx +0 -43
  158. package/src/components/multiline-input.tsx +0 -1241
  159. package/src/components/out-of-credits-banner.tsx +0 -160
  160. package/src/components/pending-attachments-banner.tsx +0 -116
  161. package/src/components/pending-bash-message.tsx +0 -59
  162. package/src/components/progress-bar.tsx +0 -81
  163. package/src/components/project-picker-screen.tsx +0 -485
  164. package/src/components/publish-confirmation.tsx +0 -493
  165. package/src/components/publish-container.tsx +0 -687
  166. package/src/components/queue-panel.tsx +0 -310
  167. package/src/components/raised-pill.tsx +0 -95
  168. package/src/components/renderers/plan-box.tsx +0 -59
  169. package/src/components/review-screen.tsx +0 -112
  170. package/src/components/scroll-to-bottom-button.tsx +0 -34
  171. package/src/components/segmented-control.tsx +0 -187
  172. package/src/components/selectable-list.tsx +0 -248
  173. package/src/components/selected-chips.tsx +0 -80
  174. package/src/components/separator.tsx +0 -40
  175. package/src/components/session-ended-banner.tsx +0 -226
  176. package/src/components/shimmer-text.tsx +0 -248
  177. package/src/components/status-bar.tsx +0 -313
  178. package/src/components/subscription-limit-banner.tsx +0 -206
  179. package/src/components/suggested-prompts.tsx +0 -184
  180. package/src/components/suggestion-menu.tsx +0 -215
  181. package/src/components/terminal-command-display.tsx +0 -151
  182. package/src/components/terminal-link.tsx +0 -97
  183. package/src/components/terminal-thinking-orb.tsx +0 -95
  184. package/src/components/text-attachment-card.tsx +0 -77
  185. package/src/components/thinking.tsx +0 -109
  186. package/src/components/tools/__tests__/apply-patch.test.tsx +0 -84
  187. package/src/components/tools/__tests__/code-search.test.tsx +0 -45
  188. package/src/components/tools/__tests__/gravity-index.test.ts +0 -76
  189. package/src/components/tools/__tests__/render-ui.test.tsx +0 -92
  190. package/src/components/tools/__tests__/run-terminal-command.test.ts +0 -233
  191. package/src/components/tools/apply-patch.tsx +0 -95
  192. package/src/components/tools/code-search.tsx +0 -45
  193. package/src/components/tools/composio.tsx +0 -152
  194. package/src/components/tools/diff-viewer.tsx +0 -76
  195. package/src/components/tools/glob.tsx +0 -61
  196. package/src/components/tools/gravity-index.tsx +0 -77
  197. package/src/components/tools/list-directory.tsx +0 -61
  198. package/src/components/tools/read-docs.tsx +0 -37
  199. package/src/components/tools/read-files.tsx +0 -91
  200. package/src/components/tools/read-subtree.tsx +0 -44
  201. package/src/components/tools/read-url.tsx +0 -33
  202. package/src/components/tools/registry.ts +0 -126
  203. package/src/components/tools/render-ui.tsx +0 -122
  204. package/src/components/tools/run-terminal-command.tsx +0 -79
  205. package/src/components/tools/skill.tsx +0 -29
  206. package/src/components/tools/str-replace.tsx +0 -77
  207. package/src/components/tools/suggest-followups.tsx +0 -369
  208. package/src/components/tools/task-completed.tsx +0 -16
  209. package/src/components/tools/tool-call-item.tsx +0 -273
  210. package/src/components/tools/types.ts +0 -55
  211. package/src/components/tools/web-search.tsx +0 -33
  212. package/src/components/tools/write-file.tsx +0 -16
  213. package/src/components/tools/write-todos.tsx +0 -95
  214. package/src/components/top-banner.tsx +0 -178
  215. package/src/components/usage-banner.tsx +0 -244
  216. package/src/components/user-error-banner.tsx +0 -56
  217. package/src/components/validation-error-popover.tsx +0 -192
  218. package/src/contexts/__tests__/chat-runtime-context.test.tsx +0 -149
  219. package/src/contexts/chat-runtime-context.tsx +0 -240
  220. package/src/data/slash-commands.test.ts +0 -24
  221. package/src/data/slash-commands.ts +0 -249
  222. package/src/entry.ts +0 -12
  223. package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +0 -49
  224. package/src/hooks/__tests__/session-fetch-signal.test.ts +0 -63
  225. package/src/hooks/__tests__/use-activity-query.test.ts +0 -1000
  226. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +0 -176
  227. package/src/hooks/__tests__/use-auth-query.test.ts +0 -21
  228. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +0 -66
  229. package/src/hooks/__tests__/use-clipboard.test.tsx +0 -73
  230. package/src/hooks/__tests__/use-connection-status.test.ts +0 -112
  231. package/src/hooks/__tests__/use-directory-browser.test.ts +0 -203
  232. package/src/hooks/__tests__/use-gravity-ad.test.ts +0 -73
  233. package/src/hooks/__tests__/use-grid-layout.test.ts +0 -349
  234. package/src/hooks/__tests__/use-input-history.test.ts +0 -699
  235. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +0 -209
  236. package/src/hooks/__tests__/use-message-queue.test.tsx +0 -71
  237. package/src/hooks/__tests__/use-path-tab-completion.test.ts +0 -315
  238. package/src/hooks/__tests__/use-queue-controls.test.ts +0 -68
  239. package/src/hooks/__tests__/use-queue-ui.test.tsx +0 -83
  240. package/src/hooks/__tests__/use-searchable-list.test.ts +0 -359
  241. package/src/hooks/__tests__/use-send-message-timer.test.ts +0 -146
  242. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +0 -361
  243. package/src/hooks/__tests__/use-suggestion-engine.test.ts +0 -485
  244. package/src/hooks/__tests__/use-terminal-focus.test.ts +0 -66
  245. package/src/hooks/__tests__/use-terminal-layout.test.ts +0 -675
  246. package/src/hooks/__tests__/use-timeout.test.ts +0 -330
  247. package/src/hooks/__tests__/use-usage-query.test.ts +0 -502
  248. package/src/hooks/__tests__/use-user-details-query.test.ts +0 -195
  249. package/src/hooks/helpers/__tests__/send-message.test.ts +0 -1918
  250. package/src/hooks/helpers/send-message.ts +0 -654
  251. package/src/hooks/stream-state.ts +0 -83
  252. package/src/hooks/use-activity-query.ts +0 -668
  253. package/src/hooks/use-agent-validation.ts +0 -74
  254. package/src/hooks/use-ask-user-bridge.ts +0 -92
  255. package/src/hooks/use-auth-query.ts +0 -253
  256. package/src/hooks/use-auth-state.ts +0 -152
  257. package/src/hooks/use-chat-input.ts +0 -105
  258. package/src/hooks/use-chat-keyboard.ts +0 -321
  259. package/src/hooks/use-chat-messages.ts +0 -254
  260. package/src/hooks/use-chat-state.ts +0 -165
  261. package/src/hooks/use-chat-streaming.ts +0 -180
  262. package/src/hooks/use-chat-ui.ts +0 -131
  263. package/src/hooks/use-clipboard.ts +0 -151
  264. package/src/hooks/use-connection-status.ts +0 -142
  265. package/src/hooks/use-directory-browser.ts +0 -88
  266. package/src/hooks/use-elapsed-time.ts +0 -116
  267. package/src/hooks/use-event.ts +0 -34
  268. package/src/hooks/use-exit-handler.ts +0 -66
  269. package/src/hooks/use-fetch-login-url.ts +0 -65
  270. package/src/hooks/use-fingerprint.ts +0 -61
  271. package/src/hooks/use-freebuff-ctrl-c-exit.ts +0 -23
  272. package/src/hooks/use-freebuff-session-progress.ts +0 -34
  273. package/src/hooks/use-freebuff-session.ts +0 -806
  274. package/src/hooks/use-freebuff-streak-query.ts +0 -61
  275. package/src/hooks/use-gravity-ad.ts +0 -656
  276. package/src/hooks/use-grid-layout.ts +0 -73
  277. package/src/hooks/use-input-history.ts +0 -173
  278. package/src/hooks/use-login-keyboard-handlers.ts +0 -75
  279. package/src/hooks/use-login-polling.ts +0 -114
  280. package/src/hooks/use-logo.tsx +0 -161
  281. package/src/hooks/use-message-queue.ts +0 -387
  282. package/src/hooks/use-now.ts +0 -20
  283. package/src/hooks/use-path-tab-completion.ts +0 -88
  284. package/src/hooks/use-publish-mutation.ts +0 -60
  285. package/src/hooks/use-queue-controls.ts +0 -68
  286. package/src/hooks/use-queue-ui.ts +0 -74
  287. package/src/hooks/use-scroll-management.ts +0 -184
  288. package/src/hooks/use-searchable-list.ts +0 -98
  289. package/src/hooks/use-send-message.ts +0 -773
  290. package/src/hooks/use-sheen-animation.tsx +0 -88
  291. package/src/hooks/use-subscription-query.ts +0 -71
  292. package/src/hooks/use-suggestion-engine.ts +0 -783
  293. package/src/hooks/use-terminal-breakpoints.ts +0 -77
  294. package/src/hooks/use-terminal-dimensions.ts +0 -46
  295. package/src/hooks/use-terminal-focus.ts +0 -29
  296. package/src/hooks/use-terminal-layout.ts +0 -189
  297. package/src/hooks/use-theme.tsx +0 -140
  298. package/src/hooks/use-timeout.ts +0 -85
  299. package/src/hooks/use-update-preference.ts +0 -66
  300. package/src/hooks/use-usage-monitor.ts +0 -55
  301. package/src/hooks/use-usage-query.ts +0 -124
  302. package/src/hooks/use-user-details-query.ts +0 -93
  303. package/src/hooks/use-why-did-you-update.ts +0 -177
  304. package/src/index.tsx +0 -441
  305. package/src/init/__tests__/init-direnv.test.ts +0 -526
  306. package/src/init/init-app.ts +0 -34
  307. package/src/init/init-direnv.ts +0 -133
  308. package/src/login/constants.ts +0 -60
  309. package/src/login/login-flow.ts +0 -204
  310. package/src/login/plain-login.ts +0 -108
  311. package/src/login/utils.ts +0 -183
  312. package/src/native/ripgrep.ts +0 -80
  313. package/src/polyfills/bun-strip-ansi.ts +0 -12
  314. package/src/pre-init/tree-sitter-wasm.ts +0 -96
  315. package/src/project-files.ts +0 -102
  316. package/src/smoke/terminal-command-broker.ts +0 -439
  317. package/src/state/__tests__/chat-store-focus.test.ts +0 -22
  318. package/src/state/__tests__/feedback-store.test.ts +0 -257
  319. package/src/state/chat-history-store.ts +0 -30
  320. package/src/state/chat-store.ts +0 -544
  321. package/src/state/feedback-store.ts +0 -160
  322. package/src/state/freebuff-model-store.ts +0 -42
  323. package/src/state/freebuff-session-store.ts +0 -53
  324. package/src/state/login-store.ts +0 -150
  325. package/src/state/message-block-store.ts +0 -149
  326. package/src/state/publish-store.ts +0 -146
  327. package/src/state/queue-panel-store.ts +0 -26
  328. package/src/state/review-store.ts +0 -24
  329. package/src/testing/env.ts +0 -46
  330. package/src/types/chat-state.ts +0 -15
  331. package/src/types/chat.ts +0 -241
  332. package/src/types/contracts/send-message.ts +0 -12
  333. package/src/types/env.ts +0 -98
  334. package/src/types/freebuff-session.ts +0 -17
  335. package/src/types/function-params.ts +0 -28
  336. package/src/types/react19-compat.d.ts +0 -19
  337. package/src/types/store.ts +0 -111
  338. package/src/types/theme-system.ts +0 -144
  339. package/src/types/utils.ts +0 -3
  340. package/src/utils/__tests__/active-run.test.ts +0 -140
  341. package/src/utils/__tests__/activity-tracker.test.ts +0 -440
  342. package/src/utils/__tests__/agent-display.test.ts +0 -139
  343. package/src/utils/__tests__/analytics-client.test.ts +0 -262
  344. package/src/utils/__tests__/anonymous-id.test.ts +0 -74
  345. package/src/utils/__tests__/anvil-api.test.ts +0 -578
  346. package/src/utils/__tests__/arrays.test.ts +0 -264
  347. package/src/utils/__tests__/bash-context-processor.test.ts +0 -38
  348. package/src/utils/__tests__/block-processor.test.ts +0 -810
  349. package/src/utils/__tests__/chat-history.test.ts +0 -203
  350. package/src/utils/__tests__/chat-input-key-intercept.test.ts +0 -40
  351. package/src/utils/__tests__/chat-meta.test.ts +0 -130
  352. package/src/utils/__tests__/clipboard-linux.test.ts +0 -359
  353. package/src/utils/__tests__/clipboard.test.ts +0 -910
  354. package/src/utils/__tests__/code-search-summary.test.ts +0 -84
  355. package/src/utils/__tests__/collapse-helpers.test.ts +0 -1109
  356. package/src/utils/__tests__/error-handling.test.ts +0 -658
  357. package/src/utils/__tests__/exit-cleanly.test.ts +0 -110
  358. package/src/utils/__tests__/feedback-helpers.test.ts +0 -444
  359. package/src/utils/__tests__/feedback-submission.test.ts +0 -26
  360. package/src/utils/__tests__/fetch-usage.test.ts +0 -355
  361. package/src/utils/__tests__/fingerprint.test.ts +0 -144
  362. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +0 -7
  363. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +0 -3
  364. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +0 -33
  365. package/src/utils/__tests__/format-elapsed-time.test.ts +0 -103
  366. package/src/utils/__tests__/format-timeout.test.ts +0 -87
  367. package/src/utils/__tests__/freebuff-instance-owner.test.ts +0 -69
  368. package/src/utils/__tests__/freebuff-model-navigation.test.ts +0 -102
  369. package/src/utils/__tests__/freebuff-premium-reset.test.ts +0 -79
  370. package/src/utils/__tests__/freebuff-referral-cache.test.ts +0 -116
  371. package/src/utils/__tests__/freebuff-session-api.test.ts +0 -215
  372. package/src/utils/__tests__/freebuff-session-display.test.ts +0 -23
  373. package/src/utils/__tests__/freebuff-streak-line.test.ts +0 -217
  374. package/src/utils/__tests__/image-dimensions.test.ts +0 -249
  375. package/src/utils/__tests__/image-processor.test.ts +0 -209
  376. package/src/utils/__tests__/implementor-helpers.test.ts +0 -1458
  377. package/src/utils/__tests__/keyboard-actions.test.ts +0 -660
  378. package/src/utils/__tests__/layout-helpers.test.ts +0 -36
  379. package/src/utils/__tests__/lazy-response-ads.test.ts +0 -167
  380. package/src/utils/__tests__/log-shipper.test.ts +0 -53
  381. package/src/utils/__tests__/markdown-renderer.test.tsx +0 -421
  382. package/src/utils/__tests__/message-block-helpers.test.ts +0 -1263
  383. package/src/utils/__tests__/message-updater.test.ts +0 -645
  384. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +0 -172
  385. package/src/utils/__tests__/pending-attachments.test.ts +0 -277
  386. package/src/utils/__tests__/polling-backoff.test.ts +0 -51
  387. package/src/utils/__tests__/queue-panel-actions.test.ts +0 -148
  388. package/src/utils/__tests__/run-state-storage.test.ts +0 -919
  389. package/src/utils/__tests__/safe-json.test.ts +0 -77
  390. package/src/utils/__tests__/sdk-event-handlers.test.ts +0 -685
  391. package/src/utils/__tests__/send-message-helpers.test.ts +0 -1898
  392. package/src/utils/__tests__/send-message-timer.test.ts +0 -64
  393. package/src/utils/__tests__/settings.test.ts +0 -99
  394. package/src/utils/__tests__/strings.test.ts +0 -192
  395. package/src/utils/__tests__/terminal-color-detection.test.ts +0 -425
  396. package/src/utils/__tests__/terminal-command-broker.test.ts +0 -528
  397. package/src/utils/__tests__/terminal-enter-detection.test.ts +0 -77
  398. package/src/utils/__tests__/terminal-io.test.ts +0 -137
  399. package/src/utils/__tests__/terminal-protocol-controller.test.ts +0 -119
  400. package/src/utils/__tests__/text-layout.test.ts +0 -82
  401. package/src/utils/__tests__/theme-platform-detection.test.ts +0 -125
  402. package/src/utils/__tests__/think-tag-parser.test.ts +0 -159
  403. package/src/utils/__tests__/trace-writer.test.ts +0 -223
  404. package/src/utils/__tests__/trim-chat-logs.test.ts +0 -78
  405. package/src/utils/__tests__/usage-banner-state.test.ts +0 -298
  406. package/src/utils/__tests__/validation-error-formatting.test.ts +0 -126
  407. package/src/utils/__tests__/windows-terminal-health.test.ts +0 -101
  408. package/src/utils/__tests__/write-file-atomic.test.ts +0 -127
  409. package/src/utils/active-run.ts +0 -131
  410. package/src/utils/activity-tracker.ts +0 -66
  411. package/src/utils/agent-display.ts +0 -87
  412. package/src/utils/agent-helpers.ts +0 -27
  413. package/src/utils/agent-id-utils.ts +0 -14
  414. package/src/utils/ai-message-id.ts +0 -12
  415. package/src/utils/analytics.ts +0 -361
  416. package/src/utils/anonymous-id.ts +0 -87
  417. package/src/utils/anvil-api.ts +0 -612
  418. package/src/utils/anvil-client.ts +0 -217
  419. package/src/utils/arrays.ts +0 -117
  420. package/src/utils/auth.ts +0 -258
  421. package/src/utils/bash-context-processor.ts +0 -47
  422. package/src/utils/bash-messages.ts +0 -108
  423. package/src/utils/block-margins.ts +0 -35
  424. package/src/utils/block-operations.ts +0 -523
  425. package/src/utils/block-processor.ts +0 -213
  426. package/src/utils/chat-history.ts +0 -253
  427. package/src/utils/chat-input-key-intercept.ts +0 -52
  428. package/src/utils/chat-meta.ts +0 -90
  429. package/src/utils/chat-scroll-accel.ts +0 -151
  430. package/src/utils/clipboard-image.ts +0 -588
  431. package/src/utils/clipboard.ts +0 -397
  432. package/src/utils/code-search-summary.ts +0 -70
  433. package/src/utils/collapse-helpers.ts +0 -256
  434. package/src/utils/config-dir.ts +0 -24
  435. package/src/utils/constants.ts +0 -190
  436. package/src/utils/create-event-handler-state.ts +0 -73
  437. package/src/utils/create-run-config.ts +0 -121
  438. package/src/utils/detect-shell.ts +0 -116
  439. package/src/utils/directory-browser.ts +0 -72
  440. package/src/utils/engagement.ts +0 -55
  441. package/src/utils/env.ts +0 -92
  442. package/src/utils/error-handling.ts +0 -255
  443. package/src/utils/error-messages.ts +0 -62
  444. package/src/utils/exit-cleanly.ts +0 -86
  445. package/src/utils/feedback-helpers.ts +0 -103
  446. package/src/utils/feedback-submission.ts +0 -22
  447. package/src/utils/fetch-usage.ts +0 -76
  448. package/src/utils/fingerprint.ts +0 -245
  449. package/src/utils/format-elapsed-time.ts +0 -33
  450. package/src/utils/format-session-units.ts +0 -6
  451. package/src/utils/format-timeout.ts +0 -28
  452. package/src/utils/format-validation-errors-for-message.ts +0 -84
  453. package/src/utils/freebuff-agent-selection.ts +0 -33
  454. package/src/utils/freebuff-instance-owner.ts +0 -66
  455. package/src/utils/freebuff-model-navigation.ts +0 -50
  456. package/src/utils/freebuff-premium-reset.ts +0 -55
  457. package/src/utils/freebuff-referral-cache.ts +0 -51
  458. package/src/utils/freebuff-session-api.ts +0 -243
  459. package/src/utils/freebuff-session-display.ts +0 -21
  460. package/src/utils/freebuff-streak-line.ts +0 -93
  461. package/src/utils/git.ts +0 -17
  462. package/src/utils/helpers.ts +0 -47
  463. package/src/utils/image-display.ts +0 -67
  464. package/src/utils/image-handler.ts +0 -325
  465. package/src/utils/image-processor.ts +0 -133
  466. package/src/utils/image-thumbnail.ts +0 -78
  467. package/src/utils/implementor-helpers.ts +0 -787
  468. package/src/utils/input-modes.ts +0 -174
  469. package/src/utils/keyboard-actions.ts +0 -392
  470. package/src/utils/keypad-keys.ts +0 -47
  471. package/src/utils/layout-helpers.ts +0 -33
  472. package/src/utils/lazy-response-ads.ts +0 -1
  473. package/src/utils/local-agent-registry.ts +0 -474
  474. package/src/utils/log-shipper.ts +0 -126
  475. package/src/utils/logger.ts +0 -327
  476. package/src/utils/markdown-renderer.tsx +0 -1090
  477. package/src/utils/math.ts +0 -3
  478. package/src/utils/message-block-helpers.ts +0 -684
  479. package/src/utils/message-history.ts +0 -143
  480. package/src/utils/message-tree-utils.ts +0 -67
  481. package/src/utils/message-updater.ts +0 -279
  482. package/src/utils/open-file.ts +0 -146
  483. package/src/utils/open-url.ts +0 -63
  484. package/src/utils/path-completion.ts +0 -111
  485. package/src/utils/path-helpers.ts +0 -38
  486. package/src/utils/pending-attachments.ts +0 -360
  487. package/src/utils/polling-backoff.ts +0 -59
  488. package/src/utils/project-picker.ts +0 -12
  489. package/src/utils/queue-panel-actions.ts +0 -70
  490. package/src/utils/recent-projects.ts +0 -156
  491. package/src/utils/renderer-cleanup.ts +0 -233
  492. package/src/utils/response-ad-positions.ts +0 -1
  493. package/src/utils/run-state-storage.ts +0 -603
  494. package/src/utils/safe-json.ts +0 -153
  495. package/src/utils/sdk-event-handlers.ts +0 -571
  496. package/src/utils/send-message-helpers.ts +0 -210
  497. package/src/utils/send-message-timer.ts +0 -110
  498. package/src/utils/settings.ts +0 -226
  499. package/src/utils/skill-registry.ts +0 -94
  500. package/src/utils/spawn-agent-matcher.ts +0 -55
  501. package/src/utils/status-indicator-state.ts +0 -107
  502. package/src/utils/stream-chunk-processor.ts +0 -57
  503. package/src/utils/strings.ts +0 -271
  504. package/src/utils/subscription.ts +0 -31
  505. package/src/utils/syntax-highlighter.tsx +0 -19
  506. package/src/utils/terminal-color-detection.ts +0 -474
  507. package/src/utils/terminal-command-broker.ts +0 -467
  508. package/src/utils/terminal-enter-detection.ts +0 -63
  509. package/src/utils/terminal-images.ts +0 -229
  510. package/src/utils/terminal-io.ts +0 -85
  511. package/src/utils/terminal-protocol-controller.ts +0 -173
  512. package/src/utils/terminal-reset-sequences.ts +0 -33
  513. package/src/utils/terminal-title.ts +0 -84
  514. package/src/utils/terminal-watchdog.ts +0 -366
  515. package/src/utils/text-layout.ts +0 -149
  516. package/src/utils/theme-config.ts +0 -141
  517. package/src/utils/theme-system.ts +0 -1206
  518. package/src/utils/think-tag-parser.ts +0 -95
  519. package/src/utils/time-format.ts +0 -21
  520. package/src/utils/trace-writer.ts +0 -162
  521. package/src/utils/ui-constants.ts +0 -69
  522. package/src/utils/update-checker.ts +0 -110
  523. package/src/utils/usage-banner-state.ts +0 -146
  524. package/src/utils/validation-error-formatting.ts +0 -86
  525. package/src/utils/validation-error-helpers.ts +0 -19
  526. package/src/utils/windows-terminal-health.ts +0 -124
  527. package/src/utils/word-wrap-utils.ts +0 -53
  528. package/src/utils/write-file-atomic.ts +0 -54
  529. 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
- }