@butlerw/vellum 0.1.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 (446) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -0
  3. package/__fixtures__/responses/code-generation.json +42 -0
  4. package/__fixtures__/responses/error-response.json +20 -0
  5. package/__fixtures__/responses/hello-world.json +32 -0
  6. package/dist/auth-6MCXESOH.js +26 -0
  7. package/dist/chunk-SECXJGWA.js +597 -0
  8. package/dist/index.js +34023 -0
  9. package/package.json +67 -0
  10. package/src/__tests__/commands.e2e.test.ts +728 -0
  11. package/src/__tests__/credentials.test.ts +713 -0
  12. package/src/__tests__/mode-e2e.test.ts +391 -0
  13. package/src/__tests__/tui-integration.test.tsx +1271 -0
  14. package/src/agents/__tests__/task-persistence.test.ts +235 -0
  15. package/src/agents/commands/delegate.ts +240 -0
  16. package/src/agents/commands/index.ts +10 -0
  17. package/src/agents/commands/resume.ts +335 -0
  18. package/src/agents/index.ts +29 -0
  19. package/src/agents/task-persistence.ts +272 -0
  20. package/src/agents/task-resumption.ts +242 -0
  21. package/src/app.tsx +4737 -0
  22. package/src/commands/__tests__/.gitkeep +1 -0
  23. package/src/commands/__tests__/agents.test.ts +606 -0
  24. package/src/commands/__tests__/auth.test.ts +626 -0
  25. package/src/commands/__tests__/autocomplete.test.ts +683 -0
  26. package/src/commands/__tests__/batch.test.ts +287 -0
  27. package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
  28. package/src/commands/__tests__/completion.test.ts +238 -0
  29. package/src/commands/__tests__/core.test.ts +363 -0
  30. package/src/commands/__tests__/executor.test.ts +496 -0
  31. package/src/commands/__tests__/exit-codes.test.ts +220 -0
  32. package/src/commands/__tests__/init.test.ts +243 -0
  33. package/src/commands/__tests__/language.test.ts +353 -0
  34. package/src/commands/__tests__/mode-cli.test.ts +667 -0
  35. package/src/commands/__tests__/model.test.ts +277 -0
  36. package/src/commands/__tests__/parser.test.ts +493 -0
  37. package/src/commands/__tests__/performance.bench.ts +380 -0
  38. package/src/commands/__tests__/registry.test.ts +534 -0
  39. package/src/commands/__tests__/resume.test.ts +449 -0
  40. package/src/commands/__tests__/security.test.ts +845 -0
  41. package/src/commands/__tests__/stream-json.test.ts +372 -0
  42. package/src/commands/__tests__/user-commands.test.ts +597 -0
  43. package/src/commands/adapters.ts +267 -0
  44. package/src/commands/agent.ts +395 -0
  45. package/src/commands/agents/generate.ts +506 -0
  46. package/src/commands/agents/index.ts +272 -0
  47. package/src/commands/agents/show.ts +271 -0
  48. package/src/commands/agents/validate.ts +387 -0
  49. package/src/commands/auth.ts +883 -0
  50. package/src/commands/autocomplete.ts +480 -0
  51. package/src/commands/batch/command.ts +388 -0
  52. package/src/commands/batch/executor.ts +361 -0
  53. package/src/commands/batch/index.ts +12 -0
  54. package/src/commands/commit.ts +235 -0
  55. package/src/commands/completion/index.ts +371 -0
  56. package/src/commands/condense.ts +191 -0
  57. package/src/commands/config.ts +344 -0
  58. package/src/commands/context-provider.ts +173 -0
  59. package/src/commands/copy.ts +329 -0
  60. package/src/commands/core/clear.ts +38 -0
  61. package/src/commands/core/exit.ts +43 -0
  62. package/src/commands/core/help.ts +354 -0
  63. package/src/commands/core/index.ts +15 -0
  64. package/src/commands/cost.ts +179 -0
  65. package/src/commands/credentials.tsx +618 -0
  66. package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
  67. package/src/commands/custom-agents/create.ts +377 -0
  68. package/src/commands/custom-agents/export.ts +135 -0
  69. package/src/commands/custom-agents/import.ts +199 -0
  70. package/src/commands/custom-agents/index.ts +372 -0
  71. package/src/commands/custom-agents/info.ts +318 -0
  72. package/src/commands/custom-agents/list.ts +267 -0
  73. package/src/commands/custom-agents/validate.ts +388 -0
  74. package/src/commands/diff-mode.ts +241 -0
  75. package/src/commands/env.ts +53 -0
  76. package/src/commands/executor.ts +579 -0
  77. package/src/commands/exit-codes.ts +202 -0
  78. package/src/commands/index.ts +701 -0
  79. package/src/commands/init/index.ts +15 -0
  80. package/src/commands/init/prompts.ts +366 -0
  81. package/src/commands/init/templates/commands-readme.md +80 -0
  82. package/src/commands/init/templates/example-command.md +79 -0
  83. package/src/commands/init/templates/example-skill.md +168 -0
  84. package/src/commands/init/templates/example-workflow.md +101 -0
  85. package/src/commands/init/templates/prompts-readme.md +52 -0
  86. package/src/commands/init/templates/rules-readme.md +63 -0
  87. package/src/commands/init/templates/skills-readme.md +83 -0
  88. package/src/commands/init/templates/workflows-readme.md +94 -0
  89. package/src/commands/init.ts +391 -0
  90. package/src/commands/install.ts +90 -0
  91. package/src/commands/language.ts +191 -0
  92. package/src/commands/loaders/.gitkeep +1 -0
  93. package/src/commands/lsp.ts +199 -0
  94. package/src/commands/markdown-commands.ts +253 -0
  95. package/src/commands/mcp.ts +588 -0
  96. package/src/commands/memory/export.ts +341 -0
  97. package/src/commands/memory/index.ts +148 -0
  98. package/src/commands/memory/list.ts +261 -0
  99. package/src/commands/memory/search.ts +346 -0
  100. package/src/commands/memory/utils.ts +15 -0
  101. package/src/commands/metrics.ts +75 -0
  102. package/src/commands/migrate/index.ts +16 -0
  103. package/src/commands/migrate/prompts.ts +477 -0
  104. package/src/commands/mode.ts +331 -0
  105. package/src/commands/model.ts +298 -0
  106. package/src/commands/onboard.ts +205 -0
  107. package/src/commands/open.ts +169 -0
  108. package/src/commands/output/stream-json.ts +373 -0
  109. package/src/commands/parser/chain-parser.ts +370 -0
  110. package/src/commands/parser/index.ts +29 -0
  111. package/src/commands/parser/pipe-parser.ts +480 -0
  112. package/src/commands/parser.ts +588 -0
  113. package/src/commands/persistence.ts +355 -0
  114. package/src/commands/progress.ts +18 -0
  115. package/src/commands/prompt/index.ts +17 -0
  116. package/src/commands/prompt/validate.ts +621 -0
  117. package/src/commands/prompt-priority.ts +401 -0
  118. package/src/commands/registry.ts +374 -0
  119. package/src/commands/sandbox/index.ts +131 -0
  120. package/src/commands/security/index.ts +21 -0
  121. package/src/commands/security/input-sanitizer.ts +168 -0
  122. package/src/commands/security/permission-checker.ts +456 -0
  123. package/src/commands/security/sensitive-data.ts +350 -0
  124. package/src/commands/session/delete.ts +38 -0
  125. package/src/commands/session/export.ts +39 -0
  126. package/src/commands/session/index.ts +26 -0
  127. package/src/commands/session/list.ts +26 -0
  128. package/src/commands/session/resume.ts +562 -0
  129. package/src/commands/session/search.ts +434 -0
  130. package/src/commands/session/show.ts +26 -0
  131. package/src/commands/settings.ts +368 -0
  132. package/src/commands/setup.ts +23 -0
  133. package/src/commands/shell/index.ts +16 -0
  134. package/src/commands/shell/setup.ts +422 -0
  135. package/src/commands/shell-init.ts +50 -0
  136. package/src/commands/shell-integration/index.ts +194 -0
  137. package/src/commands/skill.ts +1220 -0
  138. package/src/commands/spec.ts +558 -0
  139. package/src/commands/status.ts +246 -0
  140. package/src/commands/theme.ts +211 -0
  141. package/src/commands/think.ts +551 -0
  142. package/src/commands/trust.ts +211 -0
  143. package/src/commands/tutorial.ts +522 -0
  144. package/src/commands/types.ts +512 -0
  145. package/src/commands/update.ts +274 -0
  146. package/src/commands/usage.ts +213 -0
  147. package/src/commands/user-commands.ts +630 -0
  148. package/src/commands/utils.ts +142 -0
  149. package/src/commands/vim.ts +152 -0
  150. package/src/commands/workflow.ts +257 -0
  151. package/src/components/header.tsx +25 -0
  152. package/src/components/input.tsx +25 -0
  153. package/src/components/message-list.tsx +32 -0
  154. package/src/components/status-bar.tsx +23 -0
  155. package/src/index.tsx +614 -0
  156. package/src/onboarding/__tests__/tutorial.test.ts +740 -0
  157. package/src/onboarding/index.ts +69 -0
  158. package/src/onboarding/tips/index.ts +9 -0
  159. package/src/onboarding/tips/tip-engine.ts +459 -0
  160. package/src/onboarding/tutorial/index.ts +88 -0
  161. package/src/onboarding/tutorial/lessons/basics.ts +151 -0
  162. package/src/onboarding/tutorial/lessons/index.ts +151 -0
  163. package/src/onboarding/tutorial/lessons/modes.ts +230 -0
  164. package/src/onboarding/tutorial/lessons/tools.ts +172 -0
  165. package/src/onboarding/tutorial/progress-tracker.ts +350 -0
  166. package/src/onboarding/tutorial/storage.ts +249 -0
  167. package/src/onboarding/tutorial/tutorial-system.ts +462 -0
  168. package/src/onboarding/tutorial/types.ts +310 -0
  169. package/src/orchestrator-singleton.ts +129 -0
  170. package/src/shutdown.ts +33 -0
  171. package/src/test/e2e/assertions.ts +267 -0
  172. package/src/test/e2e/fixtures.ts +204 -0
  173. package/src/test/e2e/harness.ts +575 -0
  174. package/src/test/e2e/index.ts +57 -0
  175. package/src/test/e2e/types.ts +228 -0
  176. package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
  177. package/src/test/fixtures/fake-response-loader.ts +314 -0
  178. package/src/test/fixtures/index.ts +20 -0
  179. package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
  180. package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
  181. package/src/tui/__tests__/mode-components.test.tsx +395 -0
  182. package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
  183. package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
  184. package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
  185. package/src/tui/adapters/agent-adapter.ts +1008 -0
  186. package/src/tui/adapters/index.ts +48 -0
  187. package/src/tui/adapters/message-adapter.ts +315 -0
  188. package/src/tui/adapters/persistence-bridge.ts +331 -0
  189. package/src/tui/adapters/session-adapter.ts +419 -0
  190. package/src/tui/buffered-stdout.ts +223 -0
  191. package/src/tui/components/AgentProgress.tsx +424 -0
  192. package/src/tui/components/Banner/AsciiArt.ts +160 -0
  193. package/src/tui/components/Banner/Banner.tsx +355 -0
  194. package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
  195. package/src/tui/components/Banner/ShimmerText.tsx +193 -0
  196. package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
  197. package/src/tui/components/Banner/index.ts +61 -0
  198. package/src/tui/components/Banner/useShimmer.ts +241 -0
  199. package/src/tui/components/ChatView.tsx +11 -0
  200. package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
  201. package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
  202. package/src/tui/components/Checkpoint/index.ts +19 -0
  203. package/src/tui/components/CostDisplay.tsx +226 -0
  204. package/src/tui/components/InitErrorBanner.tsx +122 -0
  205. package/src/tui/components/Input/Autocomplete.tsx +603 -0
  206. package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
  207. package/src/tui/components/Input/HighlightedText.tsx +236 -0
  208. package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
  209. package/src/tui/components/Input/TextInput.tsx +1002 -0
  210. package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
  211. package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
  212. package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
  213. package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
  214. package/src/tui/components/Input/highlight.ts +362 -0
  215. package/src/tui/components/Input/index.ts +36 -0
  216. package/src/tui/components/Input/slash-command-utils.ts +135 -0
  217. package/src/tui/components/Layout.tsx +432 -0
  218. package/src/tui/components/McpPanel.tsx +137 -0
  219. package/src/tui/components/MemoryPanel.tsx +448 -0
  220. package/src/tui/components/Messages/CodeBlock.tsx +527 -0
  221. package/src/tui/components/Messages/DiffView.tsx +679 -0
  222. package/src/tui/components/Messages/ImageReference.tsx +89 -0
  223. package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
  224. package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
  225. package/src/tui/components/Messages/MessageBubble.tsx +270 -0
  226. package/src/tui/components/Messages/MessageList.tsx +1719 -0
  227. package/src/tui/components/Messages/StreamingText.tsx +216 -0
  228. package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
  229. package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
  230. package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
  231. package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
  232. package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
  233. package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
  234. package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
  235. package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
  236. package/src/tui/components/Messages/index.ts +25 -0
  237. package/src/tui/components/ModeIndicator.tsx +177 -0
  238. package/src/tui/components/ModeSelector.tsx +216 -0
  239. package/src/tui/components/ModelSelector.tsx +339 -0
  240. package/src/tui/components/OnboardingWizard.tsx +670 -0
  241. package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
  242. package/src/tui/components/RateLimitIndicator.tsx +82 -0
  243. package/src/tui/components/ScreenReaderLayout.tsx +295 -0
  244. package/src/tui/components/SettingsPanel.tsx +643 -0
  245. package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
  246. package/src/tui/components/Sidebar/index.ts +9 -0
  247. package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
  248. package/src/tui/components/Status/index.ts +12 -0
  249. package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
  250. package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
  251. package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
  252. package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
  253. package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
  254. package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
  255. package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
  256. package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
  257. package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
  258. package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
  259. package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
  260. package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
  261. package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
  262. package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
  263. package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
  264. package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
  265. package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
  266. package/src/tui/components/StatusBar/index.ts +60 -0
  267. package/src/tui/components/TipBanner.tsx +115 -0
  268. package/src/tui/components/TodoItem.tsx +208 -0
  269. package/src/tui/components/TodoPanel.tsx +455 -0
  270. package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
  271. package/src/tui/components/Tools/OptionSelector.tsx +160 -0
  272. package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
  273. package/src/tui/components/Tools/ToolParams.tsx +483 -0
  274. package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
  275. package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
  276. package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
  277. package/src/tui/components/Tools/index.ts +21 -0
  278. package/src/tui/components/TrustPrompt.tsx +279 -0
  279. package/src/tui/components/UpdateBanner.tsx +166 -0
  280. package/src/tui/components/VimModeIndicator.tsx +112 -0
  281. package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
  282. package/src/tui/components/backtrack/index.ts +13 -0
  283. package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
  284. package/src/tui/components/common/CostWarning.tsx +294 -0
  285. package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
  286. package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
  287. package/src/tui/components/common/ErrorBoundary.tsx +140 -0
  288. package/src/tui/components/common/GradientText.tsx +224 -0
  289. package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
  290. package/src/tui/components/common/HotkeyHints.tsx +70 -0
  291. package/src/tui/components/common/MaxSizedBox.tsx +354 -0
  292. package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
  293. package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
  294. package/src/tui/components/common/ScrollIndicator.tsx +160 -0
  295. package/src/tui/components/common/Spinner.tsx +342 -0
  296. package/src/tui/components/common/StreamingIndicator.tsx +316 -0
  297. package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
  298. package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
  299. package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
  300. package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
  301. package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
  302. package/src/tui/components/common/VirtualizedList/index.ts +30 -0
  303. package/src/tui/components/common/VirtualizedList/types.ts +107 -0
  304. package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
  305. package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
  306. package/src/tui/components/common/index.ts +110 -0
  307. package/src/tui/components/index.ts +79 -0
  308. package/src/tui/components/session/CheckpointPanel.tsx +323 -0
  309. package/src/tui/components/session/RollbackDialog.tsx +169 -0
  310. package/src/tui/components/session/SessionItem.tsx +136 -0
  311. package/src/tui/components/session/SessionListPanel.tsx +252 -0
  312. package/src/tui/components/session/SessionPicker.tsx +449 -0
  313. package/src/tui/components/session/SessionPreview.tsx +240 -0
  314. package/src/tui/components/session/__tests__/session.test.tsx +408 -0
  315. package/src/tui/components/session/index.ts +28 -0
  316. package/src/tui/components/session/types.ts +116 -0
  317. package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
  318. package/src/tui/components/theme/index.ts +227 -0
  319. package/src/tui/components/theme/tokens.ts +484 -0
  320. package/src/tui/config/defaults.ts +134 -0
  321. package/src/tui/config/index.ts +17 -0
  322. package/src/tui/context/AnimationContext.tsx +284 -0
  323. package/src/tui/context/AppContext.tsx +349 -0
  324. package/src/tui/context/BracketedPasteContext.tsx +372 -0
  325. package/src/tui/context/LspContext.tsx +192 -0
  326. package/src/tui/context/McpContext.tsx +325 -0
  327. package/src/tui/context/MessagesContext.tsx +870 -0
  328. package/src/tui/context/OverflowContext.tsx +213 -0
  329. package/src/tui/context/RateLimitContext.tsx +108 -0
  330. package/src/tui/context/ResilienceContext.tsx +275 -0
  331. package/src/tui/context/RootProvider.tsx +136 -0
  332. package/src/tui/context/ScrollContext.tsx +331 -0
  333. package/src/tui/context/ToolsContext.tsx +702 -0
  334. package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
  335. package/src/tui/context/index.ts +140 -0
  336. package/src/tui/enterprise-integration.ts +282 -0
  337. package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
  338. package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
  339. package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
  340. package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
  341. package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
  342. package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
  343. package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
  344. package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
  345. package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
  346. package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
  347. package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
  348. package/src/tui/hooks/index.ts +252 -0
  349. package/src/tui/hooks/useAgentLoop.ts +712 -0
  350. package/src/tui/hooks/useAlternateBuffer.ts +398 -0
  351. package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
  352. package/src/tui/hooks/useBacktrack.ts +443 -0
  353. package/src/tui/hooks/useBracketedPaste.ts +104 -0
  354. package/src/tui/hooks/useCollapsible.ts +240 -0
  355. package/src/tui/hooks/useCopyMode.ts +382 -0
  356. package/src/tui/hooks/useCostSummary.ts +75 -0
  357. package/src/tui/hooks/useDesktopNotification.ts +414 -0
  358. package/src/tui/hooks/useDiffMode.ts +44 -0
  359. package/src/tui/hooks/useFileChangeStats.ts +110 -0
  360. package/src/tui/hooks/useFileSuggestions.ts +284 -0
  361. package/src/tui/hooks/useFlickerDetector.ts +250 -0
  362. package/src/tui/hooks/useGitStatus.ts +200 -0
  363. package/src/tui/hooks/useHotkeys.ts +579 -0
  364. package/src/tui/hooks/useImagePaste.ts +114 -0
  365. package/src/tui/hooks/useInputHighlight.ts +145 -0
  366. package/src/tui/hooks/useInputHistory.ts +246 -0
  367. package/src/tui/hooks/useKeyboardScroll.ts +209 -0
  368. package/src/tui/hooks/useLineBuffer.ts +356 -0
  369. package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
  370. package/src/tui/hooks/useModeController.ts +167 -0
  371. package/src/tui/hooks/useModeShortcuts.ts +196 -0
  372. package/src/tui/hooks/usePermissionHandler.ts +146 -0
  373. package/src/tui/hooks/usePersistence.ts +480 -0
  374. package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
  375. package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
  376. package/src/tui/hooks/useProviderStatus.ts +270 -0
  377. package/src/tui/hooks/useRateLimitStatus.ts +90 -0
  378. package/src/tui/hooks/useScreenReader.ts +315 -0
  379. package/src/tui/hooks/useScrollController.ts +450 -0
  380. package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
  381. package/src/tui/hooks/useSidebarPanelData.ts +115 -0
  382. package/src/tui/hooks/useSmoothScroll.ts +202 -0
  383. package/src/tui/hooks/useSnapshots.ts +300 -0
  384. package/src/tui/hooks/useStateAndRef.ts +50 -0
  385. package/src/tui/hooks/useTerminalSize.ts +206 -0
  386. package/src/tui/hooks/useToolApprovalController.ts +91 -0
  387. package/src/tui/hooks/useVim.ts +334 -0
  388. package/src/tui/hooks/useWorkspace.ts +56 -0
  389. package/src/tui/i18n/__tests__/init.test.ts +278 -0
  390. package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
  391. package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
  392. package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
  393. package/src/tui/i18n/index.ts +72 -0
  394. package/src/tui/i18n/init.ts +131 -0
  395. package/src/tui/i18n/language-config.ts +106 -0
  396. package/src/tui/i18n/locale-detection.ts +173 -0
  397. package/src/tui/i18n/settings-integration.ts +557 -0
  398. package/src/tui/i18n/tui-namespace.ts +538 -0
  399. package/src/tui/i18n/types.ts +312 -0
  400. package/src/tui/index.ts +43 -0
  401. package/src/tui/lsp-integration.ts +409 -0
  402. package/src/tui/metrics-integration.ts +366 -0
  403. package/src/tui/plugins.ts +383 -0
  404. package/src/tui/resilience.ts +342 -0
  405. package/src/tui/sandbox-integration.ts +317 -0
  406. package/src/tui/services/clipboard.ts +348 -0
  407. package/src/tui/services/fuzzy-search.ts +441 -0
  408. package/src/tui/services/index.ts +72 -0
  409. package/src/tui/services/markdown-renderer.ts +565 -0
  410. package/src/tui/services/open-external.ts +247 -0
  411. package/src/tui/services/syntax-highlighter.ts +483 -0
  412. package/src/tui/slash-commands.ts +12 -0
  413. package/src/tui/theme/index.ts +15 -0
  414. package/src/tui/theme/provider.tsx +206 -0
  415. package/src/tui/tip-integration.ts +300 -0
  416. package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
  417. package/src/tui/types/ink-extended.ts +87 -0
  418. package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
  419. package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
  420. package/src/tui/utils/__tests__/text-width.test.ts +158 -0
  421. package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
  422. package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
  423. package/src/tui/utils/bracketedPaste.ts +107 -0
  424. package/src/tui/utils/cursor-manager.ts +131 -0
  425. package/src/tui/utils/detectTerminal.ts +596 -0
  426. package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
  427. package/src/tui/utils/heightEstimator.ts +198 -0
  428. package/src/tui/utils/index.ts +91 -0
  429. package/src/tui/utils/isNarrowWidth.ts +52 -0
  430. package/src/tui/utils/stdoutGuard.ts +90 -0
  431. package/src/tui/utils/synchronized-update.ts +70 -0
  432. package/src/tui/utils/text-width.ts +225 -0
  433. package/src/tui/utils/textSanitizer.ts +225 -0
  434. package/src/tui/utils/textUtils.ts +114 -0
  435. package/src/tui/utils/ui-sizing.ts +192 -0
  436. package/src/tui-blessed/app.ts +160 -0
  437. package/src/tui-blessed/index.ts +2 -0
  438. package/src/tui-blessed/neo-blessed.d.ts +6 -0
  439. package/src/tui-blessed/test.ts +21 -0
  440. package/src/tui-blessed/types.ts +14 -0
  441. package/src/utils/icons.ts +130 -0
  442. package/src/utils/index.ts +33 -0
  443. package/src/utils/resume-hint.ts +86 -0
  444. package/src/version.ts +1 -0
  445. package/tsconfig.json +8 -0
  446. package/vitest.config.ts +35 -0
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Enterprise Integration
3
+ *
4
+ * Wires MCP enterprise features (audit logging, server validation,
5
+ * tool blocking) to the TUI application when enterprise config is present.
6
+ *
7
+ * @module cli/tui/enterprise-integration
8
+ */
9
+
10
+ import {
11
+ // Audit logging
12
+ type AuditEvent,
13
+ type AuditEventType,
14
+ AuditLogger,
15
+ // Configuration
16
+ type FullEnterpriseConfig,
17
+ getAuditLogger,
18
+ getEnterpriseConfigPath,
19
+ getFullEnterpriseConfig,
20
+ initializeAuditLogger,
21
+ isEnterpriseMode,
22
+ loadFullEnterpriseConfig,
23
+ type ServerInfo,
24
+ type ServerValidationResult,
25
+ shutdownAuditLogger,
26
+ type ToolCallInfo,
27
+ type ToolValidationResult,
28
+ // Server validation
29
+ validateServer,
30
+ validateToolCall,
31
+ } from "@vellum/mcp";
32
+
33
+ // =============================================================================
34
+ // Types
35
+ // =============================================================================
36
+
37
+ export interface EnterpriseIntegrationResult {
38
+ /** Whether enterprise mode is enabled */
39
+ enabled: boolean;
40
+ /** Path to enterprise config file */
41
+ configPath: string;
42
+ /** Loaded enterprise configuration */
43
+ config: FullEnterpriseConfig | null;
44
+ /** Audit logger instance */
45
+ auditLogger: AuditLogger | null;
46
+ /** Error message if initialization failed */
47
+ error?: string;
48
+ }
49
+
50
+ export interface EnterpriseHooks {
51
+ /** Hook called before tool execution for validation */
52
+ onBeforeToolCall: (tool: ToolCallInfo) => Promise<{ allowed: boolean; reason?: string }>;
53
+ /** Hook called after tool execution for audit logging */
54
+ onAfterToolCall: (tool: ToolCallInfo, result: unknown, durationMs: number) => Promise<void>;
55
+ /** Hook called to validate MCP server connection */
56
+ onServerConnect: (server: ServerInfo) => Promise<{ allowed: boolean; reason?: string }>;
57
+ /** Hook called on user authentication (if applicable) */
58
+ onUserAction: (action: string, metadata?: Record<string, unknown>) => Promise<void>;
59
+ }
60
+
61
+ // =============================================================================
62
+ // Enterprise Integration
63
+ // =============================================================================
64
+
65
+ let enterpriseConfig: FullEnterpriseConfig | null = null;
66
+ let enterpriseInitialized = false;
67
+
68
+ /**
69
+ * Load and initialize enterprise configuration.
70
+ *
71
+ * @returns Enterprise integration result
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * const enterprise = await initializeEnterprise();
76
+ * if (enterprise.enabled) {
77
+ * console.log("Enterprise mode active");
78
+ * console.log("Audit logging:", enterprise.config?.audit?.enabled);
79
+ * }
80
+ * ```
81
+ */
82
+ export async function initializeEnterprise(): Promise<EnterpriseIntegrationResult> {
83
+ const configPath = getEnterpriseConfigPath();
84
+
85
+ // Check if enterprise mode is available
86
+ if (!isEnterpriseMode()) {
87
+ return {
88
+ enabled: false,
89
+ configPath,
90
+ config: null,
91
+ auditLogger: null,
92
+ };
93
+ }
94
+
95
+ try {
96
+ // Load full enterprise configuration
97
+ const config = await loadFullEnterpriseConfig();
98
+ enterpriseConfig = config;
99
+
100
+ // Initialize audit logger if enabled
101
+ let auditLogger: AuditLogger | null = null;
102
+ if (config.audit?.enabled) {
103
+ auditLogger = await initializeAuditLogger({ config });
104
+ }
105
+
106
+ enterpriseInitialized = true;
107
+
108
+ return {
109
+ enabled: true,
110
+ configPath,
111
+ config,
112
+ auditLogger,
113
+ };
114
+ } catch (error) {
115
+ return {
116
+ enabled: false,
117
+ configPath,
118
+ config: null,
119
+ auditLogger: null,
120
+ error: error instanceof Error ? error.message : String(error),
121
+ };
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get the current enterprise configuration.
127
+ *
128
+ * @returns Enterprise configuration or null if not in enterprise mode
129
+ */
130
+ export function getEnterpriseConfig(): FullEnterpriseConfig | null {
131
+ return enterpriseConfig ?? getFullEnterpriseConfig();
132
+ }
133
+
134
+ /**
135
+ * Check if enterprise mode is currently active.
136
+ *
137
+ * @returns Whether enterprise mode is active
138
+ */
139
+ export function isEnterpriseActive(): boolean {
140
+ return enterpriseInitialized && enterpriseConfig !== null;
141
+ }
142
+
143
+ /**
144
+ * Create enterprise hooks for tool and server validation.
145
+ *
146
+ * @returns Enterprise hooks object
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * const hooks = createEnterpriseHooks();
151
+ *
152
+ * // Before tool execution
153
+ * const validation = await hooks.onBeforeToolCall({
154
+ * serverName: "my-server",
155
+ * toolName: "bash",
156
+ * arguments: { command: "ls -la" },
157
+ * });
158
+ * if (!validation.allowed) {
159
+ * console.error("Tool blocked:", validation.reason);
160
+ * }
161
+ * ```
162
+ */
163
+ export function createEnterpriseHooks(): EnterpriseHooks {
164
+ return {
165
+ async onBeforeToolCall(tool: ToolCallInfo) {
166
+ if (!isEnterpriseActive() || !enterpriseConfig) {
167
+ return { allowed: true };
168
+ }
169
+
170
+ const result: ToolValidationResult = validateToolCall(tool, enterpriseConfig);
171
+ if (!result.allowed) {
172
+ // Audit log the blocked tool call
173
+ const logger = getAuditLogger();
174
+ if (logger) {
175
+ await logger.log({
176
+ eventType: "tool_blocked",
177
+ toolName: tool.toolName,
178
+ serverName: tool.serverName,
179
+ metadata: {
180
+ reason: result.reason ?? "Enterprise policy violation",
181
+ arguments: tool.arguments,
182
+ },
183
+ });
184
+ }
185
+ }
186
+
187
+ return { allowed: result.allowed, reason: result.reason };
188
+ },
189
+
190
+ async onAfterToolCall(tool: ToolCallInfo, result: unknown, durationMs: number) {
191
+ if (!isEnterpriseActive()) {
192
+ return;
193
+ }
194
+
195
+ const logger = getAuditLogger();
196
+ if (logger) {
197
+ await logger.log({
198
+ eventType: "tool_call",
199
+ toolName: tool.toolName,
200
+ serverName: tool.serverName,
201
+ metadata: {
202
+ durationMs,
203
+ arguments: tool.arguments,
204
+ resultSummary:
205
+ typeof result === "object" && result !== null
206
+ ? Object.keys(result).join(", ")
207
+ : typeof result,
208
+ },
209
+ });
210
+ }
211
+ },
212
+
213
+ async onServerConnect(server: ServerInfo) {
214
+ if (!isEnterpriseActive() || !enterpriseConfig) {
215
+ return { allowed: true };
216
+ }
217
+
218
+ const result: ServerValidationResult = validateServer(server, enterpriseConfig);
219
+ if (!result.allowed) {
220
+ // Audit log the blocked server connection
221
+ const logger = getAuditLogger();
222
+ if (logger) {
223
+ await logger.log({
224
+ eventType: "server_blocked",
225
+ serverName: server.name,
226
+ metadata: {
227
+ reason: result.reason ?? "Server not in allowlist",
228
+ url: server.url,
229
+ },
230
+ });
231
+ }
232
+ }
233
+
234
+ return { allowed: result.allowed, reason: result.reason };
235
+ },
236
+
237
+ async onUserAction(action: string, metadata?: Record<string, unknown>) {
238
+ if (!isEnterpriseActive()) {
239
+ return;
240
+ }
241
+
242
+ const logger = getAuditLogger();
243
+ if (logger) {
244
+ await logger.log({
245
+ eventType: "config_change",
246
+ metadata: {
247
+ action,
248
+ ...metadata,
249
+ },
250
+ });
251
+ }
252
+ },
253
+ };
254
+ }
255
+
256
+ /**
257
+ * Shutdown enterprise integration and flush audit logs.
258
+ */
259
+ export async function shutdownEnterprise(): Promise<void> {
260
+ if (enterpriseInitialized) {
261
+ await shutdownAuditLogger();
262
+ enterpriseConfig = null;
263
+ enterpriseInitialized = false;
264
+ }
265
+ }
266
+
267
+ // =============================================================================
268
+ // Exports
269
+ // =============================================================================
270
+
271
+ export {
272
+ type AuditEvent,
273
+ type AuditEventType,
274
+ AuditLogger,
275
+ type FullEnterpriseConfig,
276
+ type ServerInfo,
277
+ isEnterpriseMode,
278
+ };
279
+
280
+ // Note: ToolCallInfo is intentionally not re-exported here to avoid name collision
281
+ // with the ToolCallInfo type from ./context/MessagesContext.js
282
+ // Import it directly from @vellum/mcp if needed for enterprise validation.
@@ -0,0 +1,138 @@
1
+ /**
2
+ * useBacktrack Hook Tests (T058)
3
+ *
4
+ * Verifies core backtrack/branching behavior:
5
+ * - initializes with a main branch
6
+ * - push/undo/redo update currentState
7
+ * - createBranch creates and switches to a new branch
8
+ * - switchBranch switches branches and restores that branch's latest snapshot
9
+ */
10
+
11
+ import { render } from "ink-testing-library";
12
+ import type React from "react";
13
+ import { describe, expect, it, vi } from "vitest";
14
+ import {
15
+ type UseBacktrackOptions,
16
+ type UseBacktrackReturn,
17
+ useBacktrack,
18
+ } from "../useBacktrack.js";
19
+
20
+ // =============================================================================
21
+ // Test Helper Component
22
+ // =============================================================================
23
+
24
+ type TestState = { value: number };
25
+
26
+ interface TestHarnessProps {
27
+ options: UseBacktrackOptions<TestState>;
28
+ onHookReturn: (hookReturn: UseBacktrackReturn<TestState>) => void;
29
+ }
30
+
31
+ function TestHarness({ options, onHookReturn }: TestHarnessProps): React.ReactElement {
32
+ const hookReturn = useBacktrack(options);
33
+ onHookReturn(hookReturn);
34
+ return null as unknown as React.ReactElement;
35
+ }
36
+
37
+ function renderHook(options: UseBacktrackOptions<TestState>) {
38
+ let hookReturn: UseBacktrackReturn<TestState> | null = null;
39
+
40
+ const setHookReturn = (r: UseBacktrackReturn<TestState>) => {
41
+ hookReturn = r;
42
+ };
43
+
44
+ const { rerender, unmount } = render(
45
+ <TestHarness options={options} onHookReturn={setHookReturn} />
46
+ );
47
+
48
+ return {
49
+ get current() {
50
+ if (!hookReturn) throw new Error("Hook not initialized");
51
+ return hookReturn;
52
+ },
53
+ rerender: (newOptions?: UseBacktrackOptions<TestState>) => {
54
+ rerender(<TestHarness options={newOptions ?? options} onHookReturn={setHookReturn} />);
55
+ },
56
+ unmount,
57
+ };
58
+ }
59
+
60
+ // =============================================================================
61
+ // Tests
62
+ // =============================================================================
63
+
64
+ describe("useBacktrack", () => {
65
+ it("initializes with main branch and initial state", () => {
66
+ const result = renderHook({ initialState: { value: 0 } });
67
+
68
+ expect(result.current.backtrackState.currentBranch).toBe("Main");
69
+ expect(result.current.backtrackState.historyLength).toBe(1);
70
+ expect(result.current.branches).toHaveLength(1);
71
+ expect(result.current.currentState).toEqual({ value: 0 });
72
+ });
73
+
74
+ it("push/undo/redo update currentState", () => {
75
+ const onStateChange = vi.fn();
76
+ const result = renderHook({ initialState: { value: 0 }, onStateChange });
77
+
78
+ result.current.push({ value: 1 }, "v1");
79
+ result.rerender();
80
+ expect(result.current.currentState).toEqual({ value: 1 });
81
+
82
+ result.current.undo();
83
+ result.rerender();
84
+ expect(result.current.currentState).toEqual({ value: 0 });
85
+
86
+ result.current.redo();
87
+ result.rerender();
88
+ expect(result.current.currentState).toEqual({ value: 1 });
89
+
90
+ // Callback is best-effort and may be called multiple times; assert at least once.
91
+ expect(onStateChange).toHaveBeenCalled();
92
+ });
93
+
94
+ it("createBranch creates and switches to the new branch", () => {
95
+ const result = renderHook({ initialState: { value: 0 } });
96
+
97
+ result.current.push({ value: 1 }, "v1");
98
+ result.rerender();
99
+
100
+ const branchId = result.current.createBranch("Alt");
101
+ result.rerender();
102
+
103
+ expect(typeof branchId).toBe("string");
104
+ expect(result.current.backtrackState.currentBranch).toBe("Alt");
105
+ expect(result.current.branches.length).toBe(2);
106
+ });
107
+
108
+ it("switchBranch switches branch and restores that branch's latest snapshot", () => {
109
+ const result = renderHook({ initialState: { value: 0 } });
110
+
111
+ // Main branch: 0 -> 1
112
+ result.current.push({ value: 1 }, "main-v1");
113
+ result.rerender();
114
+
115
+ // Create Alt branch from current point and diverge
116
+ const altId = result.current.createBranch("Alt");
117
+ result.rerender();
118
+ expect(result.current.backtrackState.currentBranch).toBe("Alt");
119
+
120
+ result.current.push({ value: 2 }, "alt-v2");
121
+ result.rerender();
122
+ expect(result.current.currentState).toEqual({ value: 2 });
123
+
124
+ // Switch back to main
125
+ result.current.switchBranch("main");
126
+ result.rerender();
127
+
128
+ expect(result.current.backtrackState.currentBranch).toBe("Main");
129
+ expect(result.current.currentState).toEqual({ value: 1 });
130
+
131
+ // Switch to Alt again
132
+ result.current.switchBranch(altId);
133
+ result.rerender();
134
+
135
+ expect(result.current.backtrackState.currentBranch).toBe("Alt");
136
+ expect(result.current.currentState).toEqual({ value: 2 });
137
+ });
138
+ });
@@ -0,0 +1,222 @@
1
+ /**
2
+ * useBracketedPaste Hook Tests
3
+ *
4
+ * Tests for the useBracketedPaste hook which manages bracketed paste mode lifecycle.
5
+ *
6
+ * @module @vellum/cli
7
+ */
8
+
9
+ import { render } from "ink-testing-library";
10
+ import type React from "react";
11
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
12
+ import { type UseBracketedPasteOptions, useBracketedPaste } from "../useBracketedPaste.js";
13
+
14
+ // =============================================================================
15
+ // Mocks
16
+ // =============================================================================
17
+
18
+ const originalWrite = process.stdout.write;
19
+ let writtenData: string[] = [];
20
+
21
+ // Track process event listeners
22
+ const processListeners: Map<string, Set<(...args: unknown[]) => void>> = new Map();
23
+ const originalOn = process.on.bind(process);
24
+ const originalOff = process.off.bind(process);
25
+
26
+ beforeEach(() => {
27
+ writtenData = [];
28
+ processListeners.clear();
29
+
30
+ // Mock stdout.write
31
+ process.stdout.write = vi.fn((data: string | Uint8Array) => {
32
+ writtenData.push(typeof data === "string" ? data : data.toString());
33
+ return true;
34
+ }) as typeof process.stdout.write;
35
+
36
+ // Mock process event listeners
37
+ process.on = vi.fn((event: string, listener: (...args: unknown[]) => void) => {
38
+ if (!processListeners.has(event)) {
39
+ processListeners.set(event, new Set());
40
+ }
41
+ processListeners.get(event)?.add(listener);
42
+ return process;
43
+ }) as typeof process.on;
44
+
45
+ process.off = vi.fn((event: string, listener: (...args: unknown[]) => void) => {
46
+ processListeners.get(event)?.delete(listener);
47
+ return process;
48
+ }) as typeof process.off;
49
+ });
50
+
51
+ afterEach(() => {
52
+ process.stdout.write = originalWrite;
53
+ process.on = originalOn;
54
+ process.off = originalOff;
55
+ });
56
+
57
+ // =============================================================================
58
+ // Test Helper Component
59
+ // =============================================================================
60
+
61
+ interface TestHarnessProps {
62
+ options?: UseBracketedPasteOptions;
63
+ }
64
+
65
+ function TestHarness({ options }: TestHarnessProps): React.ReactElement {
66
+ useBracketedPaste(options);
67
+ return null as unknown as React.ReactElement;
68
+ }
69
+
70
+ /**
71
+ * Render the hook and return controls.
72
+ */
73
+ function renderHook(options?: UseBracketedPasteOptions) {
74
+ const { rerender, unmount } = render(<TestHarness options={options} />);
75
+
76
+ return {
77
+ rerender: (newOptions?: UseBracketedPasteOptions) => {
78
+ rerender(<TestHarness options={newOptions} />);
79
+ },
80
+ unmount,
81
+ };
82
+ }
83
+
84
+ // =============================================================================
85
+ // Tests
86
+ // =============================================================================
87
+
88
+ describe("useBracketedPaste", () => {
89
+ describe("enable on mount", () => {
90
+ it("enables bracketed paste mode on mount by default", () => {
91
+ renderHook();
92
+
93
+ expect(writtenData).toContain("\x1b[?2004h");
94
+ });
95
+
96
+ it("enables bracketed paste mode when enabled=true", () => {
97
+ renderHook({ enabled: true });
98
+
99
+ expect(writtenData).toContain("\x1b[?2004h");
100
+ });
101
+
102
+ it("does not enable bracketed paste mode when enabled=false", () => {
103
+ renderHook({ enabled: false });
104
+
105
+ expect(writtenData).not.toContain("\x1b[?2004h");
106
+ });
107
+ });
108
+
109
+ describe("disable on unmount", () => {
110
+ it("disables bracketed paste mode on unmount", () => {
111
+ const { unmount } = renderHook();
112
+ writtenData = []; // Clear mount writes
113
+
114
+ unmount();
115
+
116
+ expect(writtenData).toContain("\x1b[?2004l");
117
+ });
118
+
119
+ it("does not disable if never enabled", () => {
120
+ const { unmount } = renderHook({ enabled: false });
121
+ writtenData = [];
122
+
123
+ unmount();
124
+
125
+ expect(writtenData).not.toContain("\x1b[?2004l");
126
+ });
127
+ });
128
+
129
+ describe("process event handlers", () => {
130
+ it("registers SIGINT handler on mount", () => {
131
+ renderHook();
132
+
133
+ expect(processListeners.has("SIGINT")).toBe(true);
134
+ expect(processListeners.get("SIGINT")?.size).toBeGreaterThan(0);
135
+ });
136
+
137
+ it("registers SIGTERM handler on mount", () => {
138
+ renderHook();
139
+
140
+ expect(processListeners.has("SIGTERM")).toBe(true);
141
+ expect(processListeners.get("SIGTERM")?.size).toBeGreaterThan(0);
142
+ });
143
+
144
+ it("registers beforeExit handler on mount", () => {
145
+ renderHook();
146
+
147
+ expect(processListeners.has("beforeExit")).toBe(true);
148
+ });
149
+
150
+ it("registers exit handler on mount", () => {
151
+ renderHook();
152
+
153
+ expect(processListeners.has("exit")).toBe(true);
154
+ });
155
+
156
+ it("removes event handlers on unmount", () => {
157
+ const { unmount } = renderHook();
158
+
159
+ // Get initial listener counts
160
+ const sigintBefore = processListeners.get("SIGINT")?.size ?? 0;
161
+ const sigtermBefore = processListeners.get("SIGTERM")?.size ?? 0;
162
+
163
+ unmount();
164
+
165
+ // Listeners should be removed
166
+ const sigintAfter = processListeners.get("SIGINT")?.size ?? 0;
167
+ const sigtermAfter = processListeners.get("SIGTERM")?.size ?? 0;
168
+
169
+ expect(sigintAfter).toBeLessThan(sigintBefore);
170
+ expect(sigtermAfter).toBeLessThan(sigtermBefore);
171
+ });
172
+
173
+ it("does not register handlers when disabled", () => {
174
+ renderHook({ enabled: false });
175
+
176
+ // No handlers should be registered (or fewer than when enabled)
177
+ const sigintCount = processListeners.get("SIGINT")?.size ?? 0;
178
+ expect(sigintCount).toBe(0);
179
+ });
180
+ });
181
+
182
+ describe("enabled toggle", () => {
183
+ it("enables bracketed paste when enabled changes from false to true", () => {
184
+ const { rerender } = renderHook({ enabled: false });
185
+ expect(writtenData).not.toContain("\x1b[?2004h");
186
+
187
+ writtenData = [];
188
+ rerender({ enabled: true });
189
+
190
+ expect(writtenData).toContain("\x1b[?2004h");
191
+ });
192
+
193
+ it("disables bracketed paste when enabled changes from true to false", () => {
194
+ const { rerender } = renderHook({ enabled: true });
195
+ expect(writtenData).toContain("\x1b[?2004h");
196
+
197
+ writtenData = [];
198
+ rerender({ enabled: false });
199
+
200
+ expect(writtenData).toContain("\x1b[?2004l");
201
+ });
202
+ });
203
+
204
+ describe("signal handlers cleanup behavior", () => {
205
+ it("signal handler calls disable sequence", () => {
206
+ renderHook();
207
+
208
+ // Get the SIGINT handler
209
+ const sigintHandlers = processListeners.get("SIGINT");
210
+ expect(sigintHandlers).toBeDefined();
211
+ expect(sigintHandlers?.size).toBeGreaterThan(0);
212
+
213
+ // Simulate SIGINT
214
+ writtenData = [];
215
+ const handler = sigintHandlers ? Array.from(sigintHandlers)[0] : undefined;
216
+ handler?.();
217
+
218
+ // Should have written disable sequence
219
+ expect(writtenData).toContain("\x1b[?2004l");
220
+ });
221
+ });
222
+ });