@alumbwe/anvil 1.0.19 → 1.0.20

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 (494) hide show
  1. package/README.md +28 -30
  2. package/bin/anvil +2 -0
  3. package/package.json +71 -75
  4. package/src/agents/bundled-agents.generated.d.ts +14 -0
  5. package/src/agents/bundled-agents.generated.ts +3760 -0
  6. package/src/app.tsx +399 -0
  7. package/src/chat.tsx +1698 -0
  8. package/src/cli-args.ts +134 -0
  9. package/src/commands/__tests__/anvil-command-aliases.test.ts +56 -0
  10. package/src/commands/__tests__/bash-command.test.ts +445 -0
  11. package/src/commands/__tests__/command-args.test.ts +349 -0
  12. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
  13. package/src/commands/__tests__/copy-conversation.test.ts +226 -0
  14. package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
  15. package/src/commands/__tests__/image.test.ts +99 -0
  16. package/src/commands/__tests__/init.test.ts +456 -0
  17. package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
  18. package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
  19. package/src/commands/__tests__/prompt-builders.test.ts +28 -0
  20. package/src/commands/__tests__/router-input.test.ts +298 -0
  21. package/src/commands/ads.ts +44 -0
  22. package/src/commands/command-registry.ts +726 -0
  23. package/src/commands/copy-conversation.ts +392 -0
  24. package/src/commands/help.ts +13 -0
  25. package/src/commands/image.ts +20 -0
  26. package/src/commands/init.ts +121 -0
  27. package/src/commands/process-diagnostics.ts +120 -0
  28. package/src/commands/prompt-builders.ts +100 -0
  29. package/src/commands/publish.ts +203 -0
  30. package/src/commands/router-utils.ts +80 -0
  31. package/src/commands/router.ts +487 -0
  32. package/src/commands/usage.ts +26 -0
  33. package/src/components/__tests__/ad-banner.test.tsx +83 -0
  34. package/src/components/__tests__/anvil-landing-heading-row.test.tsx +91 -0
  35. package/src/components/__tests__/anvil-model-selector.test.tsx +519 -0
  36. package/src/components/__tests__/anvil-offer-invariants.test.ts +54 -0
  37. package/src/components/__tests__/anvil-takeover-prompt.test.tsx +212 -0
  38. package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
  39. package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
  40. package/src/components/__tests__/grid-layout.test.tsx +1051 -0
  41. package/src/components/__tests__/message-block.completion.test.tsx +104 -0
  42. package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
  43. package/src/components/__tests__/message-with-agents.test.tsx +579 -0
  44. package/src/components/__tests__/multiline-input.test.tsx +1132 -0
  45. package/src/components/__tests__/queue-panel.test.tsx +398 -0
  46. package/src/components/__tests__/selectable-list.test.ts +232 -0
  47. package/src/components/__tests__/status-bar.test.tsx +44 -0
  48. package/src/components/__tests__/status-indicator.test.tsx +204 -0
  49. package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
  50. package/src/components/__tests__/user-error-banner.test.tsx +102 -0
  51. package/src/components/ad-banner.tsx +323 -0
  52. package/src/components/agent-checklist.tsx +350 -0
  53. package/src/components/agent-mode-toggle.tsx +241 -0
  54. package/src/components/anvil-active-session-summary.tsx +63 -0
  55. package/src/components/anvil-landing-screen.tsx +865 -0
  56. package/src/components/anvil-mark.tsx +52 -0
  57. package/src/components/anvil-model-selector.tsx +1087 -0
  58. package/src/components/anvil-referral-banner.tsx +604 -0
  59. package/src/components/anvil-superseded-screen.tsx +62 -0
  60. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
  61. package/src/components/ask-user/__tests__/validation.test.ts +213 -0
  62. package/src/components/ask-user/components/accordion-question.tsx +153 -0
  63. package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
  64. package/src/components/ask-user/components/options-list.tsx +133 -0
  65. package/src/components/ask-user/components/question-header.tsx +80 -0
  66. package/src/components/ask-user/components/question-option.tsx +89 -0
  67. package/src/components/ask-user/constants.ts +38 -0
  68. package/src/components/ask-user/index.tsx +631 -0
  69. package/src/components/ask-user/utils/validation.ts +85 -0
  70. package/src/components/attachment-card.tsx +65 -0
  71. package/src/components/blocks/agent-block-grid.tsx +58 -0
  72. package/src/components/blocks/agent-branch-item.tsx +311 -0
  73. package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
  74. package/src/components/blocks/agent-list-branch.tsx +77 -0
  75. package/src/components/blocks/ask-user-branch.tsx +81 -0
  76. package/src/components/blocks/block-helpers.ts +11 -0
  77. package/src/components/blocks/blocks-renderer.tsx +289 -0
  78. package/src/components/blocks/content-with-markdown.tsx +33 -0
  79. package/src/components/blocks/image-block.tsx +128 -0
  80. package/src/components/blocks/implementor-row.tsx +429 -0
  81. package/src/components/blocks/single-block.tsx +201 -0
  82. package/src/components/blocks/thinking-block.tsx +67 -0
  83. package/src/components/blocks/tool-block-group.tsx +57 -0
  84. package/src/components/blocks/tool-branch.tsx +206 -0
  85. package/src/components/blocks/user-content-copy.tsx +157 -0
  86. package/src/components/bottom-banner.tsx +136 -0
  87. package/src/components/build-mode-buttons.tsx +110 -0
  88. package/src/components/button.tsx +66 -0
  89. package/src/components/chat-header.tsx +77 -0
  90. package/src/components/chat-history-screen.tsx +413 -0
  91. package/src/components/chat-input-bar.tsx +523 -0
  92. package/src/components/clickable-title-box.tsx +66 -0
  93. package/src/components/clickable.tsx +119 -0
  94. package/src/components/collapse-button.tsx +34 -0
  95. package/src/components/copy-button.tsx +203 -0
  96. package/src/components/elapsed-timer.tsx +55 -0
  97. package/src/components/error-boundary.tsx +55 -0
  98. package/src/components/feedback-container.tsx +181 -0
  99. package/src/components/feedback-icon-button.tsx +89 -0
  100. package/src/components/feedback-input-mode.tsx +344 -0
  101. package/src/components/file-attachment-card.tsx +98 -0
  102. package/src/components/grid-layout.tsx +80 -0
  103. package/src/components/help-banner.tsx +127 -0
  104. package/src/components/highlighted-text.tsx +52 -0
  105. package/src/components/image-card.tsx +167 -0
  106. package/src/components/image-thumbnail.tsx +114 -0
  107. package/src/components/input-cursor.tsx +77 -0
  108. package/src/components/input-mode-banner.tsx +57 -0
  109. package/src/components/load-previous-button.tsx +48 -0
  110. package/src/components/login-modal.tsx +475 -0
  111. package/src/components/message-block.tsx +346 -0
  112. package/src/components/message-footer.tsx +258 -0
  113. package/src/components/message-with-agents.tsx +506 -0
  114. package/src/components/mode-divider.tsx +43 -0
  115. package/src/components/multiline-input.tsx +1241 -0
  116. package/src/components/out-of-credits-banner.tsx +160 -0
  117. package/src/components/pending-attachments-banner.tsx +116 -0
  118. package/src/components/pending-bash-message.tsx +59 -0
  119. package/src/components/progress-bar.tsx +81 -0
  120. package/src/components/project-picker-screen.tsx +485 -0
  121. package/src/components/publish-confirmation.tsx +493 -0
  122. package/src/components/publish-container.tsx +687 -0
  123. package/src/components/queue-panel.tsx +310 -0
  124. package/src/components/raised-pill.tsx +95 -0
  125. package/src/components/renderers/plan-box.tsx +59 -0
  126. package/src/components/review-screen.tsx +112 -0
  127. package/src/components/scroll-to-bottom-button.tsx +34 -0
  128. package/src/components/segmented-control.tsx +187 -0
  129. package/src/components/selectable-list.tsx +248 -0
  130. package/src/components/selected-chips.tsx +80 -0
  131. package/src/components/separator.tsx +40 -0
  132. package/src/components/session-ended-banner.tsx +226 -0
  133. package/src/components/shimmer-text.tsx +248 -0
  134. package/src/components/status-bar.tsx +313 -0
  135. package/src/components/subscription-limit-banner.tsx +206 -0
  136. package/src/components/suggested-prompts.tsx +184 -0
  137. package/src/components/suggestion-menu.tsx +215 -0
  138. package/src/components/terminal-command-display.tsx +151 -0
  139. package/src/components/terminal-link.tsx +97 -0
  140. package/src/components/terminal-thinking-orb.tsx +95 -0
  141. package/src/components/text-attachment-card.tsx +77 -0
  142. package/src/components/thinking.tsx +109 -0
  143. package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
  144. package/src/components/tools/__tests__/code-search.test.tsx +45 -0
  145. package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
  146. package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
  147. package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
  148. package/src/components/tools/apply-patch.tsx +95 -0
  149. package/src/components/tools/code-search.tsx +45 -0
  150. package/src/components/tools/composio.tsx +152 -0
  151. package/src/components/tools/diff-viewer.tsx +76 -0
  152. package/src/components/tools/glob.tsx +61 -0
  153. package/src/components/tools/gravity-index.tsx +77 -0
  154. package/src/components/tools/list-directory.tsx +61 -0
  155. package/src/components/tools/read-docs.tsx +37 -0
  156. package/src/components/tools/read-files.tsx +91 -0
  157. package/src/components/tools/read-subtree.tsx +44 -0
  158. package/src/components/tools/read-url.tsx +33 -0
  159. package/src/components/tools/registry.ts +126 -0
  160. package/src/components/tools/render-ui.tsx +122 -0
  161. package/src/components/tools/run-terminal-command.tsx +79 -0
  162. package/src/components/tools/skill.tsx +29 -0
  163. package/src/components/tools/str-replace.tsx +77 -0
  164. package/src/components/tools/suggest-followups.tsx +369 -0
  165. package/src/components/tools/task-completed.tsx +16 -0
  166. package/src/components/tools/tool-call-item.tsx +273 -0
  167. package/src/components/tools/types.ts +55 -0
  168. package/src/components/tools/web-search.tsx +33 -0
  169. package/src/components/tools/write-file.tsx +16 -0
  170. package/src/components/tools/write-todos.tsx +95 -0
  171. package/src/components/top-banner.tsx +178 -0
  172. package/src/components/usage-banner.tsx +244 -0
  173. package/src/components/user-error-banner.tsx +56 -0
  174. package/src/components/validation-error-popover.tsx +192 -0
  175. package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
  176. package/src/contexts/chat-runtime-context.tsx +240 -0
  177. package/src/data/slash-commands.test.ts +24 -0
  178. package/src/data/slash-commands.ts +249 -0
  179. package/src/entry.ts +12 -0
  180. package/src/hooks/__tests__/holds-live-anvil-slot.test.ts +49 -0
  181. package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
  182. package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
  183. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
  184. package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
  185. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
  186. package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
  187. package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
  188. package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
  189. package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
  190. package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
  191. package/src/hooks/__tests__/use-input-history.test.ts +699 -0
  192. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
  193. package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
  194. package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
  195. package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
  196. package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
  197. package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
  198. package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
  199. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
  200. package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
  201. package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
  202. package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
  203. package/src/hooks/__tests__/use-timeout.test.ts +330 -0
  204. package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
  205. package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
  206. package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
  207. package/src/hooks/helpers/send-message.ts +654 -0
  208. package/src/hooks/stream-state.ts +83 -0
  209. package/src/hooks/use-activity-query.ts +668 -0
  210. package/src/hooks/use-agent-validation.ts +74 -0
  211. package/src/hooks/use-anvil-ctrl-c-exit.ts +23 -0
  212. package/src/hooks/use-anvil-session-progress.ts +34 -0
  213. package/src/hooks/use-anvil-session.ts +806 -0
  214. package/src/hooks/use-anvil-streak-query.ts +61 -0
  215. package/src/hooks/use-ask-user-bridge.ts +92 -0
  216. package/src/hooks/use-auth-query.ts +262 -0
  217. package/src/hooks/use-auth-state.ts +152 -0
  218. package/src/hooks/use-chat-input.ts +105 -0
  219. package/src/hooks/use-chat-keyboard.ts +321 -0
  220. package/src/hooks/use-chat-messages.ts +254 -0
  221. package/src/hooks/use-chat-state.ts +165 -0
  222. package/src/hooks/use-chat-streaming.ts +180 -0
  223. package/src/hooks/use-chat-ui.ts +131 -0
  224. package/src/hooks/use-clipboard.ts +151 -0
  225. package/src/hooks/use-connection-status.ts +142 -0
  226. package/src/hooks/use-directory-browser.ts +88 -0
  227. package/src/hooks/use-elapsed-time.ts +116 -0
  228. package/src/hooks/use-event.ts +34 -0
  229. package/src/hooks/use-exit-handler.ts +66 -0
  230. package/src/hooks/use-fetch-login-url.ts +65 -0
  231. package/src/hooks/use-fingerprint.ts +61 -0
  232. package/src/hooks/use-gravity-ad.ts +656 -0
  233. package/src/hooks/use-grid-layout.ts +73 -0
  234. package/src/hooks/use-input-history.ts +173 -0
  235. package/src/hooks/use-login-keyboard-handlers.ts +75 -0
  236. package/src/hooks/use-login-polling.ts +114 -0
  237. package/src/hooks/use-logo.tsx +161 -0
  238. package/src/hooks/use-message-queue.ts +387 -0
  239. package/src/hooks/use-now.ts +20 -0
  240. package/src/hooks/use-path-tab-completion.ts +88 -0
  241. package/src/hooks/use-publish-mutation.ts +60 -0
  242. package/src/hooks/use-queue-controls.ts +68 -0
  243. package/src/hooks/use-queue-ui.ts +74 -0
  244. package/src/hooks/use-scroll-management.ts +184 -0
  245. package/src/hooks/use-searchable-list.ts +98 -0
  246. package/src/hooks/use-send-message.ts +773 -0
  247. package/src/hooks/use-sheen-animation.tsx +88 -0
  248. package/src/hooks/use-subscription-query.ts +71 -0
  249. package/src/hooks/use-suggestion-engine.ts +783 -0
  250. package/src/hooks/use-terminal-breakpoints.ts +77 -0
  251. package/src/hooks/use-terminal-dimensions.ts +46 -0
  252. package/src/hooks/use-terminal-focus.ts +29 -0
  253. package/src/hooks/use-terminal-layout.ts +189 -0
  254. package/src/hooks/use-theme.tsx +140 -0
  255. package/src/hooks/use-timeout.ts +85 -0
  256. package/src/hooks/use-update-preference.ts +66 -0
  257. package/src/hooks/use-usage-monitor.ts +55 -0
  258. package/src/hooks/use-usage-query.ts +124 -0
  259. package/src/hooks/use-user-details-query.ts +93 -0
  260. package/src/hooks/use-why-did-you-update.ts +177 -0
  261. package/src/index.tsx +473 -0
  262. package/src/init/__tests__/init-direnv.test.ts +526 -0
  263. package/src/init/init-app.ts +34 -0
  264. package/src/init/init-direnv.ts +133 -0
  265. package/src/login/constants.ts +60 -0
  266. package/src/login/login-flow.ts +204 -0
  267. package/src/login/plain-login.ts +108 -0
  268. package/src/login/utils.ts +183 -0
  269. package/src/native/ripgrep.ts +80 -0
  270. package/src/polyfills/bun-strip-ansi.ts +12 -0
  271. package/src/pre-init/tree-sitter-wasm.ts +96 -0
  272. package/src/project-files.ts +102 -0
  273. package/src/setup/key-setup.ts +295 -0
  274. package/src/setup/load-env.ts +106 -0
  275. package/src/state/__tests__/chat-store-focus.test.ts +22 -0
  276. package/src/state/__tests__/feedback-store.test.ts +257 -0
  277. package/src/state/anvil-model-store.ts +42 -0
  278. package/src/state/anvil-session-store.ts +53 -0
  279. package/src/state/chat-history-store.ts +30 -0
  280. package/src/state/chat-store.ts +544 -0
  281. package/src/state/feedback-store.ts +160 -0
  282. package/src/state/login-store.ts +150 -0
  283. package/src/state/message-block-store.ts +149 -0
  284. package/src/state/publish-store.ts +146 -0
  285. package/src/state/queue-panel-store.ts +26 -0
  286. package/src/state/review-store.ts +24 -0
  287. package/src/types/anvil-session.ts +17 -0
  288. package/src/types/chat-state.ts +15 -0
  289. package/src/types/chat.ts +241 -0
  290. package/src/types/contracts/send-message.ts +12 -0
  291. package/src/types/env.ts +98 -0
  292. package/src/types/function-params.ts +28 -0
  293. package/src/types/react19-compat.d.ts +19 -0
  294. package/src/types/store.ts +111 -0
  295. package/src/types/theme-system.ts +144 -0
  296. package/src/types/utils.ts +3 -0
  297. package/src/utils/__tests__/active-run.test.ts +140 -0
  298. package/src/utils/__tests__/activity-tracker.test.ts +440 -0
  299. package/src/utils/__tests__/agent-display.test.ts +139 -0
  300. package/src/utils/__tests__/analytics-client.test.ts +262 -0
  301. package/src/utils/__tests__/anonymous-id.test.ts +74 -0
  302. package/src/utils/__tests__/anvil-api.test.ts +578 -0
  303. package/src/utils/__tests__/anvil-instance-owner.test.ts +69 -0
  304. package/src/utils/__tests__/anvil-model-navigation.test.ts +102 -0
  305. package/src/utils/__tests__/anvil-premium-reset.test.ts +79 -0
  306. package/src/utils/__tests__/anvil-referral-cache.test.ts +116 -0
  307. package/src/utils/__tests__/anvil-session-api.test.ts +215 -0
  308. package/src/utils/__tests__/anvil-session-display.test.ts +23 -0
  309. package/src/utils/__tests__/anvil-streak-line.test.ts +217 -0
  310. package/src/utils/__tests__/arrays.test.ts +264 -0
  311. package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
  312. package/src/utils/__tests__/block-processor.test.ts +810 -0
  313. package/src/utils/__tests__/chat-history.test.ts +203 -0
  314. package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
  315. package/src/utils/__tests__/chat-meta.test.ts +130 -0
  316. package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
  317. package/src/utils/__tests__/clipboard.test.ts +910 -0
  318. package/src/utils/__tests__/code-search-summary.test.ts +84 -0
  319. package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
  320. package/src/utils/__tests__/error-handling.test.ts +658 -0
  321. package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
  322. package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
  323. package/src/utils/__tests__/feedback-submission.test.ts +26 -0
  324. package/src/utils/__tests__/fetch-usage.test.ts +355 -0
  325. package/src/utils/__tests__/fingerprint.test.ts +144 -0
  326. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
  327. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
  328. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
  329. package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
  330. package/src/utils/__tests__/format-timeout.test.ts +87 -0
  331. package/src/utils/__tests__/image-dimensions.test.ts +249 -0
  332. package/src/utils/__tests__/image-processor.test.ts +209 -0
  333. package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
  334. package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
  335. package/src/utils/__tests__/layout-helpers.test.ts +36 -0
  336. package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
  337. package/src/utils/__tests__/log-shipper.test.ts +53 -0
  338. package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
  339. package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
  340. package/src/utils/__tests__/message-updater.test.ts +645 -0
  341. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
  342. package/src/utils/__tests__/pending-attachments.test.ts +277 -0
  343. package/src/utils/__tests__/polling-backoff.test.ts +51 -0
  344. package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
  345. package/src/utils/__tests__/run-state-storage.test.ts +919 -0
  346. package/src/utils/__tests__/safe-json.test.ts +77 -0
  347. package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
  348. package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
  349. package/src/utils/__tests__/send-message-timer.test.ts +64 -0
  350. package/src/utils/__tests__/settings.test.ts +99 -0
  351. package/src/utils/__tests__/strings.test.ts +192 -0
  352. package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
  353. package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
  354. package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
  355. package/src/utils/__tests__/terminal-io.test.ts +137 -0
  356. package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
  357. package/src/utils/__tests__/text-layout.test.ts +82 -0
  358. package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
  359. package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
  360. package/src/utils/__tests__/trace-writer.test.ts +223 -0
  361. package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
  362. package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
  363. package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
  364. package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
  365. package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
  366. package/src/utils/active-run.ts +131 -0
  367. package/src/utils/activity-tracker.ts +66 -0
  368. package/src/utils/agent-display.ts +87 -0
  369. package/src/utils/agent-helpers.ts +27 -0
  370. package/src/utils/agent-id-utils.ts +14 -0
  371. package/src/utils/ai-message-id.ts +12 -0
  372. package/src/utils/analytics.ts +361 -0
  373. package/src/utils/anonymous-id.ts +87 -0
  374. package/src/utils/anvil-agent-selection.ts +33 -0
  375. package/src/utils/anvil-api.ts +612 -0
  376. package/src/utils/anvil-client.ts +217 -0
  377. package/src/utils/anvil-instance-owner.ts +66 -0
  378. package/src/utils/anvil-model-navigation.ts +50 -0
  379. package/src/utils/anvil-premium-reset.ts +55 -0
  380. package/src/utils/anvil-referral-cache.ts +51 -0
  381. package/src/utils/anvil-session-api.ts +243 -0
  382. package/src/utils/anvil-session-display.ts +21 -0
  383. package/src/utils/anvil-streak-line.ts +93 -0
  384. package/src/utils/arrays.ts +117 -0
  385. package/src/utils/auth.ts +258 -0
  386. package/src/utils/bash-context-processor.ts +47 -0
  387. package/src/utils/bash-messages.ts +108 -0
  388. package/src/utils/block-margins.ts +35 -0
  389. package/src/utils/block-operations.ts +523 -0
  390. package/src/utils/block-processor.ts +213 -0
  391. package/src/utils/chat-history.ts +253 -0
  392. package/src/utils/chat-input-key-intercept.ts +52 -0
  393. package/src/utils/chat-meta.ts +90 -0
  394. package/src/utils/chat-scroll-accel.ts +151 -0
  395. package/src/utils/clipboard-image.ts +588 -0
  396. package/src/utils/clipboard.ts +397 -0
  397. package/src/utils/code-search-summary.ts +70 -0
  398. package/src/utils/collapse-helpers.ts +256 -0
  399. package/src/utils/config-dir.ts +24 -0
  400. package/src/utils/constants.ts +190 -0
  401. package/src/utils/create-event-handler-state.ts +73 -0
  402. package/src/utils/create-run-config.ts +121 -0
  403. package/src/utils/detect-shell.ts +116 -0
  404. package/src/utils/directory-browser.ts +72 -0
  405. package/src/utils/engagement.ts +55 -0
  406. package/src/utils/env.ts +92 -0
  407. package/src/utils/error-handling.ts +255 -0
  408. package/src/utils/error-messages.ts +62 -0
  409. package/src/utils/exit-cleanly.ts +86 -0
  410. package/src/utils/feedback-helpers.ts +103 -0
  411. package/src/utils/feedback-submission.ts +22 -0
  412. package/src/utils/fetch-usage.ts +76 -0
  413. package/src/utils/fingerprint.ts +245 -0
  414. package/src/utils/format-elapsed-time.ts +33 -0
  415. package/src/utils/format-session-units.ts +6 -0
  416. package/src/utils/format-timeout.ts +28 -0
  417. package/src/utils/format-validation-errors-for-message.ts +84 -0
  418. package/src/utils/git.ts +17 -0
  419. package/src/utils/helpers.ts +47 -0
  420. package/src/utils/image-display.ts +67 -0
  421. package/src/utils/image-handler.ts +325 -0
  422. package/src/utils/image-processor.ts +133 -0
  423. package/src/utils/image-thumbnail.ts +78 -0
  424. package/src/utils/implementor-helpers.ts +787 -0
  425. package/src/utils/input-modes.ts +174 -0
  426. package/src/utils/keyboard-actions.ts +392 -0
  427. package/src/utils/keypad-keys.ts +47 -0
  428. package/src/utils/layout-helpers.ts +33 -0
  429. package/src/utils/lazy-response-ads.ts +1 -0
  430. package/src/utils/local-agent-registry.ts +474 -0
  431. package/src/utils/log-shipper.ts +126 -0
  432. package/src/utils/logger.ts +327 -0
  433. package/src/utils/markdown-renderer.tsx +1090 -0
  434. package/src/utils/math.ts +3 -0
  435. package/src/utils/message-block-helpers.ts +684 -0
  436. package/src/utils/message-history.ts +143 -0
  437. package/src/utils/message-tree-utils.ts +67 -0
  438. package/src/utils/message-updater.ts +279 -0
  439. package/src/utils/open-file.ts +146 -0
  440. package/src/utils/open-url.ts +63 -0
  441. package/src/utils/path-completion.ts +111 -0
  442. package/src/utils/path-helpers.ts +38 -0
  443. package/src/utils/pending-attachments.ts +360 -0
  444. package/src/utils/polling-backoff.ts +59 -0
  445. package/src/utils/project-picker.ts +12 -0
  446. package/src/utils/queue-panel-actions.ts +70 -0
  447. package/src/utils/recent-projects.ts +156 -0
  448. package/src/utils/renderer-cleanup.ts +233 -0
  449. package/src/utils/response-ad-positions.ts +1 -0
  450. package/src/utils/run-state-storage.ts +603 -0
  451. package/src/utils/safe-json.ts +153 -0
  452. package/src/utils/sdk-event-handlers.ts +571 -0
  453. package/src/utils/send-message-helpers.ts +210 -0
  454. package/src/utils/send-message-timer.ts +110 -0
  455. package/src/utils/settings.ts +226 -0
  456. package/src/utils/skill-registry.ts +94 -0
  457. package/src/utils/spawn-agent-matcher.ts +55 -0
  458. package/src/utils/status-indicator-state.ts +107 -0
  459. package/src/utils/stream-chunk-processor.ts +57 -0
  460. package/src/utils/strings.ts +271 -0
  461. package/src/utils/subscription.ts +31 -0
  462. package/src/utils/syntax-highlighter.tsx +19 -0
  463. package/src/utils/terminal-color-detection.ts +474 -0
  464. package/src/utils/terminal-command-broker.ts +467 -0
  465. package/src/utils/terminal-enter-detection.ts +63 -0
  466. package/src/utils/terminal-images.ts +229 -0
  467. package/src/utils/terminal-io.ts +85 -0
  468. package/src/utils/terminal-protocol-controller.ts +173 -0
  469. package/src/utils/terminal-reset-sequences.ts +33 -0
  470. package/src/utils/terminal-title.ts +84 -0
  471. package/src/utils/terminal-watchdog.ts +366 -0
  472. package/src/utils/text-layout.ts +149 -0
  473. package/src/utils/theme-config.ts +141 -0
  474. package/src/utils/theme-system.ts +1206 -0
  475. package/src/utils/think-tag-parser.ts +95 -0
  476. package/src/utils/time-format.ts +21 -0
  477. package/src/utils/trace-writer.ts +162 -0
  478. package/src/utils/ui-constants.ts +69 -0
  479. package/src/utils/update-checker.ts +110 -0
  480. package/src/utils/usage-banner-state.ts +146 -0
  481. package/src/utils/validation-error-formatting.ts +86 -0
  482. package/src/utils/validation-error-helpers.ts +19 -0
  483. package/src/utils/windows-terminal-health.ts +124 -0
  484. package/src/utils/word-wrap-utils.ts +53 -0
  485. package/src/utils/write-file-atomic.ts +54 -0
  486. package/src/utils/yield-to-event-loop.ts +9 -0
  487. package/bin/anvil.cjs +0 -40
  488. package/dist/bundled.js +0 -123508
  489. package/dist/bundled.js.map +0 -828
  490. package/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
  491. package/dist/vendor/ripgrep/arm64-linux/rg +0 -0
  492. package/dist/vendor/ripgrep/x64-darwin/rg +0 -0
  493. package/dist/vendor/ripgrep/x64-linux/rg +0 -0
  494. package/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
