@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,134 @@
1
+ import { createRequire } from 'module'
2
+
3
+ import { Argument, Command } from 'commander'
4
+
5
+ import { IS_FREEBUFF, type AgentMode } from './utils/constants'
6
+ import { getCliEnv } from './utils/env'
7
+
8
+ const require = createRequire(import.meta.url)
9
+
10
+ export type ParsedArgs = {
11
+ initialPrompt: string | null
12
+ command?: string
13
+ agent?: string
14
+ clearLogs: boolean
15
+ continue: boolean
16
+ continueId?: string | null
17
+ cwd?: string
18
+ initialMode?: AgentMode
19
+ }
20
+
21
+ export function loadPackageVersion(): string {
22
+ const env = getCliEnv()
23
+ if (env.ANVIL_CLI_VERSION) {
24
+ return env.ANVIL_CLI_VERSION
25
+ }
26
+
27
+ try {
28
+ const pkg = require('../package.json') as { version?: string }
29
+ if (pkg.version) {
30
+ return pkg.version
31
+ }
32
+ } catch {
33
+ // Continue to dev fallback
34
+ }
35
+
36
+ return 'dev'
37
+ }
38
+
39
+ export function parseArgs({
40
+ argv = process.argv,
41
+ isFreebuff = IS_FREEBUFF,
42
+ version = loadPackageVersion(),
43
+ }: {
44
+ argv?: string[]
45
+ isFreebuff?: boolean
46
+ version?: string
47
+ } = {}): ParsedArgs {
48
+ const program = new Command()
49
+
50
+ if (isFreebuff) {
51
+ // Freebuff: simplified CLI - no prompt args, no agent override, no clear-logs
52
+ program
53
+ .name('freebuff')
54
+ .description('Freebuff - Free AI coding assistant')
55
+ .version(version, '-v, --version', 'Print the CLI version')
56
+ .option(
57
+ '--continue [conversation-id]',
58
+ 'Continue from a previous conversation (optionally specify a conversation id)',
59
+ )
60
+ .option(
61
+ '--cwd <directory>',
62
+ 'Set the working directory (default: current directory)',
63
+ )
64
+ .addArgument(
65
+ new Argument('[command]', 'Command to run').choices(['login']),
66
+ )
67
+ .helpOption('-h, --help', 'Show this help message')
68
+ } else {
69
+ // Anvil: full CLI with all options
70
+ program
71
+ .name('anvil')
72
+ .description('Anvil CLI - AI-powered coding assistant')
73
+ .version(version, '-v, --version', 'Print the CLI version')
74
+ .option(
75
+ '--agent <agent-id>',
76
+ 'Run a specific agent id (skips loading local .agents overrides)',
77
+ )
78
+ .option(
79
+ '--clear-logs',
80
+ 'Remove any existing CLI log files before starting',
81
+ )
82
+ .option(
83
+ '--continue [conversation-id]',
84
+ 'Continue from a previous conversation (optionally specify a conversation id)',
85
+ )
86
+ .option(
87
+ '--cwd <directory>',
88
+ 'Set the working directory (default: current directory)',
89
+ )
90
+ .option('--lite', 'Start in LITE mode')
91
+ .option('--free', 'Start in LITE mode (deprecated alias)')
92
+ .option('--max', 'Start in MAX mode')
93
+ .option('--plan', 'Start in PLAN mode')
94
+ .addHelpText(
95
+ 'after',
96
+ '\nCommands:\n login Log in to your account\n publish Publish agents to the registry',
97
+ )
98
+ .helpOption('-h, --help', 'Show this help message')
99
+ .argument('[prompt...]', 'Initial prompt to send to the agent')
100
+ .allowExcessArguments(true)
101
+ }
102
+
103
+ program.parse(argv)
104
+
105
+ const options = program.opts()
106
+ const args = program.args
107
+
108
+ const continueFlag = options.continue
109
+
110
+ // Determine initial mode from flags (last flag wins if multiple specified)
111
+ // Freebuff always uses LITE mode
112
+ let initialMode: AgentMode | undefined
113
+ if (isFreebuff) {
114
+ initialMode = 'LITE'
115
+ } else {
116
+ if (options.free || options.lite) initialMode = 'LITE'
117
+ if (options.max) initialMode = 'MAX'
118
+ if (options.plan) initialMode = 'PLAN'
119
+ }
120
+
121
+ return {
122
+ initialPrompt: !isFreebuff && args.length > 0 ? args.join(' ') : null,
123
+ command: args[0],
124
+ agent: options.agent,
125
+ clearLogs: options.clearLogs || false,
126
+ continue: Boolean(continueFlag),
127
+ continueId:
128
+ typeof continueFlag === 'string' && continueFlag.trim().length > 0
129
+ ? continueFlag.trim()
130
+ : null,
131
+ cwd: options.cwd,
132
+ initialMode,
133
+ }
134
+ }
@@ -0,0 +1,445 @@
1
+ import { describe, test, expect, mock, beforeEach } from 'bun:test'
2
+
3
+ import { useChatStore } from '../../state/chat-store'
4
+ import { INPUT_MODE_CONFIGS, getInputModeConfig } from '../../utils/input-modes'
5
+ import { findCommand } from '../command-registry'
6
+
7
+ import type { RouterParams } from '../command-registry'
8
+
9
+ /**
10
+ * Tests for bash command execution logic.
11
+ *
12
+ * These tests cover:
13
+ * 1. runBashCommand - ghost vs direct mode selection based on store state
14
+ * 2. /bash slash command handler - immediate execution vs entering bash mode
15
+ */
16
+
17
+ describe('bash command', () => {
18
+ // Reset store state before each test
19
+ beforeEach(() => {
20
+ useChatStore.getState().reset()
21
+ })
22
+
23
+ describe('/bash slash command handler', () => {
24
+ const createMockParams = (
25
+ overrides: Partial<RouterParams> = {},
26
+ ): RouterParams => ({
27
+ agentMode: 'DEFAULT',
28
+ inputRef: { current: null },
29
+ inputValue: '/bash',
30
+ isChainInProgressRef: { current: false },
31
+ isStreaming: false,
32
+ logoutMutation: {} as RouterParams['logoutMutation'],
33
+ streamMessageIdRef: { current: null },
34
+ addToQueue: mock(() => {}),
35
+ clearMessages: mock(() => {}),
36
+ saveToHistory: mock(() => {}),
37
+ scrollToLatest: mock(() => {}),
38
+ sendMessage: mock(async () => {}),
39
+ setCanProcessQueue: mock(() => {}),
40
+ setInputFocused: mock(() => {}),
41
+ setInputValue: mock(() => {}),
42
+ setIsAuthenticated: mock(() => {}),
43
+ setMessages: mock(() => {}),
44
+ setUser: mock(() => {}),
45
+ ...overrides,
46
+ })
47
+
48
+ test('/bash command exists in registry', () => {
49
+ const bashCommand = findCommand('bash')
50
+ expect(bashCommand).toBeDefined()
51
+ expect(bashCommand?.name).toBe('bash')
52
+ })
53
+
54
+ test('/bash with no args enters bash mode', () => {
55
+ const bashCommand = findCommand('bash')
56
+ const params = createMockParams()
57
+
58
+ // Execute with empty args
59
+ bashCommand?.handler(params, '')
60
+
61
+ // Should enter bash mode
62
+ expect(useChatStore.getState().inputMode).toBe('bash')
63
+ })
64
+
65
+ test('/bash with args does NOT enter bash mode', () => {
66
+ const bashCommand = findCommand('bash')
67
+ const params = createMockParams()
68
+
69
+ // Execute with args - this will call runBashCommand which tries to run a real command
70
+ // We just verify it doesn't enter bash mode
71
+ bashCommand?.handler(params, 'echo test')
72
+
73
+ // Should NOT enter bash mode - command is executed immediately
74
+ expect(useChatStore.getState().inputMode).toBe('default')
75
+ })
76
+
77
+ test('/bash with args saves command WITH ! prefix to history', () => {
78
+ const saveToHistory = mock(() => {})
79
+ const bashCommand = findCommand('bash')
80
+ const params = createMockParams({ saveToHistory })
81
+
82
+ bashCommand?.handler(params, 'ls -la')
83
+
84
+ // Should save with ! prefix
85
+ expect(saveToHistory).toHaveBeenCalledWith('!ls -la')
86
+ })
87
+
88
+ test('/bash with no args saves original input to history', () => {
89
+ const saveToHistory = mock(() => {})
90
+ const bashCommand = findCommand('bash')
91
+ const params = createMockParams({
92
+ inputValue: '/bash',
93
+ saveToHistory,
94
+ })
95
+
96
+ bashCommand?.handler(params, '')
97
+
98
+ // Should save the original input
99
+ expect(saveToHistory).toHaveBeenCalledWith('/bash')
100
+ })
101
+
102
+ test('/bash with args clears input', () => {
103
+ const setInputValue = mock(() => {})
104
+ const bashCommand = findCommand('bash')
105
+ const params = createMockParams({ setInputValue })
106
+
107
+ bashCommand?.handler(params, 'pwd')
108
+
109
+ expect(setInputValue).toHaveBeenCalledWith({
110
+ text: '',
111
+ cursorPosition: 0,
112
+ lastEditDueToNav: false,
113
+ })
114
+ })
115
+
116
+ test('/bash with whitespace-only args enters bash mode', () => {
117
+ const bashCommand = findCommand('bash')
118
+ const params = createMockParams()
119
+
120
+ bashCommand?.handler(params, ' ')
121
+
122
+ // Whitespace-only should be treated as no args
123
+ expect(useChatStore.getState().inputMode).toBe('bash')
124
+ })
125
+
126
+ test('"!" is an alias for /bash', () => {
127
+ const bangCommand = findCommand('!')
128
+ expect(bangCommand).toBeDefined()
129
+ expect(bangCommand?.name).toBe('bash')
130
+ })
131
+ })
132
+
133
+ describe('runBashCommand mode selection', () => {
134
+ test('uses direct mode when not busy (no streaming agents, no chain in progress)', () => {
135
+ // Ensure store is in non-busy state
136
+ const state = useChatStore.getState()
137
+ expect(state.streamingAgents.size).toBe(0)
138
+ expect(state.isChainInProgress).toBe(false)
139
+
140
+ // The isBusy calculation should be false
141
+ const isBusy = state.streamingAgents.size > 0 || state.isChainInProgress
142
+ expect(isBusy).toBe(false)
143
+ })
144
+
145
+ test('uses ghost mode when streaming agents present', () => {
146
+ // Set up streaming agents
147
+ useChatStore.getState().setStreamingAgents(new Set(['agent-1']))
148
+
149
+ const state = useChatStore.getState()
150
+ const isBusy = state.streamingAgents.size > 0 || state.isChainInProgress
151
+ expect(isBusy).toBe(true)
152
+ })
153
+
154
+ test('uses ghost mode when chain in progress', () => {
155
+ // Set chain in progress
156
+ useChatStore.getState().setIsChainInProgress(true)
157
+
158
+ const state = useChatStore.getState()
159
+ const isBusy = state.streamingAgents.size > 0 || state.isChainInProgress
160
+ expect(isBusy).toBe(true)
161
+ })
162
+
163
+ test('uses ghost mode when both streaming and chain in progress', () => {
164
+ useChatStore.getState().setStreamingAgents(new Set(['agent-1']))
165
+ useChatStore.getState().setIsChainInProgress(true)
166
+
167
+ const state = useChatStore.getState()
168
+ const isBusy = state.streamingAgents.size > 0 || state.isChainInProgress
169
+ expect(isBusy).toBe(true)
170
+ })
171
+ })
172
+
173
+ describe('pending bash messages', () => {
174
+ test('addPendingBashMessage adds message to store', () => {
175
+ const { addPendingBashMessage } = useChatStore.getState()
176
+
177
+ addPendingBashMessage({
178
+ id: 'test-id',
179
+ command: 'ls -la',
180
+ stdout: '',
181
+ stderr: '',
182
+ exitCode: 0,
183
+ isRunning: true,
184
+ cwd: '/test',
185
+ })
186
+
187
+ const messages = useChatStore.getState().pendingBashMessages
188
+ expect(messages.length).toBe(1)
189
+ expect(messages[0].command).toBe('ls -la')
190
+ expect(messages[0].isRunning).toBe(true)
191
+ })
192
+
193
+ test('updatePendingBashMessage updates existing message', () => {
194
+ const { addPendingBashMessage, updatePendingBashMessage } =
195
+ useChatStore.getState()
196
+
197
+ addPendingBashMessage({
198
+ id: 'test-id',
199
+ command: 'ls -la',
200
+ stdout: '',
201
+ stderr: '',
202
+ exitCode: 0,
203
+ isRunning: true,
204
+ cwd: '/test',
205
+ })
206
+
207
+ updatePendingBashMessage('test-id', {
208
+ stdout: 'file1.txt\nfile2.txt',
209
+ exitCode: 0,
210
+ isRunning: false,
211
+ })
212
+
213
+ const messages = useChatStore.getState().pendingBashMessages
214
+ expect(messages[0].stdout).toBe('file1.txt\nfile2.txt')
215
+ expect(messages[0].isRunning).toBe(false)
216
+ })
217
+
218
+ test('removePendingBashMessage removes message from store', () => {
219
+ const { addPendingBashMessage, removePendingBashMessage } =
220
+ useChatStore.getState()
221
+
222
+ addPendingBashMessage({
223
+ id: 'test-id',
224
+ command: 'ls',
225
+ stdout: '',
226
+ stderr: '',
227
+ exitCode: 0,
228
+ isRunning: false,
229
+ cwd: '/test',
230
+ })
231
+
232
+ expect(useChatStore.getState().pendingBashMessages.length).toBe(1)
233
+
234
+ removePendingBashMessage('test-id')
235
+
236
+ expect(useChatStore.getState().pendingBashMessages.length).toBe(0)
237
+ })
238
+
239
+ test('clearPendingBashMessages removes all messages', () => {
240
+ const { addPendingBashMessage, clearPendingBashMessages } =
241
+ useChatStore.getState()
242
+
243
+ addPendingBashMessage({
244
+ id: 'test-1',
245
+ command: 'ls',
246
+ stdout: '',
247
+ stderr: '',
248
+ exitCode: 0,
249
+ isRunning: false,
250
+ cwd: '/test',
251
+ })
252
+ addPendingBashMessage({
253
+ id: 'test-2',
254
+ command: 'pwd',
255
+ stdout: '',
256
+ stderr: '',
257
+ exitCode: 0,
258
+ isRunning: false,
259
+ cwd: '/test',
260
+ })
261
+
262
+ expect(useChatStore.getState().pendingBashMessages.length).toBe(2)
263
+
264
+ clearPendingBashMessages()
265
+
266
+ expect(useChatStore.getState().pendingBashMessages.length).toBe(0)
267
+ })
268
+ })
269
+
270
+ describe('bash mode state transitions', () => {
271
+ test('entering bash mode sets inputMode to bash', () => {
272
+ useChatStore.getState().setInputMode('bash')
273
+ expect(useChatStore.getState().inputMode).toBe('bash')
274
+ })
275
+
276
+ test('exiting bash mode sets inputMode to default', () => {
277
+ useChatStore.getState().setInputMode('bash')
278
+ useChatStore.getState().setInputMode('default')
279
+ expect(useChatStore.getState().inputMode).toBe('default')
280
+ })
281
+
282
+ test('reset clears inputMode to default', () => {
283
+ useChatStore.getState().setInputMode('bash')
284
+ useChatStore.getState().reset()
285
+ expect(useChatStore.getState().inputMode).toBe('default')
286
+ })
287
+ })
288
+
289
+ describe('/bash with special characters in args', () => {
290
+ const createMockParams = (
291
+ overrides: Partial<RouterParams> = {},
292
+ ): RouterParams => ({
293
+ agentMode: 'DEFAULT',
294
+ inputRef: { current: null },
295
+ inputValue: '/bash',
296
+ isChainInProgressRef: { current: false },
297
+ isStreaming: false,
298
+ logoutMutation: {} as RouterParams['logoutMutation'],
299
+ streamMessageIdRef: { current: null },
300
+ addToQueue: mock(() => {}),
301
+ clearMessages: mock(() => {}),
302
+ saveToHistory: mock(() => {}),
303
+ scrollToLatest: mock(() => {}),
304
+ sendMessage: mock(async () => {}),
305
+ setCanProcessQueue: mock(() => {}),
306
+ setInputFocused: mock(() => {}),
307
+ setInputValue: mock(() => {}),
308
+ setIsAuthenticated: mock(() => {}),
309
+ setMessages: mock(() => {}),
310
+ setUser: mock(() => {}),
311
+ ...overrides,
312
+ })
313
+
314
+ test('/bash with pipe characters preserves them', () => {
315
+ const saveToHistory = mock(() => {})
316
+ const bashCommand = findCommand('bash')
317
+ const params = createMockParams({ saveToHistory })
318
+
319
+ bashCommand?.handler(params, 'ls | grep foo')
320
+
321
+ expect(saveToHistory).toHaveBeenCalledWith('!ls | grep foo')
322
+ })
323
+
324
+ test('/bash with quoted arguments preserves them', () => {
325
+ const saveToHistory = mock(() => {})
326
+ const bashCommand = findCommand('bash')
327
+ const params = createMockParams({ saveToHistory })
328
+
329
+ bashCommand?.handler(params, 'echo "hello world"')
330
+
331
+ expect(saveToHistory).toHaveBeenCalledWith('!echo "hello world"')
332
+ })
333
+
334
+ test('/bash with redirection operators preserves them', () => {
335
+ const saveToHistory = mock(() => {})
336
+ const bashCommand = findCommand('bash')
337
+ const params = createMockParams({ saveToHistory })
338
+
339
+ bashCommand?.handler(params, 'echo test > debug/output.txt')
340
+
341
+ expect(saveToHistory).toHaveBeenCalledWith(
342
+ '!echo test > debug/output.txt',
343
+ )
344
+ })
345
+
346
+ test('/bash with environment variables preserves them', () => {
347
+ const saveToHistory = mock(() => {})
348
+ const bashCommand = findCommand('bash')
349
+ const params = createMockParams({ saveToHistory })
350
+
351
+ bashCommand?.handler(params, 'echo $HOME')
352
+
353
+ expect(saveToHistory).toHaveBeenCalledWith('!echo $HOME')
354
+ })
355
+
356
+ test('/bash with semicolon command chaining preserves it', () => {
357
+ const saveToHistory = mock(() => {})
358
+ const bashCommand = findCommand('bash')
359
+ const params = createMockParams({ saveToHistory })
360
+
361
+ bashCommand?.handler(params, 'cd /tmp; ls')
362
+
363
+ expect(saveToHistory).toHaveBeenCalledWith('!cd /tmp; ls')
364
+ })
365
+
366
+ test('/bash with && command chaining preserves it', () => {
367
+ const saveToHistory = mock(() => {})
368
+ const bashCommand = findCommand('bash')
369
+ const params = createMockParams({ saveToHistory })
370
+
371
+ bashCommand?.handler(params, 'mkdir test && cd test')
372
+
373
+ expect(saveToHistory).toHaveBeenCalledWith('!mkdir test && cd test')
374
+ })
375
+ })
376
+
377
+ describe('bang prefix handling in queue', () => {
378
+ test('command starting with ! and length > 1 is recognized as bash command', () => {
379
+ const input = '!ls -la'
380
+ const isBashFromQueue = input.startsWith('!') && input.length > 1
381
+ expect(isBashFromQueue).toBe(true)
382
+ })
383
+
384
+ test('single ! character is NOT recognized as bash command from queue', () => {
385
+ const input = '!'
386
+ const isBashFromQueue = input.startsWith('!') && input.length > 1
387
+ expect(isBashFromQueue).toBe(false)
388
+ })
389
+
390
+ test('command extracts correctly without ! prefix', () => {
391
+ const input = '!git status'
392
+ const command = input.slice(1)
393
+ expect(command).toBe('git status')
394
+ })
395
+
396
+ test('empty string is not a bash command from queue', () => {
397
+ const input = ''
398
+ const isBashFromQueue = input.startsWith('!') && input.length > 1
399
+ expect(isBashFromQueue).toBe(false)
400
+ })
401
+
402
+ test('regular text without ! is not a bash command from queue', () => {
403
+ const input = 'help me with this'
404
+ const isBashFromQueue = input.startsWith('!') && input.length > 1
405
+ expect(isBashFromQueue).toBe(false)
406
+ })
407
+ })
408
+
409
+ describe('bash mode configuration', () => {
410
+ test('bash mode has correct label', () => {
411
+ const config = getInputModeConfig('bash')
412
+ expect(config.icon).toBe(null)
413
+ expect(config.label).toBe('!')
414
+ })
415
+
416
+ test('bash mode uses info color', () => {
417
+ const config = getInputModeConfig('bash')
418
+ expect(config.color).toBe('info')
419
+ })
420
+
421
+ test('bash mode has correct placeholder', () => {
422
+ const config = getInputModeConfig('bash')
423
+ expect(config.placeholder).toBe('enter bash command...')
424
+ })
425
+
426
+ test('bash mode has width adjustment of 4', () => {
427
+ const config = getInputModeConfig('bash')
428
+ expect(config.widthAdjustment).toBe(4)
429
+ })
430
+
431
+ test('bash mode hides agent mode toggle', () => {
432
+ const config = getInputModeConfig('bash')
433
+ expect(config.showAgentModeToggle).toBe(false)
434
+ })
435
+
436
+ test('bash mode disables slash command suggestions', () => {
437
+ const config = getInputModeConfig('bash')
438
+ expect(config.disableSlashSuggestions).toBe(true)
439
+ })
440
+
441
+ test('bash mode config exists in INPUT_MODE_CONFIGS', () => {
442
+ expect(INPUT_MODE_CONFIGS.bash).toBeDefined()
443
+ })
444
+ })
445
+ })