@alumbwe/anvil 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. package/README.md +84 -0
  2. package/bin/anvil +2 -0
  3. package/bin/tree-sitter.wasm +0 -0
  4. package/package.json +67 -0
  5. package/src/__tests__/README.md +256 -0
  6. package/src/__tests__/bash-mode.test.ts +442 -0
  7. package/src/__tests__/cli-args.test.ts +181 -0
  8. package/src/__tests__/cli-harness.test.ts +52 -0
  9. package/src/__tests__/e2e/first-time-login.test.ts +173 -0
  10. package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
  11. package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
  12. package/src/__tests__/e2e-cli.test.ts +183 -0
  13. package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
  14. package/src/__tests__/helpers/mock-api-client.ts +64 -0
  15. package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
  16. package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
  17. package/src/__tests__/home-directory-detection.test.ts +213 -0
  18. package/src/__tests__/integration/api-integration.test.ts +288 -0
  19. package/src/__tests__/integration/credentials-storage.test.ts +486 -0
  20. package/src/__tests__/integration/local-agents.test.ts +1187 -0
  21. package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
  22. package/src/__tests__/integration/login-polling-working.test.ts +285 -0
  23. package/src/__tests__/integration/queue-editing.test.tsx +261 -0
  24. package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
  25. package/src/__tests__/integration-tmux.test.ts +188 -0
  26. package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
  27. package/src/__tests__/launcher-disconnect.test.ts +104 -0
  28. package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
  29. package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
  30. package/src/__tests__/path-completion.test.ts +243 -0
  31. package/src/__tests__/project-files-chat-id.test.ts +34 -0
  32. package/src/__tests__/release/proxy-http-get.test.ts +667 -0
  33. package/src/__tests__/release/wrapper-safety.test.ts +412 -0
  34. package/src/__tests__/renderer-cleanup.test.ts +174 -0
  35. package/src/__tests__/rerender-perf.integration.test.ts +283 -0
  36. package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
  37. package/src/__tests__/terminal-watchdog.test.ts +388 -0
  38. package/src/__tests__/test-utils.ts +271 -0
  39. package/src/__tests__/tmux-poc.ts +150 -0
  40. package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
  41. package/src/__tests__/unit/copy-button.test.ts +157 -0
  42. package/src/__tests__/unit/create-run-config.test.ts +66 -0
  43. package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
  44. package/src/__tests__/unit/segmented-control.test.ts +152 -0
  45. package/src/__tests__/utils/env.test.ts +169 -0
  46. package/src/__tests__/utils/project-picker.test.ts +39 -0
  47. package/src/agents/bundled-agents.generated.d.ts +14 -0
  48. package/src/agents/bundled-agents.generated.ts +3692 -0
  49. package/src/app.tsx +399 -0
  50. package/src/chat.tsx +1698 -0
  51. package/src/cli-args.ts +134 -0
  52. package/src/commands/__tests__/bash-command.test.ts +445 -0
  53. package/src/commands/__tests__/command-args.test.ts +349 -0
  54. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
  55. package/src/commands/__tests__/copy-conversation.test.ts +226 -0
  56. package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
  57. package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
  58. package/src/commands/__tests__/image.test.ts +99 -0
  59. package/src/commands/__tests__/init.test.ts +456 -0
  60. package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
  61. package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
  62. package/src/commands/__tests__/prompt-builders.test.ts +28 -0
  63. package/src/commands/__tests__/router-input.test.ts +298 -0
  64. package/src/commands/ads.ts +44 -0
  65. package/src/commands/command-registry.ts +726 -0
  66. package/src/commands/copy-conversation.ts +392 -0
  67. package/src/commands/help.ts +13 -0
  68. package/src/commands/image.ts +20 -0
  69. package/src/commands/init.ts +121 -0
  70. package/src/commands/process-diagnostics.ts +120 -0
  71. package/src/commands/prompt-builders.ts +100 -0
  72. package/src/commands/publish.ts +203 -0
  73. package/src/commands/router-utils.ts +80 -0
  74. package/src/commands/router.ts +487 -0
  75. package/src/commands/usage.ts +26 -0
  76. package/src/components/__tests__/ad-banner.test.tsx +83 -0
  77. package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
  78. package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
  79. package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
  80. package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
  81. package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
  82. package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
  83. package/src/components/__tests__/grid-layout.test.tsx +1051 -0
  84. package/src/components/__tests__/message-block.completion.test.tsx +104 -0
  85. package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
  86. package/src/components/__tests__/message-with-agents.test.tsx +579 -0
  87. package/src/components/__tests__/multiline-input.test.tsx +1132 -0
  88. package/src/components/__tests__/queue-panel.test.tsx +398 -0
  89. package/src/components/__tests__/selectable-list.test.ts +232 -0
  90. package/src/components/__tests__/status-bar.test.tsx +44 -0
  91. package/src/components/__tests__/status-indicator.test.tsx +204 -0
  92. package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
  93. package/src/components/__tests__/user-error-banner.test.tsx +102 -0
  94. package/src/components/ad-banner.tsx +323 -0
  95. package/src/components/agent-checklist.tsx +350 -0
  96. package/src/components/agent-mode-toggle.tsx +241 -0
  97. package/src/components/anvil-mark.tsx +52 -0
  98. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
  99. package/src/components/ask-user/__tests__/validation.test.ts +213 -0
  100. package/src/components/ask-user/components/accordion-question.tsx +153 -0
  101. package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
  102. package/src/components/ask-user/components/options-list.tsx +133 -0
  103. package/src/components/ask-user/components/question-header.tsx +80 -0
  104. package/src/components/ask-user/components/question-option.tsx +89 -0
  105. package/src/components/ask-user/constants.ts +38 -0
  106. package/src/components/ask-user/index.tsx +631 -0
  107. package/src/components/ask-user/utils/validation.ts +85 -0
  108. package/src/components/attachment-card.tsx +65 -0
  109. package/src/components/blocks/agent-block-grid.tsx +58 -0
  110. package/src/components/blocks/agent-branch-item.tsx +311 -0
  111. package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
  112. package/src/components/blocks/agent-list-branch.tsx +77 -0
  113. package/src/components/blocks/ask-user-branch.tsx +81 -0
  114. package/src/components/blocks/block-helpers.ts +11 -0
  115. package/src/components/blocks/blocks-renderer.tsx +289 -0
  116. package/src/components/blocks/content-with-markdown.tsx +33 -0
  117. package/src/components/blocks/image-block.tsx +128 -0
  118. package/src/components/blocks/implementor-row.tsx +429 -0
  119. package/src/components/blocks/single-block.tsx +201 -0
  120. package/src/components/blocks/thinking-block.tsx +67 -0
  121. package/src/components/blocks/tool-block-group.tsx +57 -0
  122. package/src/components/blocks/tool-branch.tsx +206 -0
  123. package/src/components/blocks/user-content-copy.tsx +157 -0
  124. package/src/components/bottom-banner.tsx +136 -0
  125. package/src/components/build-mode-buttons.tsx +110 -0
  126. package/src/components/button.tsx +66 -0
  127. package/src/components/chat-header.tsx +77 -0
  128. package/src/components/chat-history-screen.tsx +413 -0
  129. package/src/components/chat-input-bar.tsx +523 -0
  130. package/src/components/clickable-title-box.tsx +66 -0
  131. package/src/components/clickable.tsx +119 -0
  132. package/src/components/collapse-button.tsx +34 -0
  133. package/src/components/copy-button.tsx +203 -0
  134. package/src/components/elapsed-timer.tsx +55 -0
  135. package/src/components/error-boundary.tsx +55 -0
  136. package/src/components/feedback-container.tsx +181 -0
  137. package/src/components/feedback-icon-button.tsx +89 -0
  138. package/src/components/feedback-input-mode.tsx +344 -0
  139. package/src/components/file-attachment-card.tsx +98 -0
  140. package/src/components/freebuff-active-session-summary.tsx +63 -0
  141. package/src/components/freebuff-landing-screen.tsx +865 -0
  142. package/src/components/freebuff-model-selector.tsx +1087 -0
  143. package/src/components/freebuff-referral-banner.tsx +604 -0
  144. package/src/components/freebuff-superseded-screen.tsx +62 -0
  145. package/src/components/grid-layout.tsx +80 -0
  146. package/src/components/help-banner.tsx +127 -0
  147. package/src/components/highlighted-text.tsx +52 -0
  148. package/src/components/image-card.tsx +167 -0
  149. package/src/components/image-thumbnail.tsx +114 -0
  150. package/src/components/input-cursor.tsx +77 -0
  151. package/src/components/input-mode-banner.tsx +57 -0
  152. package/src/components/load-previous-button.tsx +48 -0
  153. package/src/components/login-modal.tsx +475 -0
  154. package/src/components/message-block.tsx +346 -0
  155. package/src/components/message-footer.tsx +258 -0
  156. package/src/components/message-with-agents.tsx +506 -0
  157. package/src/components/mode-divider.tsx +43 -0
  158. package/src/components/multiline-input.tsx +1241 -0
  159. package/src/components/out-of-credits-banner.tsx +160 -0
  160. package/src/components/pending-attachments-banner.tsx +116 -0
  161. package/src/components/pending-bash-message.tsx +59 -0
  162. package/src/components/progress-bar.tsx +81 -0
  163. package/src/components/project-picker-screen.tsx +485 -0
  164. package/src/components/publish-confirmation.tsx +493 -0
  165. package/src/components/publish-container.tsx +687 -0
  166. package/src/components/queue-panel.tsx +310 -0
  167. package/src/components/raised-pill.tsx +95 -0
  168. package/src/components/renderers/plan-box.tsx +59 -0
  169. package/src/components/review-screen.tsx +112 -0
  170. package/src/components/scroll-to-bottom-button.tsx +34 -0
  171. package/src/components/segmented-control.tsx +187 -0
  172. package/src/components/selectable-list.tsx +248 -0
  173. package/src/components/selected-chips.tsx +80 -0
  174. package/src/components/separator.tsx +40 -0
  175. package/src/components/session-ended-banner.tsx +226 -0
  176. package/src/components/shimmer-text.tsx +248 -0
  177. package/src/components/status-bar.tsx +313 -0
  178. package/src/components/subscription-limit-banner.tsx +206 -0
  179. package/src/components/suggested-prompts.tsx +184 -0
  180. package/src/components/suggestion-menu.tsx +215 -0
  181. package/src/components/terminal-command-display.tsx +151 -0
  182. package/src/components/terminal-link.tsx +97 -0
  183. package/src/components/terminal-thinking-orb.tsx +95 -0
  184. package/src/components/text-attachment-card.tsx +77 -0
  185. package/src/components/thinking.tsx +109 -0
  186. package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
  187. package/src/components/tools/__tests__/code-search.test.tsx +45 -0
  188. package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
  189. package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
  190. package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
  191. package/src/components/tools/apply-patch.tsx +95 -0
  192. package/src/components/tools/code-search.tsx +45 -0
  193. package/src/components/tools/composio.tsx +152 -0
  194. package/src/components/tools/diff-viewer.tsx +76 -0
  195. package/src/components/tools/glob.tsx +61 -0
  196. package/src/components/tools/gravity-index.tsx +77 -0
  197. package/src/components/tools/list-directory.tsx +61 -0
  198. package/src/components/tools/read-docs.tsx +37 -0
  199. package/src/components/tools/read-files.tsx +91 -0
  200. package/src/components/tools/read-subtree.tsx +44 -0
  201. package/src/components/tools/read-url.tsx +33 -0
  202. package/src/components/tools/registry.ts +126 -0
  203. package/src/components/tools/render-ui.tsx +122 -0
  204. package/src/components/tools/run-terminal-command.tsx +79 -0
  205. package/src/components/tools/skill.tsx +29 -0
  206. package/src/components/tools/str-replace.tsx +77 -0
  207. package/src/components/tools/suggest-followups.tsx +369 -0
  208. package/src/components/tools/task-completed.tsx +16 -0
  209. package/src/components/tools/tool-call-item.tsx +273 -0
  210. package/src/components/tools/types.ts +55 -0
  211. package/src/components/tools/web-search.tsx +33 -0
  212. package/src/components/tools/write-file.tsx +16 -0
  213. package/src/components/tools/write-todos.tsx +95 -0
  214. package/src/components/top-banner.tsx +178 -0
  215. package/src/components/usage-banner.tsx +244 -0
  216. package/src/components/user-error-banner.tsx +56 -0
  217. package/src/components/validation-error-popover.tsx +192 -0
  218. package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
  219. package/src/contexts/chat-runtime-context.tsx +240 -0
  220. package/src/data/slash-commands.test.ts +24 -0
  221. package/src/data/slash-commands.ts +249 -0
  222. package/src/entry.ts +12 -0
  223. package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
  224. package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
  225. package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
  226. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
  227. package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
  228. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
  229. package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
  230. package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
  231. package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
  232. package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
  233. package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
  234. package/src/hooks/__tests__/use-input-history.test.ts +699 -0
  235. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
  236. package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
  237. package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
  238. package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
  239. package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
  240. package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
  241. package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
  242. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
  243. package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
  244. package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
  245. package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
  246. package/src/hooks/__tests__/use-timeout.test.ts +330 -0
  247. package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
  248. package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
  249. package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
  250. package/src/hooks/helpers/send-message.ts +654 -0
  251. package/src/hooks/stream-state.ts +83 -0
  252. package/src/hooks/use-activity-query.ts +668 -0
  253. package/src/hooks/use-agent-validation.ts +74 -0
  254. package/src/hooks/use-ask-user-bridge.ts +92 -0
  255. package/src/hooks/use-auth-query.ts +253 -0
  256. package/src/hooks/use-auth-state.ts +152 -0
  257. package/src/hooks/use-chat-input.ts +105 -0
  258. package/src/hooks/use-chat-keyboard.ts +321 -0
  259. package/src/hooks/use-chat-messages.ts +254 -0
  260. package/src/hooks/use-chat-state.ts +165 -0
  261. package/src/hooks/use-chat-streaming.ts +180 -0
  262. package/src/hooks/use-chat-ui.ts +131 -0
  263. package/src/hooks/use-clipboard.ts +151 -0
  264. package/src/hooks/use-connection-status.ts +142 -0
  265. package/src/hooks/use-directory-browser.ts +88 -0
  266. package/src/hooks/use-elapsed-time.ts +116 -0
  267. package/src/hooks/use-event.ts +34 -0
  268. package/src/hooks/use-exit-handler.ts +66 -0
  269. package/src/hooks/use-fetch-login-url.ts +65 -0
  270. package/src/hooks/use-fingerprint.ts +61 -0
  271. package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
  272. package/src/hooks/use-freebuff-session-progress.ts +34 -0
  273. package/src/hooks/use-freebuff-session.ts +806 -0
  274. package/src/hooks/use-freebuff-streak-query.ts +61 -0
  275. package/src/hooks/use-gravity-ad.ts +656 -0
  276. package/src/hooks/use-grid-layout.ts +73 -0
  277. package/src/hooks/use-input-history.ts +173 -0
  278. package/src/hooks/use-login-keyboard-handlers.ts +75 -0
  279. package/src/hooks/use-login-polling.ts +114 -0
  280. package/src/hooks/use-logo.tsx +161 -0
  281. package/src/hooks/use-message-queue.ts +387 -0
  282. package/src/hooks/use-now.ts +20 -0
  283. package/src/hooks/use-path-tab-completion.ts +88 -0
  284. package/src/hooks/use-publish-mutation.ts +60 -0
  285. package/src/hooks/use-queue-controls.ts +68 -0
  286. package/src/hooks/use-queue-ui.ts +74 -0
  287. package/src/hooks/use-scroll-management.ts +184 -0
  288. package/src/hooks/use-searchable-list.ts +98 -0
  289. package/src/hooks/use-send-message.ts +773 -0
  290. package/src/hooks/use-sheen-animation.tsx +88 -0
  291. package/src/hooks/use-subscription-query.ts +71 -0
  292. package/src/hooks/use-suggestion-engine.ts +783 -0
  293. package/src/hooks/use-terminal-breakpoints.ts +77 -0
  294. package/src/hooks/use-terminal-dimensions.ts +46 -0
  295. package/src/hooks/use-terminal-focus.ts +29 -0
  296. package/src/hooks/use-terminal-layout.ts +189 -0
  297. package/src/hooks/use-theme.tsx +140 -0
  298. package/src/hooks/use-timeout.ts +85 -0
  299. package/src/hooks/use-update-preference.ts +66 -0
  300. package/src/hooks/use-usage-monitor.ts +55 -0
  301. package/src/hooks/use-usage-query.ts +124 -0
  302. package/src/hooks/use-user-details-query.ts +93 -0
  303. package/src/hooks/use-why-did-you-update.ts +177 -0
  304. package/src/index.tsx +441 -0
  305. package/src/init/__tests__/init-direnv.test.ts +526 -0
  306. package/src/init/init-app.ts +34 -0
  307. package/src/init/init-direnv.ts +133 -0
  308. package/src/login/constants.ts +60 -0
  309. package/src/login/login-flow.ts +204 -0
  310. package/src/login/plain-login.ts +108 -0
  311. package/src/login/utils.ts +183 -0
  312. package/src/native/ripgrep.ts +80 -0
  313. package/src/polyfills/bun-strip-ansi.ts +12 -0
  314. package/src/pre-init/tree-sitter-wasm.ts +96 -0
  315. package/src/project-files.ts +102 -0
  316. package/src/smoke/terminal-command-broker.ts +439 -0
  317. package/src/state/__tests__/chat-store-focus.test.ts +22 -0
  318. package/src/state/__tests__/feedback-store.test.ts +257 -0
  319. package/src/state/chat-history-store.ts +30 -0
  320. package/src/state/chat-store.ts +544 -0
  321. package/src/state/feedback-store.ts +160 -0
  322. package/src/state/freebuff-model-store.ts +42 -0
  323. package/src/state/freebuff-session-store.ts +53 -0
  324. package/src/state/login-store.ts +150 -0
  325. package/src/state/message-block-store.ts +149 -0
  326. package/src/state/publish-store.ts +146 -0
  327. package/src/state/queue-panel-store.ts +26 -0
  328. package/src/state/review-store.ts +24 -0
  329. package/src/testing/env.ts +46 -0
  330. package/src/types/chat-state.ts +15 -0
  331. package/src/types/chat.ts +241 -0
  332. package/src/types/contracts/send-message.ts +12 -0
  333. package/src/types/env.ts +98 -0
  334. package/src/types/freebuff-session.ts +17 -0
  335. package/src/types/function-params.ts +28 -0
  336. package/src/types/react19-compat.d.ts +19 -0
  337. package/src/types/store.ts +111 -0
  338. package/src/types/theme-system.ts +144 -0
  339. package/src/types/utils.ts +3 -0
  340. package/src/utils/__tests__/active-run.test.ts +140 -0
  341. package/src/utils/__tests__/activity-tracker.test.ts +440 -0
  342. package/src/utils/__tests__/agent-display.test.ts +139 -0
  343. package/src/utils/__tests__/analytics-client.test.ts +262 -0
  344. package/src/utils/__tests__/anonymous-id.test.ts +74 -0
  345. package/src/utils/__tests__/anvil-api.test.ts +578 -0
  346. package/src/utils/__tests__/arrays.test.ts +264 -0
  347. package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
  348. package/src/utils/__tests__/block-processor.test.ts +810 -0
  349. package/src/utils/__tests__/chat-history.test.ts +203 -0
  350. package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
  351. package/src/utils/__tests__/chat-meta.test.ts +130 -0
  352. package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
  353. package/src/utils/__tests__/clipboard.test.ts +910 -0
  354. package/src/utils/__tests__/code-search-summary.test.ts +84 -0
  355. package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
  356. package/src/utils/__tests__/error-handling.test.ts +658 -0
  357. package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
  358. package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
  359. package/src/utils/__tests__/feedback-submission.test.ts +26 -0
  360. package/src/utils/__tests__/fetch-usage.test.ts +355 -0
  361. package/src/utils/__tests__/fingerprint.test.ts +144 -0
  362. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
  363. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
  364. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
  365. package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
  366. package/src/utils/__tests__/format-timeout.test.ts +87 -0
  367. package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
  368. package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
  369. package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
  370. package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
  371. package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
  372. package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
  373. package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
  374. package/src/utils/__tests__/image-dimensions.test.ts +249 -0
  375. package/src/utils/__tests__/image-processor.test.ts +209 -0
  376. package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
  377. package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
  378. package/src/utils/__tests__/layout-helpers.test.ts +36 -0
  379. package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
  380. package/src/utils/__tests__/log-shipper.test.ts +53 -0
  381. package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
  382. package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
  383. package/src/utils/__tests__/message-updater.test.ts +645 -0
  384. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
  385. package/src/utils/__tests__/pending-attachments.test.ts +277 -0
  386. package/src/utils/__tests__/polling-backoff.test.ts +51 -0
  387. package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
  388. package/src/utils/__tests__/run-state-storage.test.ts +919 -0
  389. package/src/utils/__tests__/safe-json.test.ts +77 -0
  390. package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
  391. package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
  392. package/src/utils/__tests__/send-message-timer.test.ts +64 -0
  393. package/src/utils/__tests__/settings.test.ts +99 -0
  394. package/src/utils/__tests__/strings.test.ts +192 -0
  395. package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
  396. package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
  397. package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
  398. package/src/utils/__tests__/terminal-io.test.ts +137 -0
  399. package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
  400. package/src/utils/__tests__/text-layout.test.ts +82 -0
  401. package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
  402. package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
  403. package/src/utils/__tests__/trace-writer.test.ts +223 -0
  404. package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
  405. package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
  406. package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
  407. package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
  408. package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
  409. package/src/utils/active-run.ts +131 -0
  410. package/src/utils/activity-tracker.ts +66 -0
  411. package/src/utils/agent-display.ts +87 -0
  412. package/src/utils/agent-helpers.ts +27 -0
  413. package/src/utils/agent-id-utils.ts +14 -0
  414. package/src/utils/ai-message-id.ts +12 -0
  415. package/src/utils/analytics.ts +361 -0
  416. package/src/utils/anonymous-id.ts +87 -0
  417. package/src/utils/anvil-api.ts +612 -0
  418. package/src/utils/anvil-client.ts +217 -0
  419. package/src/utils/arrays.ts +117 -0
  420. package/src/utils/auth.ts +258 -0
  421. package/src/utils/bash-context-processor.ts +47 -0
  422. package/src/utils/bash-messages.ts +108 -0
  423. package/src/utils/block-margins.ts +35 -0
  424. package/src/utils/block-operations.ts +523 -0
  425. package/src/utils/block-processor.ts +213 -0
  426. package/src/utils/chat-history.ts +253 -0
  427. package/src/utils/chat-input-key-intercept.ts +52 -0
  428. package/src/utils/chat-meta.ts +90 -0
  429. package/src/utils/chat-scroll-accel.ts +151 -0
  430. package/src/utils/clipboard-image.ts +588 -0
  431. package/src/utils/clipboard.ts +397 -0
  432. package/src/utils/code-search-summary.ts +70 -0
  433. package/src/utils/collapse-helpers.ts +256 -0
  434. package/src/utils/config-dir.ts +24 -0
  435. package/src/utils/constants.ts +190 -0
  436. package/src/utils/create-event-handler-state.ts +73 -0
  437. package/src/utils/create-run-config.ts +121 -0
  438. package/src/utils/detect-shell.ts +116 -0
  439. package/src/utils/directory-browser.ts +72 -0
  440. package/src/utils/engagement.ts +55 -0
  441. package/src/utils/env.ts +92 -0
  442. package/src/utils/error-handling.ts +255 -0
  443. package/src/utils/error-messages.ts +62 -0
  444. package/src/utils/exit-cleanly.ts +86 -0
  445. package/src/utils/feedback-helpers.ts +103 -0
  446. package/src/utils/feedback-submission.ts +22 -0
  447. package/src/utils/fetch-usage.ts +76 -0
  448. package/src/utils/fingerprint.ts +245 -0
  449. package/src/utils/format-elapsed-time.ts +33 -0
  450. package/src/utils/format-session-units.ts +6 -0
  451. package/src/utils/format-timeout.ts +28 -0
  452. package/src/utils/format-validation-errors-for-message.ts +84 -0
  453. package/src/utils/freebuff-agent-selection.ts +33 -0
  454. package/src/utils/freebuff-instance-owner.ts +66 -0
  455. package/src/utils/freebuff-model-navigation.ts +50 -0
  456. package/src/utils/freebuff-premium-reset.ts +55 -0
  457. package/src/utils/freebuff-referral-cache.ts +51 -0
  458. package/src/utils/freebuff-session-api.ts +243 -0
  459. package/src/utils/freebuff-session-display.ts +21 -0
  460. package/src/utils/freebuff-streak-line.ts +93 -0
  461. package/src/utils/git.ts +17 -0
  462. package/src/utils/helpers.ts +47 -0
  463. package/src/utils/image-display.ts +67 -0
  464. package/src/utils/image-handler.ts +325 -0
  465. package/src/utils/image-processor.ts +133 -0
  466. package/src/utils/image-thumbnail.ts +78 -0
  467. package/src/utils/implementor-helpers.ts +787 -0
  468. package/src/utils/input-modes.ts +174 -0
  469. package/src/utils/keyboard-actions.ts +392 -0
  470. package/src/utils/keypad-keys.ts +47 -0
  471. package/src/utils/layout-helpers.ts +33 -0
  472. package/src/utils/lazy-response-ads.ts +1 -0
  473. package/src/utils/local-agent-registry.ts +474 -0
  474. package/src/utils/log-shipper.ts +126 -0
  475. package/src/utils/logger.ts +327 -0
  476. package/src/utils/markdown-renderer.tsx +1090 -0
  477. package/src/utils/math.ts +3 -0
  478. package/src/utils/message-block-helpers.ts +684 -0
  479. package/src/utils/message-history.ts +143 -0
  480. package/src/utils/message-tree-utils.ts +67 -0
  481. package/src/utils/message-updater.ts +279 -0
  482. package/src/utils/open-file.ts +146 -0
  483. package/src/utils/open-url.ts +63 -0
  484. package/src/utils/path-completion.ts +111 -0
  485. package/src/utils/path-helpers.ts +38 -0
  486. package/src/utils/pending-attachments.ts +360 -0
  487. package/src/utils/polling-backoff.ts +59 -0
  488. package/src/utils/project-picker.ts +12 -0
  489. package/src/utils/queue-panel-actions.ts +70 -0
  490. package/src/utils/recent-projects.ts +156 -0
  491. package/src/utils/renderer-cleanup.ts +233 -0
  492. package/src/utils/response-ad-positions.ts +1 -0
  493. package/src/utils/run-state-storage.ts +603 -0
  494. package/src/utils/safe-json.ts +153 -0
  495. package/src/utils/sdk-event-handlers.ts +571 -0
  496. package/src/utils/send-message-helpers.ts +210 -0
  497. package/src/utils/send-message-timer.ts +110 -0
  498. package/src/utils/settings.ts +226 -0
  499. package/src/utils/skill-registry.ts +94 -0
  500. package/src/utils/spawn-agent-matcher.ts +55 -0
  501. package/src/utils/status-indicator-state.ts +107 -0
  502. package/src/utils/stream-chunk-processor.ts +57 -0
  503. package/src/utils/strings.ts +271 -0
  504. package/src/utils/subscription.ts +31 -0
  505. package/src/utils/syntax-highlighter.tsx +19 -0
  506. package/src/utils/terminal-color-detection.ts +474 -0
  507. package/src/utils/terminal-command-broker.ts +467 -0
  508. package/src/utils/terminal-enter-detection.ts +63 -0
  509. package/src/utils/terminal-images.ts +229 -0
  510. package/src/utils/terminal-io.ts +85 -0
  511. package/src/utils/terminal-protocol-controller.ts +173 -0
  512. package/src/utils/terminal-reset-sequences.ts +33 -0
  513. package/src/utils/terminal-title.ts +84 -0
  514. package/src/utils/terminal-watchdog.ts +366 -0
  515. package/src/utils/text-layout.ts +149 -0
  516. package/src/utils/theme-config.ts +141 -0
  517. package/src/utils/theme-system.ts +1206 -0
  518. package/src/utils/think-tag-parser.ts +95 -0
  519. package/src/utils/time-format.ts +21 -0
  520. package/src/utils/trace-writer.ts +162 -0
  521. package/src/utils/ui-constants.ts +69 -0
  522. package/src/utils/update-checker.ts +110 -0
  523. package/src/utils/usage-banner-state.ts +146 -0
  524. package/src/utils/validation-error-formatting.ts +86 -0
  525. package/src/utils/validation-error-helpers.ts +19 -0
  526. package/src/utils/windows-terminal-health.ts +124 -0
  527. package/src/utils/word-wrap-utils.ts +53 -0
  528. package/src/utils/write-file-atomic.ts +54 -0
  529. package/src/utils/yield-to-event-loop.ts +9 -0