@@ -0,0 +1,93 @@
1
+ // The label/dots/perk-note logic is shared with Anvil Desktop and lives in
2
+ // common; this module re-exports it and adds the terminal rendering and layout
3
+ // gating only the CLI needs.
4
+ export {
5
+ ANVIL_STREAK_WEEK,
6
+ getAnvilStreakBonusNote,
7
+ } from '@anvil/common/util/anvil-streak-line'
8
+ export type { AnvilStreakLine } from '@anvil/common/util/anvil-streak-line'
9
+
10
+ import {
11
+ ANVIL_STREAK_WEEK,
12
+ getAnvilStreakBonusNote,
13
+ getAnvilStreakLine as getSharedAnvilStreakLine,
14
+ } from '@anvil/common/util/anvil-streak-line'
15
+
16
+ import type { AnvilStreakLine } from '@anvil/common/util/anvil-streak-line'
17
+
18
+ const ANVIL_STREAK_BONUS_MIN_HEIGHT = 30
19
+
20
+ /** Columns between the count label and its progress dots. */
21
+ export const ANVIL_STREAK_LABEL_GAP = 2
22
+
23
+ /** Columns kept clear between the heading and the streak when they share a
24
+ * row. The heading row is laid out space-between inside a shrink-to-fit
25
+ * column, so when the row is the widest child there is no free space to
26
+ * distribute and the two would otherwise render flush against each other
27
+ * ("Start coding for free18 day streak"). This is the floor, and the same
28
+ * number decides whether they may share a row at all. */
29
+ export const ANVIL_STREAK_INLINE_GAP = 3
30
+
31
+ /** Progress glyphs for a terminal — the same ●/○ pair the shared module and
32
+ * the desktop app use. Bullet and middle dot were tried here because U+25CF
33
+ * is missing from a few terminal fonts and lands as a tofu box, but • and ·
34
+ * differ only in size: at a glance a partial week and a full one look alike,
35
+ * which is the whole point of the row. Filled-vs-hollow reads instantly, and
36
+ * the CLI already bets on ●/○ for its agent status indicators, so a font that
37
+ * can't draw them is already visibly broken elsewhere.
38
+ *
39
+ * If a font ever fails these, █/░ (Block Elements) is the fallback pair: the
40
+ * ASCII logo and the progress bar are built from them, so anything that
41
+ * renders the CLI at all renders those. */
42
+ const TERMINAL_DOT_CHARS = { filled: '●', empty: '○' }
43
+
44
+ /** The streak line as the CLI draws it. */
45
+ export function getAnvilStreakLine(
46
+ streak: number,
47
+ ): AnvilStreakLine | null {
48
+ return getSharedAnvilStreakLine(streak, TERMINAL_DOT_CHARS)
49
+ }
50
+
51
+ /** Rendered width of the streak, e.g. "18 day streak ●●●●●●●+". */
52
+ export function getAnvilStreakInlineWidth(line: AnvilStreakLine): number {
53
+ return line.label.length + ANVIL_STREAK_LABEL_GAP + line.dots.length
54
+ }
55
+
56
+ /** What a user with no streak yet is about to earn. The empty slot is measured
57
+ * against it so the row doesn't move on day one. */
58
+ const DAY_ONE_LINE = getAnvilStreakLine(1)!
59
+
60
+ /** Whether the heading and the streak can share a row with the inline gap left
61
+ * clear between them. A streak long enough to widen its own label (or a
62
+ * narrow terminal) pushes the streak onto its own line instead of letting the
63
+ * two collide. */
64
+ export function fitsAnvilStreakOnHeadingRow(params: {
65
+ /** null when the user has no streak yet — measured as day one. */
66
+ line: AnvilStreakLine | null
67
+ headingWidth: number
68
+ availableWidth: number
69
+ }): boolean {
70
+ return (
71
+ params.headingWidth +
72
+ ANVIL_STREAK_INLINE_GAP +
73
+ getAnvilStreakInlineWidth(params.line ?? DAY_ONE_LINE) <=
74
+ params.availableWidth
75
+ )
76
+ }
77
+
78
+ /** Returns the earned perk note only when the landing layout can show it
79
+ * without crowding the picker or wrapping onto additional rows. */
80
+ export function getAnvilStreakBonusNoteForLayout(params: {
81
+ streak: number
82
+ accessTier: 'full' | 'limited'
83
+ terminalHeight: number
84
+ availableWidth: number
85
+ }): string | null {
86
+ if (params.streak < ANVIL_STREAK_WEEK) return null
87
+ if (params.terminalHeight < ANVIL_STREAK_BONUS_MIN_HEIGHT) return null
88
+
89
+ const note = getAnvilStreakBonusNote(params)
90
+ if (!note || note.length > params.availableWidth) return null
91
+
92
+ return note
93
+ }
@@ -0,0 +1,117 @@
1
+ /** Python-style range function */
2
+ export function* range(start: number, stop?: number, step: number = 1) {
3
+ if (stop === undefined) {
4
+ stop = start
5
+ start = 0
6
+ }
7
+
8
+ if (step === 0) {
9
+ throw new Error('Step cannot be zero')
10
+ }
11
+
12
+ if (step > 0) {
13
+ for (let i = start; i < stop; i += step) {
14
+ yield i
15
+ }
16
+ } else {
17
+ for (let i = start; i > stop; i += step) {
18
+ yield i
19
+ }
20
+ }
21
+ }
22
+
23
+ export function repeated<T>(value: T, count: number): T[] {
24
+ return Array.from({ length: count }, () => value)
25
+ }
26
+
27
+ export class Queue<T> {
28
+ private _items: T[]
29
+ private _head: number
30
+ private _length: number
31
+ private _defaultCapacity: number
32
+
33
+ constructor(items: T[] | undefined = undefined, capacity: number = 100) {
34
+ this._defaultCapacity = capacity
35
+ if (!items) {
36
+ items = []
37
+ }
38
+ this._items = items.length
39
+ ? [
40
+ ...items,
41
+ ...repeated(undefined as T, Math.max(0, capacity - items.length)),
42
+ ]
43
+ : [undefined as T]
44
+ this._head = 0
45
+ this._length = items.length
46
+ }
47
+
48
+ static from<T>(iterable: Iterable<T>): Queue<T> {
49
+ return new Queue<T>([...iterable])
50
+ }
51
+
52
+ enqueue(...items: T[]) {
53
+ if (this._items.length < this._length + items.length) {
54
+ const newItems = [
55
+ ...repeated(undefined as T, this._length),
56
+ ...items,
57
+ ...repeated(undefined as T, this._length),
58
+ ]
59
+ for (let i = 0; i < this._length; i++) {
60
+ newItems[i] = this.at(i)!
61
+ }
62
+
63
+ this._items = newItems
64
+ this._head = 0
65
+ this._length += items.length
66
+ return
67
+ }
68
+
69
+ let index = (this._head + this._length) % this._items.length
70
+ for (const item of items) {
71
+ this._items[index] = item
72
+ index = (index + 1) % this._items.length
73
+ }
74
+ this._length += items.length
75
+ }
76
+
77
+ dequeue(): T | undefined {
78
+ if (this._length === 0) {
79
+ return undefined
80
+ }
81
+
82
+ const item = this._items[this._head]
83
+ this._items[this._head] = undefined as T
84
+ this._head = (this._head + 1) % this._items.length
85
+ this._length--
86
+ return item
87
+ }
88
+
89
+ peek(): T | undefined {
90
+ if (this._length === 0) {
91
+ return undefined
92
+ }
93
+ return this._items[this._head]
94
+ }
95
+
96
+ at(index: number): T | undefined {
97
+ if (index >= this._length || index < -this.length) {
98
+ return undefined
99
+ }
100
+ if (index < 0) {
101
+ return this._items[
102
+ (this._head + this._length + index) % this._items.length
103
+ ]
104
+ }
105
+ return this._items[(this._head + index) % this._items.length]
106
+ }
107
+
108
+ clear(): void {
109
+ this._items = repeated(undefined as T, this._defaultCapacity)
110
+ this._head = 0
111
+ this._length = 0
112
+ }
113
+
114
+ get length() {
115
+ return this._length
116
+ }
117
+ }
@@ -0,0 +1,258 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ import { getCiEnv } from '@anvil/common/env-ci'
5
+ import { getDirectDeepSeekApiKey } from '@anvil/common/constants/deepseek-direct'
6
+ import { z } from 'zod'
7
+
8
+
9
+ import { getApiClient, setApiClientAuthToken } from './anvil-api'
10
+ import { getConfigDir as getConfigDirBase } from './config-dir'
11
+ import { logger } from './logger'
12
+
13
+ import type { CiEnv } from '@anvil/common/types/contracts/env'
14
+
15
+ // User schema
16
+ const userSchema = z.object({
17
+ id: z.string().optional(),
18
+ name: z.string(),
19
+ email: z.string(),
20
+ authToken: z.string(),
21
+ fingerprintId: z.string().optional(),
22
+ fingerprintHash: z.string().optional(),
23
+ credits: z.number().optional(),
24
+ })
25
+
26
+ export type User = z.infer<typeof userSchema>
27
+
28
+ const credentialsSchema = z
29
+ .object({
30
+ default: userSchema.optional(),
31
+ })
32
+ .catchall(z.unknown())
33
+
34
+ // Get the config directory path.
35
+ // Re-exported as a wrapper (rather than a bare `export ... from`) so existing
36
+ // tests can still `spyOn(authModule, 'getConfigDir')`.
37
+ export const getConfigDir = (): string => getConfigDirBase()
38
+
39
+ // Get the credentials file path
40
+ export const getCredentialsPath = (): string => {
41
+ return path.join(getConfigDir(), 'credentials.json')
42
+ }
43
+
44
+ /**
45
+ * Parse user from JSON string
46
+ */
47
+ const userFromJson = (
48
+ json: string,
49
+ profileName: string = 'default',
50
+ ): User | undefined => {
51
+ try {
52
+ const allCredentials = credentialsSchema.parse(JSON.parse(json))
53
+ const profile = allCredentials[profileName]
54
+ // Validate that the profile matches the user schema
55
+ const parsed = userSchema.safeParse(profile)
56
+ return parsed.success ? parsed.data : undefined
57
+ } catch (error) {
58
+ logger.error(
59
+ {
60
+ errorMessage: error instanceof Error ? error.message : String(error),
61
+ errorStack: error instanceof Error ? error.stack : undefined,
62
+ profileName,
63
+ },
64
+ 'Error parsing user JSON',
65
+ )
66
+ return
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Get user credentials from file system
72
+ * @returns User object or null if not found/authenticated
73
+ */
74
+ export const getUserCredentials = (): User | null => {
75
+ const credentialsPath = getCredentialsPath()
76
+
77
+ // Read user credentials directly from file
78
+ if (!fs.existsSync(credentialsPath)) {
79
+ return null
80
+ }
81
+
82
+ try {
83
+ const credentialsFile = fs.readFileSync(credentialsPath, 'utf8')
84
+ const user = userFromJson(credentialsFile)
85
+ return user || null
86
+ } catch (error) {
87
+ logger.error(
88
+ {
89
+ error: error instanceof Error ? error.message : String(error),
90
+ },
91
+ 'Error reading credentials',
92
+ )
93
+ return null
94
+ }
95
+ }
96
+
97
+ export type AuthTokenSource = 'credentials' | 'environment' | null
98
+
99
+ export interface AuthTokenDetails {
100
+ token?: string
101
+ source: AuthTokenSource
102
+ }
103
+
104
+ /**
105
+ * Resolve the auth token and track where it came from.
106
+ */
107
+ export const getAuthTokenDetails = (
108
+ ciEnv: CiEnv = getCiEnv(),
109
+ ): AuthTokenDetails => {
110
+ const userCredentials = getUserCredentials()
111
+ if (userCredentials?.authToken) {
112
+ return { token: userCredentials.authToken, source: 'credentials' }
113
+ }
114
+
115
+ const envToken = ciEnv.ANVIL_API_KEY
116
+ if (envToken) {
117
+ return { token: envToken, source: 'environment' }
118
+ }
119
+
120
+ // Direct-to-DeepSeek mode: the DeepSeek key doubles as the auth token so
121
+ // the CLI treats the user as authenticated without a Anvil backend.
122
+ const deepseekToken = getDirectDeepSeekApiKey()
123
+ if (deepseekToken) {
124
+ return { token: deepseekToken, source: 'environment' }
125
+ }
126
+
127
+ return { source: null }
128
+ }
129
+
130
+ /**
131
+ * Get the auth token from user credentials or environment variable
132
+ */
133
+ export const getAuthToken = (): string | undefined => {
134
+ return getAuthTokenDetails().token
135
+ }
136
+
137
+ /**
138
+ * Check if the user has authentication credentials (but doesn't validate them)
139
+ */
140
+ export const hasAuthCredentials = (): boolean => {
141
+ return !!getAuthTokenDetails().token
142
+ }
143
+
144
+ export interface AuthValidationResult {
145
+ authenticated: boolean
146
+ hasInvalidCredentials: boolean
147
+ }
148
+
149
+ /**
150
+ * Read existing credentials file, returns empty object if missing/invalid.
151
+ *
152
+ * Drops `chatgptOAuth`, which the removed ChatGPT integration wrote. Both
153
+ * callers spread this result straight back over the file, so the dead key —
154
+ * an OAuth access and refresh token for the user's ChatGPT account — is
155
+ * cleaned up the next time we write for any reason. Doing it here rather than
156
+ * in a startup pass means no extra write and no new race with login/logout;
157
+ * nothing reads the key anymore, and with /connect gone the user has no way
158
+ * to clear it themselves.
159
+ */
160
+ const readCredentialsFile = (): Record<string, unknown> => {
161
+ const credentialsPath = getCredentialsPath()
162
+ if (!fs.existsSync(credentialsPath)) return {}
163
+ try {
164
+ const { chatgptOAuth: _removedIntegration, ...rest } = JSON.parse(
165
+ fs.readFileSync(credentialsPath, 'utf8'),
166
+ )
167
+ return rest
168
+ } catch {
169
+ return {}
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Save user credentials to file system.
175
+ */
176
+ export const saveUserCredentials = (user: User): void => {
177
+ const configDir = getConfigDir()
178
+ const credentialsPath = getCredentialsPath()
179
+
180
+ try {
181
+ if (!fs.existsSync(configDir)) {
182
+ fs.mkdirSync(configDir, { recursive: true })
183
+ }
184
+
185
+ const updatedData = { ...readCredentialsFile(), default: user }
186
+ fs.writeFileSync(credentialsPath, JSON.stringify(updatedData, null, 2))
187
+ } catch (error) {
188
+ logger.error(
189
+ {
190
+ error: error instanceof Error ? error.message : String(error),
191
+ },
192
+ 'Error saving credentials',
193
+ )
194
+ throw error
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Clear user credentials from file system.
200
+ * Only removes the 'default' field, preserving other credentials.
201
+ */
202
+ export const clearUserCredentials = (): void => {
203
+ const credentialsPath = getCredentialsPath()
204
+
205
+ try {
206
+ if (!fs.existsSync(credentialsPath)) return
207
+
208
+ const { default: _, ...rest } = readCredentialsFile()
209
+
210
+ if (Object.keys(rest).length === 0) {
211
+ fs.unlinkSync(credentialsPath)
212
+ } else {
213
+ fs.writeFileSync(credentialsPath, JSON.stringify(rest, null, 2))
214
+ }
215
+ } catch (error) {
216
+ logger.error(
217
+ {
218
+ error: error instanceof Error ? error.message : String(error),
219
+ },
220
+ 'Error clearing credentials',
221
+ )
222
+ throw error
223
+ }
224
+ }
225
+
226
+ export async function logoutUser(): Promise<boolean> {
227
+ try {
228
+ const user = getUserCredentials()
229
+ if (user?.authToken) {
230
+ setApiClientAuthToken(user.authToken)
231
+ const apiClient = getApiClient()
232
+ try {
233
+ const response = await apiClient.logout({
234
+ userId: user.id,
235
+ fingerprintId: user.fingerprintId,
236
+ fingerprintHash: user.fingerprintHash,
237
+ })
238
+ if (!response.ok) {
239
+ logger.error(
240
+ { status: response.status, error: response.error },
241
+ 'Logout request failed',
242
+ )
243
+ }
244
+ } catch (err) {
245
+ logger.error(err, 'Logout request error')
246
+ }
247
+ }
248
+ } catch (error) {
249
+ logger.error(error, 'Unexpected error preparing logout')
250
+ }
251
+
252
+ try {
253
+ clearUserCredentials()
254
+ } catch (error) {
255
+ logger.debug({ error }, 'Failed to clear credentials during logout')
256
+ }
257
+ return true
258
+ }
@@ -0,0 +1,47 @@
1
+ import {
2
+ buildBashHistoryMessages,
3
+ createRunTerminalToolResult,
4
+ formatBashContextForPrompt,
5
+ } from './bash-messages'
6
+
7
+ import type { PendingBashMessage } from '../types/store'
8
+ import type { ChatMessage } from '../types/chat'
9
+
10
+ // Turns pending bash executions into chat history messages and prompt context.
11
+ export const processBashContext = (
12
+ pendingBashMessages: PendingBashMessage[],
13
+ ): {
14
+ bashMessages: ChatMessage[]
15
+ bashContextForPrompt: string
16
+ } => {
17
+ const bashContextForPrompt = formatBashContextForPrompt(pendingBashMessages)
18
+ const bashMessages: ChatMessage[] = []
19
+
20
+ for (const bash of pendingBashMessages) {
21
+ if (bash.addedToHistory) {
22
+ continue
23
+ }
24
+
25
+ const toolCallId = crypto.randomUUID()
26
+ const cwd = bash.cwd || process.cwd()
27
+ const toolResultOutput = createRunTerminalToolResult({
28
+ command: bash.command,
29
+ cwd,
30
+ stdout: bash.stdout || null,
31
+ stderr: bash.stderr || null,
32
+ exitCode: bash.exitCode,
33
+ })
34
+ const outputJson = JSON.stringify(toolResultOutput)
35
+ const { assistantMessage } = buildBashHistoryMessages({
36
+ command: bash.command,
37
+ cwd,
38
+ toolCallId,
39
+ output: outputJson,
40
+ isComplete: true,
41
+ })
42
+
43
+ bashMessages.push(assistantMessage)
44
+ }
45
+
46
+ return { bashMessages, bashContextForPrompt }
47
+ }
@@ -0,0 +1,108 @@
1
+ import { formatTimestamp } from './helpers'
2
+
3
+ import type { PendingBashMessage } from '../types/store'
4
+ import type { ChatMessage, ContentBlock } from '../types/chat'
5
+ import type { ToolResultOutput } from '@anvil/common/types/messages/content-part'
6
+
7
+ export function createRunTerminalToolResult(params: {
8
+ command: string
9
+ cwd: string
10
+ stdout: string | null
11
+ stderr: string | null
12
+ exitCode: number
13
+ errorMessage?: string
14
+ }): ToolResultOutput[] {
15
+ const { command, cwd, stdout, stderr, exitCode, errorMessage } = params
16
+ if (errorMessage) {
17
+ return [
18
+ {
19
+ type: 'json' as const,
20
+ value: { command, startingCwd: cwd, errorMessage },
21
+ },
22
+ ]
23
+ }
24
+ return [
25
+ {
26
+ type: 'json' as const,
27
+ value: {
28
+ command,
29
+ startingCwd: cwd,
30
+ stdout: stdout || null,
31
+ stderr: stderr || null,
32
+ exitCode,
33
+ },
34
+ },
35
+ ]
36
+ }
37
+
38
+ export function buildBashHistoryMessages(params: {
39
+ command: string
40
+ cwd: string
41
+ toolCallId?: string
42
+ output?: string
43
+ isComplete?: boolean
44
+ }): {
45
+ assistantMessage: ChatMessage
46
+ toolCallId: string
47
+ } {
48
+ const { command, cwd, output = '...', isComplete = false } = params
49
+ const toolCallId = params.toolCallId ?? crypto.randomUUID()
50
+
51
+ const toolBlock: ContentBlock = {
52
+ type: 'tool',
53
+ toolName: 'run_terminal_command',
54
+ toolCallId,
55
+ input: { command },
56
+ output,
57
+ }
58
+
59
+ const assistantMessage: ChatMessage = {
60
+ id: `bash-result-${toolCallId}`,
61
+ variant: 'ai',
62
+ content: '',
63
+ blocks: [toolBlock],
64
+ timestamp: formatTimestamp(),
65
+ isComplete,
66
+ metadata: { bashCwd: cwd },
67
+ }
68
+
69
+ return { assistantMessage, toolCallId }
70
+ }
71
+
72
+ /**
73
+ * Format pending bash messages as context to prepend to the user's prompt.
74
+ * This provides the LLM with information about commands the user ran manually.
75
+ * Only includes completed (non-running) commands.
76
+ */
77
+ export function formatBashContextForPrompt(
78
+ pendingBashMessages: PendingBashMessage[],
79
+ ): string {
80
+ // Only include completed commands
81
+ const completedCommands = pendingBashMessages.filter((msg) => !msg.isRunning)
82
+
83
+ if (completedCommands.length === 0) {
84
+ return ''
85
+ }
86
+
87
+ const commandsContext = completedCommands
88
+ .map((bash) => {
89
+ const cwd = bash.cwd || process.cwd()
90
+ let result = `Command: ${bash.command}\nDirectory: ${cwd}\nExit code: ${bash.exitCode}`
91
+ if (bash.stdout) {
92
+ result += `\nStdout:\n${bash.stdout}`
93
+ }
94
+ if (bash.stderr) {
95
+ result += `\nStderr:\n${bash.stderr}`
96
+ }
97
+ return result
98
+ })
99
+ .join('\n\n---\n\n')
100
+
101
+ return `<user_terminal_commands>
102
+ The user ran the following terminal command(s) before this message:
103
+
104
+ ${commandsContext}
105
+ </user_terminal_commands>
106
+
107
+ `
108
+ }
@@ -0,0 +1,35 @@
1
+ import type { ContentBlock, TextContentBlock } from '../types/chat'
2
+
3
+ /**
4
+ * Margin calculation result for a content block.
5
+ */
6
+ export interface BlockMargins {
7
+ marginTop: number
8
+ marginBottom: number
9
+ }
10
+
11
+ /** Extracts margins for a text block, suppressing top margin after tool/agent blocks. */
12
+ export function extractTextBlockMargins(
13
+ block: TextContentBlock,
14
+ prevBlock: ContentBlock | null,
15
+ ): BlockMargins {
16
+ const prevBlockSuppressesMargin =
17
+ prevBlock !== null &&
18
+ (prevBlock.type === 'tool' || prevBlock.type === 'agent')
19
+
20
+ const marginTop = prevBlockSuppressesMargin ? 0 : (block.marginTop ?? 0)
21
+ const marginBottom = block.marginBottom ?? 0
22
+
23
+ return { marginTop, marginBottom }
24
+ }
25
+
26
+ /** Extracts margins for an HTML block using explicit values without context adjustments. */
27
+ export function extractHtmlBlockMargins(block: {
28
+ marginTop?: number
29
+ marginBottom?: number
30
+ }): BlockMargins {
31
+ return {
32
+ marginTop: block.marginTop ?? 0,
33
+ marginBottom: block.marginBottom ?? 0,
34
+ }
35
+ }