@@ -0,0 +1,726 @@
1
+ import { safeOpen } from '../utils/open-url'
2
+
3
+ import { handleAdsEnable, handleAdsDisable } from './ads'
4
+ import { handleCopyConversationCommand } from './copy-conversation'
5
+ import { handleHelpCommand } from './help'
6
+ import { handleImageCommand } from './image'
7
+ import { handleInitializationFlowLocally } from './init'
8
+ import {
9
+ collectProcessDiagnostics,
10
+ formatProcessDiagnostics,
11
+ } from './process-diagnostics'
12
+ import { buildInterviewPrompt, buildPlanPrompt, buildReviewPromptFromArgs } from './prompt-builders'
13
+ import { runBashCommand } from './router'
14
+ import { handleUsageCommand } from './usage'
15
+ import { returnToFreebuffLanding } from '../hooks/use-freebuff-session'
16
+ import { useThemeStore } from '../hooks/use-theme'
17
+ import { LOGIN_WEBSITE_URL, WEBSITE_URL } from '../login/constants'
18
+ import { startNewChat } from '../project-files'
19
+ import { useChatStore } from '../state/chat-store'
20
+ import { stopActiveRun } from '../utils/active-run'
21
+ import { useFeedbackStore } from '../state/feedback-store'
22
+ import { useLoginStore } from '../state/login-store'
23
+ import { AGENT_MODES, END_SESSION_MESSAGE, IS_FREEBUFF } from '../utils/constants'
24
+ import { exitCliCleanly } from '../utils/exit-cleanly'
25
+ import { getSystemMessage, getUserMessage } from '../utils/message-history'
26
+ import { capturePendingAttachments } from '../utils/pending-attachments'
27
+ import { getSkillByName } from '../utils/skill-registry'
28
+
29
+ import type { MultilineInputHandle } from '../components/multiline-input'
30
+ import type { InputValue, PendingAttachment } from '../types/store'
31
+ import type { ChatMessage } from '../types/chat'
32
+ import type { SendMessageFn } from '../types/contracts/send-message'
33
+ import type { User } from '../utils/auth'
34
+ import type { AgentMode } from '../utils/constants'
35
+ import type { UseMutationResult } from '@tanstack/react-query'
36
+
37
+ export type RouterParams = {
38
+ agentMode: AgentMode
39
+ inputRef: React.MutableRefObject<MultilineInputHandle | null>
40
+ inputValue: string
41
+ isChainInProgressRef: React.MutableRefObject<boolean>
42
+ isStreaming: boolean
43
+ logoutMutation: UseMutationResult<boolean, Error, void, unknown>
44
+ streamMessageIdRef: React.MutableRefObject<string | null>
45
+ addToQueue: (message: string, attachments?: PendingAttachment[]) => void
46
+ clearMessages: () => void
47
+ saveToHistory: (message: string) => void
48
+ scrollToLatest: () => void
49
+ sendMessage: SendMessageFn
50
+ setCanProcessQueue: (value: React.SetStateAction<boolean>) => void
51
+ setInputFocused: (focused: boolean) => void
52
+ setInputValue: (
53
+ value: InputValue | ((prev: InputValue) => InputValue),
54
+ ) => void
55
+ setIsAuthenticated: (value: React.SetStateAction<boolean | null>) => void
56
+ setMessages: (
57
+ value: ChatMessage[] | ((prev: ChatMessage[]) => ChatMessage[]),
58
+ ) => void
59
+ setUser: (value: React.SetStateAction<User | null>) => void
60
+ }
61
+
62
+ export type CommandResult = {
63
+ openFeedbackMode?: boolean
64
+ openPublishMode?: boolean
65
+ openChatHistory?: boolean
66
+ openReviewScreen?: boolean
67
+ openQueuePanel?: boolean
68
+ preSelectAgents?: string[]
69
+ } | void
70
+
71
+ export type CommandHandler = (
72
+ params: RouterParams,
73
+ args: string,
74
+ ) => Promise<CommandResult> | CommandResult
75
+
76
+ export type CommandDefinition = {
77
+ name: string
78
+ aliases: string[]
79
+ handler: CommandHandler
80
+ /** Whether this command accepts arguments. Set automatically by the factory functions. */
81
+ acceptsArgs: boolean
82
+ }
83
+
84
+ /**
85
+ * Handler type for commands that don't accept arguments.
86
+ */
87
+ type CommandHandlerNoArgs = (
88
+ params: RouterParams,
89
+ ) => Promise<CommandResult> | CommandResult
90
+
91
+ /**
92
+ * Handler type for commands that accept arguments.
93
+ */
94
+ type CommandHandlerWithArgs = (
95
+ params: RouterParams,
96
+ args: string,
97
+ ) => Promise<CommandResult> | CommandResult
98
+
99
+ /**
100
+ * Configuration for defining a command that does NOT accept arguments.
101
+ */
102
+ type CommandConfig = {
103
+ name: string
104
+ aliases?: string[]
105
+ handler: CommandHandlerNoArgs
106
+ }
107
+
108
+ /**
109
+ * Configuration for defining a command that accepts arguments.
110
+ */
111
+ type CommandWithArgsConfig = {
112
+ name: string
113
+ aliases?: string[]
114
+ handler: CommandHandlerWithArgs
115
+ }
116
+
117
+ /**
118
+ * Factory for commands that do NOT accept arguments.
119
+ * Any args passed are gracefully ignored.
120
+ *
121
+ * @example
122
+ * defineCommand({
123
+ * name: 'new',
124
+ * aliases: ['n', 'clear'],
125
+ * handler: (params) => {
126
+ * params.setMessages(() => [])
127
+ * },
128
+ * })
129
+ */
130
+ export function defineCommand(config: CommandConfig): CommandDefinition {
131
+ return {
132
+ name: config.name,
133
+ aliases: config.aliases ?? [],
134
+ acceptsArgs: false,
135
+ handler: (params) => {
136
+ // Args are gracefully ignored for commands that don't accept them
137
+ return config.handler(params)
138
+ },
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Factory for commands that accept arguments.
144
+ * The handler receives both params and args.
145
+ *
146
+ * @example
147
+ * defineCommandWithArgs({
148
+ * name: 'bash',
149
+ * aliases: ['!'],
150
+ * handler: (params, args) => {
151
+ * if (args.trim()) {
152
+ * runBashCommand(args.trim())
153
+ * }
154
+ * },
155
+ * })
156
+ */
157
+ export function defineCommandWithArgs(
158
+ config: CommandWithArgsConfig,
159
+ ): CommandDefinition {
160
+ return {
161
+ name: config.name,
162
+ aliases: config.aliases ?? [],
163
+ acceptsArgs: true,
164
+ handler: config.handler,
165
+ }
166
+ }
167
+
168
+ const clearInput = (params: RouterParams) => {
169
+ params.setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
170
+ }
171
+
172
+ const FREEBUFF_REMOVED_COMMANDS = new Set([
173
+ 'ads:enable',
174
+ 'ads:disable',
175
+ 'usage',
176
+ 'subscribe',
177
+ 'image',
178
+ 'publish',
179
+ 'gpt-5-agent',
180
+ ])
181
+
182
+ const FREEBUFF_ONLY_COMMANDS = new Set([
183
+ 'plan',
184
+ 'end-session',
185
+ 'dashboard',
186
+ ])
187
+
188
+ const ALL_COMMANDS: CommandDefinition[] = [
189
+ defineCommand({
190
+ name: 'ads:enable',
191
+ handler: (params) => {
192
+ const { postUserMessage } = handleAdsEnable()
193
+ params.setMessages((prev) => postUserMessage(prev))
194
+ params.saveToHistory(params.inputValue.trim())
195
+ clearInput(params)
196
+ },
197
+ }),
198
+ defineCommand({
199
+ name: 'ads:disable',
200
+ handler: (params) => {
201
+ const { postUserMessage } = handleAdsDisable()
202
+ params.setMessages((prev) => postUserMessage(prev))
203
+ params.saveToHistory(params.inputValue.trim())
204
+ clearInput(params)
205
+ },
206
+ }),
207
+ defineCommand({
208
+ name: 'help',
209
+ aliases: ['h', '?'],
210
+ handler: async (params) => {
211
+ const { postUserMessage } = await handleHelpCommand()
212
+ params.setMessages((prev) => postUserMessage(prev))
213
+ params.saveToHistory(params.inputValue.trim())
214
+ clearInput(params)
215
+ },
216
+ }),
217
+ defineCommand({
218
+ name: 'diagnostics',
219
+ aliases: ['diag', 'processes'],
220
+ handler: (params) => {
221
+ const diagnostics = formatProcessDiagnostics(collectProcessDiagnostics())
222
+ params.setMessages((prev) => [...prev, getSystemMessage(diagnostics)])
223
+ params.saveToHistory(params.inputValue.trim())
224
+ clearInput(params)
225
+ },
226
+ }),
227
+ defineCommand({
228
+ name: 'copy',
229
+ aliases: ['copy-chat', 'export'],
230
+ handler: async (params) => {
231
+ await handleCopyConversationCommand(params)
232
+ },
233
+ }),
234
+ defineCommandWithArgs({
235
+ name: 'feedback',
236
+ aliases: ['bug', 'report'],
237
+ handler: (params, args) => {
238
+ const trimmedArgs = args.trim()
239
+
240
+ // If user provided feedback text directly, pre-populate the form
241
+ if (trimmedArgs) {
242
+ useFeedbackStore.getState().setFeedbackText(trimmedArgs)
243
+ useFeedbackStore.getState().setFeedbackCursor(trimmedArgs.length)
244
+ }
245
+
246
+ params.saveToHistory(params.inputValue.trim())
247
+ clearInput(params)
248
+ return { openFeedbackMode: true }
249
+ },
250
+ }),
251
+ defineCommandWithArgs({
252
+ name: 'bash',
253
+ aliases: ['!'],
254
+ handler: (params, args) => {
255
+ const trimmedArgs = args.trim()
256
+
257
+ // If user provided a command directly, execute it immediately
258
+ if (trimmedArgs) {
259
+ const commandWithBang = '!' + trimmedArgs
260
+ params.saveToHistory(commandWithBang)
261
+ clearInput(params)
262
+ runBashCommand(trimmedArgs)
263
+ return
264
+ }
265
+
266
+ // Otherwise enter bash mode
267
+ useChatStore.getState().setInputMode('bash')
268
+ params.saveToHistory(params.inputValue.trim())
269
+ clearInput(params)
270
+ },
271
+ }),
272
+ defineCommand({
273
+ name: 'login',
274
+ aliases: ['signin'],
275
+ handler: (params) => {
276
+ params.setMessages((prev) => [
277
+ ...prev,
278
+ getSystemMessage(
279
+ "You're already in the app. Use /logout to switch accounts.",
280
+ ),
281
+ ])
282
+ clearInput(params)
283
+ },
284
+ }),
285
+ defineCommand({
286
+ name: 'logout',
287
+ aliases: ['signout'],
288
+ handler: (params) => {
289
+ stopActiveRun('logout')
290
+
291
+ const { resetLoginState } = useLoginStore.getState()
292
+ params.logoutMutation.mutate(undefined, {
293
+ onSettled: () => {
294
+ resetLoginState()
295
+ params.setMessages((prev) => [
296
+ ...prev,
297
+ getSystemMessage('Logged out.'),
298
+ ])
299
+ clearInput(params)
300
+ setTimeout(() => {
301
+ // The confirmation remains visible briefly; fence that window too
302
+ // before unmounting the authenticated runtime.
303
+ stopActiveRun('logout')
304
+ params.setUser(null)
305
+ params.setIsAuthenticated(false)
306
+ }, 300)
307
+ },
308
+ })
309
+ },
310
+ }),
311
+ defineCommand({
312
+ name: 'exit',
313
+ aliases: ['quit', 'q'],
314
+ handler: () => {
315
+ void exitCliCleanly()
316
+ },
317
+ }),
318
+ defineCommandWithArgs({
319
+ name: 'new',
320
+ aliases: ['n', 'clear', 'c', 'reset'],
321
+ handler: (params, args) => {
322
+ const trimmedArgs = args.trim()
323
+
324
+ // Abort any in-flight run BEFORE clearing state and rotating the chat
325
+ // id: an orphaned run would keep streaming after the switch and its
326
+ // late checkpoints/final save would persist the old conversation's
327
+ // state under the new chat (or vice versa).
328
+ stopActiveRun('new-chat')
329
+
330
+ // Clear the conversation and rotate to a fresh chat directory, so the
331
+ // next message doesn't overwrite the previous conversation's history
332
+ params.setMessages(() => [])
333
+ params.clearMessages()
334
+ startNewChat()
335
+ params.saveToHistory(params.inputValue.trim())
336
+ clearInput(params)
337
+ // If user provided a message, send it as the first message in the new chat
338
+ if (trimmedArgs) {
339
+ // Re-enable queue processing so the message can be sent
340
+ params.setCanProcessQueue(true)
341
+ params.sendMessage({
342
+ content: trimmedArgs,
343
+ agentMode: params.agentMode,
344
+ })
345
+ setTimeout(() => {
346
+ params.scrollToLatest()
347
+ }, 0)
348
+ } else {
349
+ // Only disable queue if we're not sending a message
350
+ params.setCanProcessQueue(false)
351
+ }
352
+ },
353
+ }),
354
+ defineCommand({
355
+ name: 'init',
356
+ handler: async (params) => {
357
+ const { postUserMessage } = handleInitializationFlowLocally()
358
+ const trimmed = params.inputValue.trim()
359
+
360
+ params.saveToHistory(trimmed)
361
+ clearInput(params)
362
+
363
+ // Check streaming/queue state
364
+ if (
365
+ params.isStreaming ||
366
+ params.streamMessageIdRef.current ||
367
+ params.isChainInProgressRef.current
368
+ ) {
369
+ const pendingAttachments = capturePendingAttachments()
370
+ params.addToQueue(trimmed, pendingAttachments)
371
+ params.setInputFocused(true)
372
+ params.inputRef.current?.focus()
373
+ return
374
+ }
375
+
376
+ params.sendMessage({
377
+ content: trimmed,
378
+ agentMode: params.agentMode,
379
+ postUserMessage,
380
+ })
381
+ setTimeout(() => {
382
+ params.scrollToLatest()
383
+ }, 0)
384
+ },
385
+ }),
386
+ defineCommand({
387
+ name: 'usage',
388
+ aliases: ['credits'],
389
+ handler: async (params) => {
390
+ const { postUserMessage } = await handleUsageCommand()
391
+ params.setMessages((prev) => postUserMessage(prev))
392
+ params.saveToHistory(params.inputValue.trim())
393
+ clearInput(params)
394
+ },
395
+ }),
396
+ defineCommand({
397
+ name: 'subscribe',
398
+ aliases: ['strong', 'sub', 'buy-credits'],
399
+ handler: (params) => {
400
+ safeOpen(WEBSITE_URL + '/subscribe')
401
+ clearInput(params)
402
+ },
403
+ }),
404
+ defineCommand({
405
+ name: 'dashboard',
406
+ // Freebuff-only (see FREEBUFF_ONLY_COMMANDS): the hub is a Freebuff web
407
+ // surface, and Anvil has its own credits-shaped `/usage` banner.
408
+ //
409
+ // `usage` is one of the aliases because Freebuff removes that command —
410
+ // its banner is credits- and subscription-shaped — leaving the product
411
+ // with no answer at all to "how much have I used?". The word now lands
412
+ // somewhere, and only in the build where nothing else claims it.
413
+ aliases: ['usage', 'stats', 'streak'],
414
+ handler: (params) => {
415
+ const url = `${LOGIN_WEBSITE_URL}/account`
416
+ params.setMessages((prev) => [
417
+ ...prev,
418
+ getUserMessage(params.inputValue.trim()),
419
+ getSystemMessage(
420
+ `Opening your dashboard: ${url}\n\nStreak, activity, tokens, sessions and settings for your account — across the CLI, Desktop and web.`,
421
+ ),
422
+ ])
423
+ // Best-effort: `safeOpen` skips headless Linux and a locked-down WSL
424
+ // rather than risking the process, so the URL above is printed first and
425
+ // stays useful when nothing opens.
426
+ void safeOpen(url)
427
+ params.saveToHistory(params.inputValue.trim())
428
+ clearInput(params)
429
+ },
430
+ }),
431
+ defineCommandWithArgs({
432
+ name: 'image',
433
+ aliases: ['img', 'attach'],
434
+ handler: async (params, args) => {
435
+ const trimmedArgs = args.trim()
436
+
437
+ // If user provided a path directly, process it immediately
438
+ if (trimmedArgs) {
439
+ await handleImageCommand(trimmedArgs)
440
+ params.saveToHistory(params.inputValue.trim())
441
+ clearInput(params)
442
+ return
443
+ }
444
+
445
+ // Otherwise enter image mode
446
+ useChatStore.getState().setInputMode('image')
447
+ params.saveToHistory(params.inputValue.trim())
448
+ clearInput(params)
449
+ },
450
+ }),
451
+ // Mode commands generated from AGENT_MODES (excluded in Freebuff)
452
+ ...(IS_FREEBUFF ? [] : AGENT_MODES).map((mode) =>
453
+ defineCommandWithArgs({
454
+ name: `mode:${mode.toLowerCase()}`,
455
+ aliases: [`model:${mode.toLowerCase()}`],
456
+ handler: (params, args) => {
457
+ const trimmedArgs = args.trim()
458
+
459
+ useChatStore.getState().setAgentMode(mode)
460
+ params.setMessages((prev) => [
461
+ ...prev,
462
+ getUserMessage(params.inputValue.trim()),
463
+ getSystemMessage(`Switched to ${mode} mode.`),
464
+ ])
465
+ params.saveToHistory(params.inputValue.trim())
466
+ clearInput(params)
467
+
468
+ // If user provided a message, send it in the new mode
469
+ if (trimmedArgs) {
470
+ params.setCanProcessQueue(true)
471
+ params.sendMessage({
472
+ content: trimmedArgs,
473
+ agentMode: mode,
474
+ })
475
+ setTimeout(() => {
476
+ params.scrollToLatest()
477
+ }, 0)
478
+ }
479
+ },
480
+ }),
481
+ ),
482
+ defineCommandWithArgs({
483
+ name: 'publish',
484
+ handler: (params, args) => {
485
+ const trimmedArgs = args.trim()
486
+ params.saveToHistory(params.inputValue.trim())
487
+ clearInput(params)
488
+
489
+ // If user provided agent ids directly, skip to confirmation step
490
+ if (trimmedArgs) {
491
+ const agentIds = trimmedArgs.split(/\s+/).filter(Boolean)
492
+ return { openPublishMode: true, preSelectAgents: agentIds }
493
+ }
494
+
495
+ // Otherwise open selection UI
496
+ return { openPublishMode: true }
497
+ },
498
+ }),
499
+ defineCommand({
500
+ name: 'gpt-5-agent',
501
+ handler: (params) => {
502
+ // Insert @ GPT-5 Agent into the input field (UI shortcut, not a real command)
503
+ params.setInputValue({
504
+ text: '@GPT-5 Agent ',
505
+ cursorPosition: '@GPT-5 Agent '.length,
506
+ lastEditDueToNav: false,
507
+ })
508
+ params.inputRef.current?.focus()
509
+ // Don't save to history - this is just a UI shortcut
510
+ },
511
+ }),
512
+ defineCommand({
513
+ name: 'history',
514
+ aliases: ['chats'],
515
+ handler: (params) => {
516
+ params.saveToHistory(params.inputValue.trim())
517
+ clearInput(params)
518
+ return { openChatHistory: true }
519
+ },
520
+ }),
521
+ defineCommandWithArgs({
522
+ name: 'interview',
523
+ handler: (params, args) => {
524
+ const trimmedArgs = args.trim()
525
+
526
+ params.saveToHistory(params.inputValue.trim())
527
+ clearInput(params)
528
+
529
+ // If user provided text directly, send it immediately
530
+ if (trimmedArgs) {
531
+ params.sendMessage({
532
+ content: buildInterviewPrompt(trimmedArgs),
533
+ agentMode: params.agentMode,
534
+ })
535
+ setTimeout(() => {
536
+ params.scrollToLatest()
537
+ }, 0)
538
+ return
539
+ }
540
+
541
+ // Otherwise enter interview mode
542
+ useChatStore.getState().setInputMode('interview')
543
+ },
544
+ }),
545
+ defineCommandWithArgs({
546
+ name: 'plan',
547
+ handler: (params, args) => {
548
+ // /plan runs on the selected model. No gate.
549
+ const trimmedArgs = args.trim()
550
+
551
+ params.saveToHistory(params.inputValue.trim())
552
+ clearInput(params)
553
+
554
+ // If user provided plan text directly, send it immediately
555
+ if (trimmedArgs) {
556
+ params.sendMessage({
557
+ content: buildPlanPrompt(trimmedArgs),
558
+ agentMode: params.agentMode,
559
+ })
560
+ setTimeout(() => {
561
+ params.scrollToLatest()
562
+ }, 0)
563
+ return
564
+ }
565
+
566
+ // Otherwise enter plan mode
567
+ useChatStore.getState().setInputMode('plan')
568
+ },
569
+ }),
570
+ defineCommandWithArgs({
571
+ name: 'review',
572
+ handler: (params, args) => {
573
+ // /review runs on the selected model. No gate.
574
+ const trimmedArgs = args.trim()
575
+
576
+ params.saveToHistory(params.inputValue.trim())
577
+ clearInput(params)
578
+
579
+ // If user provided review text directly, send it immediately without showing the screen
580
+ if (trimmedArgs) {
581
+ params.sendMessage({
582
+ content: buildReviewPromptFromArgs(trimmedArgs),
583
+ agentMode: params.agentMode,
584
+ })
585
+ setTimeout(() => {
586
+ params.scrollToLatest()
587
+ }, 0)
588
+ return
589
+ }
590
+
591
+ // Otherwise open the selection UI
592
+ return { openReviewScreen: true }
593
+ },
594
+ }),
595
+ defineCommand({
596
+ // No `/q` alias: that one already quits the CLI, and a queue editor is not
597
+ // worth the chance of a mis-fired exit.
598
+ name: 'queue',
599
+ aliases: ['queued'],
600
+ handler: (params) => {
601
+ params.saveToHistory(params.inputValue.trim())
602
+ clearInput(params)
603
+ return { openQueuePanel: true }
604
+ },
605
+ }),
606
+ defineCommand({
607
+ name: 'theme:toggle',
608
+ handler: (params) => {
609
+ const { theme, setThemeName } = useThemeStore.getState()
610
+ const newTheme = theme.name === 'dark' ? 'light' : 'dark'
611
+ setThemeName(newTheme)
612
+ params.setMessages((prev) => [
613
+ ...prev,
614
+ getUserMessage(params.inputValue.trim()),
615
+ getSystemMessage(`Switched to ${newTheme} theme.`),
616
+ ])
617
+ clearInput(params)
618
+ },
619
+ }),
620
+ // /end-session (freebuff-only) — end the active session early and drop back
621
+ // to the model picker. The hook flips status to 'none', which unmounts
622
+ // <Chat> and mounts <FreebuffLandingScreen>, where the user picks a model
623
+ // and hits Enter to start a new session.
624
+ defineCommand({
625
+ name: 'end-session',
626
+ aliases: ['model'],
627
+ handler: (params) => {
628
+ params.setMessages((prev) => [
629
+ ...prev,
630
+ getUserMessage(params.inputValue.trim()),
631
+ getSystemMessage(END_SESSION_MESSAGE),
632
+ ])
633
+ params.saveToHistory(params.inputValue.trim())
634
+ clearInput(params)
635
+ returnToFreebuffLanding({ resetChat: true }).catch(() => {
636
+ // The hook surfaces poll errors via the session store; nothing to do
637
+ // here beyond letting the chat history reflect the attempt.
638
+ })
639
+ },
640
+ }),
641
+ ]
642
+
643
+ export const COMMAND_REGISTRY: CommandDefinition[] = IS_FREEBUFF
644
+ ? ALL_COMMANDS.filter((cmd) => !FREEBUFF_REMOVED_COMMANDS.has(cmd.name))
645
+ : ALL_COMMANDS.filter((cmd) => !FREEBUFF_ONLY_COMMANDS.has(cmd.name))
646
+
647
+ export function findCommand(cmd: string): CommandDefinition | undefined {
648
+ const lowerCmd = cmd.toLowerCase()
649
+
650
+ // First check the static command registry
651
+ const staticCommand = COMMAND_REGISTRY.find(
652
+ (def) => def.name === lowerCmd || def.aliases.includes(lowerCmd),
653
+ )
654
+ if (staticCommand) {
655
+ return staticCommand
656
+ }
657
+
658
+ // Check if this is a skill command (prefixed with "skill:")
659
+ if (lowerCmd.startsWith('skill:')) {
660
+ const skillName = lowerCmd.slice('skill:'.length)
661
+ const skill = getSkillByName(skillName)
662
+ if (skill) {
663
+ return createSkillCommand(skill.name)
664
+ }
665
+ }
666
+
667
+ return undefined
668
+ }
669
+
670
+ /**
671
+ * Creates a dynamic command definition for a skill.
672
+ * When invoked, the skill's content is sent to the agent.
673
+ */
674
+ function createSkillCommand(skillName: string): CommandDefinition {
675
+ return defineCommandWithArgs({
676
+ name: skillName,
677
+ handler: (params, args) => {
678
+ const skill = getSkillByName(skillName)
679
+ if (!skill) {
680
+ params.setMessages((prev) => [
681
+ ...prev,
682
+ getUserMessage(params.inputValue.trim()),
683
+ getSystemMessage(`Skill not found: ${skillName}`),
684
+ ])
685
+ params.saveToHistory(params.inputValue.trim())
686
+ params.setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
687
+ return
688
+ }
689
+
690
+ const trimmed = params.inputValue.trim()
691
+ params.saveToHistory(trimmed)
692
+ params.setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
693
+
694
+ // Build the message content with skill context and optional user args
695
+ const skillContext = `<skill name="${skill.name}">
696
+ ${skill.content}
697
+ </skill>`
698
+
699
+ const userPrompt = `I invoke the following skill:\n\n${skillContext}\n\n`
700
+ + (args.trim()
701
+ ? `User request: ${args.trim()}`
702
+ : '')
703
+
704
+ // Check streaming/queue state
705
+ if (
706
+ params.isStreaming ||
707
+ params.streamMessageIdRef.current ||
708
+ params.isChainInProgressRef.current
709
+ ) {
710
+ const pendingAttachments = capturePendingAttachments()
711
+ params.addToQueue(userPrompt, pendingAttachments)
712
+ params.setInputFocused(true)
713
+ params.inputRef.current?.focus()
714
+ return
715
+ }
716
+
717
+ params.sendMessage({
718
+ content: userPrompt,
719
+ agentMode: params.agentMode,
720
+ })
721
+ setTimeout(() => {
722
+ params.scrollToLatest()
723
+ }, 0)
724
+ },
725
+ })
726
+ }