@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,349 @@
1
+ /**
2
+ * Application Context and State Management
3
+ *
4
+ * Provides global application state for the Vellum TUI including
5
+ * mode, loading state, error handling, and focus management.
6
+ *
7
+ * @module tui/context/AppContext
8
+ */
9
+
10
+ import React, {
11
+ createContext,
12
+ type Dispatch,
13
+ type ReactNode,
14
+ useCallback,
15
+ useContext,
16
+ useMemo,
17
+ useReducer,
18
+ } from "react";
19
+
20
+ // =============================================================================
21
+ // Types
22
+ // =============================================================================
23
+
24
+ /**
25
+ * Application mode representing the current operational state
26
+ */
27
+ export type AppMode = "idle" | "loading" | "streaming" | "waiting" | "error";
28
+
29
+ /**
30
+ * Focusable areas in the TUI
31
+ */
32
+ export type FocusedArea = "input" | "messages" | "tools" | "status";
33
+
34
+ /**
35
+ * Application state interface
36
+ */
37
+ export interface AppState {
38
+ /** Current application mode */
39
+ readonly mode: AppMode;
40
+ /** Whether the application is in a loading state */
41
+ readonly loading: boolean;
42
+ /** Current error, if any */
43
+ readonly error: Error | null;
44
+ /** Whether vim mode is enabled for input */
45
+ readonly vimMode: boolean;
46
+ /** Currently focused area of the UI */
47
+ readonly focusedArea: FocusedArea;
48
+ }
49
+
50
+ /**
51
+ * Initial application state
52
+ */
53
+ const initialState: AppState = {
54
+ mode: "idle",
55
+ loading: false,
56
+ error: null,
57
+ vimMode: false,
58
+ focusedArea: "input",
59
+ };
60
+
61
+ // =============================================================================
62
+ // Actions (Discriminated Union)
63
+ // =============================================================================
64
+
65
+ /**
66
+ * Set the application mode
67
+ */
68
+ export interface SetModeAction {
69
+ readonly type: "SET_MODE";
70
+ readonly mode: AppMode;
71
+ }
72
+
73
+ /**
74
+ * Set the loading state
75
+ */
76
+ export interface SetLoadingAction {
77
+ readonly type: "SET_LOADING";
78
+ readonly loading: boolean;
79
+ }
80
+
81
+ /**
82
+ * Set an error
83
+ */
84
+ export interface SetErrorAction {
85
+ readonly type: "SET_ERROR";
86
+ readonly error: Error | null;
87
+ }
88
+
89
+ /**
90
+ * Toggle vim mode
91
+ */
92
+ export interface ToggleVimModeAction {
93
+ readonly type: "TOGGLE_VIM_MODE";
94
+ }
95
+
96
+ /**
97
+ * Set vim mode explicitly
98
+ */
99
+ export interface SetVimModeAction {
100
+ readonly type: "SET_VIM_MODE";
101
+ readonly vimMode: boolean;
102
+ }
103
+
104
+ /**
105
+ * Set the focused area
106
+ */
107
+ export interface SetFocusedAreaAction {
108
+ readonly type: "SET_FOCUSED_AREA";
109
+ readonly focusedArea: FocusedArea;
110
+ }
111
+
112
+ /**
113
+ * Reset the application state to initial values
114
+ */
115
+ export interface ResetAction {
116
+ readonly type: "RESET";
117
+ }
118
+
119
+ /**
120
+ * Discriminated union of all application actions
121
+ */
122
+ export type AppAction =
123
+ | SetModeAction
124
+ | SetLoadingAction
125
+ | SetErrorAction
126
+ | ToggleVimModeAction
127
+ | SetVimModeAction
128
+ | SetFocusedAreaAction
129
+ | ResetAction;
130
+
131
+ // =============================================================================
132
+ // Reducer
133
+ // =============================================================================
134
+
135
+ /**
136
+ * Application state reducer
137
+ *
138
+ * @param state - Current application state
139
+ * @param action - Action to apply
140
+ * @returns New application state
141
+ */
142
+ function appReducer(state: AppState, action: AppAction): AppState {
143
+ switch (action.type) {
144
+ case "SET_MODE":
145
+ return {
146
+ ...state,
147
+ mode: action.mode,
148
+ // Automatically update loading based on mode
149
+ loading: action.mode === "loading" || action.mode === "streaming",
150
+ // Clear error when transitioning away from error state
151
+ error: action.mode === "error" ? state.error : null,
152
+ };
153
+
154
+ case "SET_LOADING":
155
+ return {
156
+ ...state,
157
+ loading: action.loading,
158
+ };
159
+
160
+ case "SET_ERROR":
161
+ return {
162
+ ...state,
163
+ error: action.error,
164
+ mode: action.error !== null ? "error" : state.mode,
165
+ };
166
+
167
+ case "TOGGLE_VIM_MODE":
168
+ return {
169
+ ...state,
170
+ vimMode: !state.vimMode,
171
+ };
172
+
173
+ case "SET_VIM_MODE":
174
+ return {
175
+ ...state,
176
+ vimMode: action.vimMode,
177
+ };
178
+
179
+ case "SET_FOCUSED_AREA":
180
+ return {
181
+ ...state,
182
+ focusedArea: action.focusedArea,
183
+ };
184
+
185
+ case "RESET":
186
+ return initialState;
187
+
188
+ default:
189
+ // Exhaustive check - TypeScript will error if a case is missing
190
+ return state;
191
+ }
192
+ }
193
+
194
+ // =============================================================================
195
+ // Context
196
+ // =============================================================================
197
+
198
+ /**
199
+ * Context value interface
200
+ */
201
+ export interface AppContextValue {
202
+ /** Current application state */
203
+ readonly state: AppState;
204
+ /** Dispatch function for state updates */
205
+ readonly dispatch: Dispatch<AppAction>;
206
+ /** Reset state to initial values */
207
+ readonly reset: () => void;
208
+ }
209
+
210
+ /**
211
+ * React context for application state
212
+ *
213
+ * Initialized as undefined to detect usage outside provider
214
+ */
215
+ const AppContext = createContext<AppContextValue | undefined>(undefined);
216
+
217
+ // =============================================================================
218
+ // Hook
219
+ // =============================================================================
220
+
221
+ /**
222
+ * Hook to access the application state and dispatch
223
+ *
224
+ * Must be used within an AppProvider component.
225
+ *
226
+ * @returns The current app context value with state, dispatch, and reset
227
+ * @throws Error if used outside AppProvider
228
+ *
229
+ * @example
230
+ * ```tsx
231
+ * function MyComponent() {
232
+ * const { state, dispatch, reset } = useApp();
233
+ *
234
+ * // Read state
235
+ * if (state.loading) {
236
+ * return <Text>Loading...</Text>;
237
+ * }
238
+ *
239
+ * // Dispatch actions
240
+ * const handleStart = () => {
241
+ * dispatch({ type: 'SET_MODE', mode: 'loading' });
242
+ * };
243
+ *
244
+ * // Reset to initial state
245
+ * const handleReset = () => reset();
246
+ *
247
+ * return <Box>...</Box>;
248
+ * }
249
+ * ```
250
+ */
251
+ export function useApp(): AppContextValue {
252
+ const context = useContext(AppContext);
253
+
254
+ if (context === undefined) {
255
+ throw new Error(
256
+ "useApp must be used within an AppProvider. " +
257
+ "Ensure your component is wrapped in <AppProvider>."
258
+ );
259
+ }
260
+
261
+ return context;
262
+ }
263
+
264
+ // =============================================================================
265
+ // Provider Props
266
+ // =============================================================================
267
+
268
+ /**
269
+ * Props for the AppProvider component
270
+ */
271
+ export interface AppProviderProps {
272
+ /**
273
+ * Initial state overrides
274
+ *
275
+ * Partial state that will be merged with the default initial state
276
+ */
277
+ readonly initialState?: Partial<AppState>;
278
+
279
+ /**
280
+ * Children to render within the app context
281
+ */
282
+ readonly children: ReactNode;
283
+ }
284
+
285
+ // =============================================================================
286
+ // Provider Component
287
+ // =============================================================================
288
+
289
+ /**
290
+ * Application state provider component
291
+ *
292
+ * Provides application state context to all child components, enabling
293
+ * access to the current state and dispatch via the useApp hook.
294
+ *
295
+ * @example
296
+ * ```tsx
297
+ * // Using default initial state
298
+ * <AppProvider>
299
+ * <App />
300
+ * </AppProvider>
301
+ *
302
+ * // Using custom initial state
303
+ * <AppProvider initialState={{ vimMode: true }}>
304
+ * <App />
305
+ * </AppProvider>
306
+ * ```
307
+ */
308
+ export function AppProvider({
309
+ initialState: initialStateOverrides,
310
+ children,
311
+ }: AppProviderProps): React.JSX.Element {
312
+ // State management with useReducer
313
+ // Initial state is computed once via lazy initializer
314
+ const [state, dispatch] = useReducer(
315
+ appReducer,
316
+ initialStateOverrides,
317
+ (overrides): AppState => ({
318
+ ...initialState,
319
+ ...overrides,
320
+ })
321
+ );
322
+
323
+ /**
324
+ * Reset state to initial values
325
+ */
326
+ const reset = useCallback((): void => {
327
+ dispatch({ type: "RESET" });
328
+ }, []);
329
+
330
+ /**
331
+ * Memoized context value
332
+ */
333
+ const contextValue = useMemo<AppContextValue>(
334
+ () => ({
335
+ state,
336
+ dispatch,
337
+ reset,
338
+ }),
339
+ [state, reset]
340
+ );
341
+
342
+ return <AppContext value={contextValue}>{children}</AppContext>;
343
+ }
344
+
345
+ // =============================================================================
346
+ // Exports
347
+ // =============================================================================
348
+
349
+ export { AppContext, initialState };
@@ -0,0 +1,372 @@
1
+ /**
2
+ * Bracketed Paste Context
3
+ *
4
+ * React context for managing bracketed paste mode state and providing
5
+ * paste event callbacks to child components.
6
+ *
7
+ * @module tui/context/BracketedPasteContext
8
+ */
9
+
10
+ import { useStdin } from "ink";
11
+ import type { ReactNode } from "react";
12
+ import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
13
+ import {
14
+ disableBracketedPaste,
15
+ enableBracketedPaste,
16
+ PASTE_END,
17
+ PASTE_START,
18
+ } from "../utils/bracketedPaste.js";
19
+
20
+ // =============================================================================
21
+ // Types
22
+ // =============================================================================
23
+
24
+ /**
25
+ * Handler function for paste events
26
+ */
27
+ export type PasteHandler = (content: string) => void;
28
+
29
+ /**
30
+ * Context value for bracketed paste
31
+ */
32
+ interface BracketedPasteContextValue {
33
+ /**
34
+ * Register a paste event handler
35
+ * @returns Unsubscribe function
36
+ */
37
+ subscribe: (handler: PasteHandler) => () => void;
38
+
39
+ /**
40
+ * Whether a paste operation is currently in progress
41
+ */
42
+ isPasting: boolean;
43
+ }
44
+
45
+ // =============================================================================
46
+ // Constants
47
+ // =============================================================================
48
+
49
+ /** Timeout for incomplete paste sequences (30 seconds) */
50
+ const PASTE_TIMEOUT_MS = 30_000;
51
+
52
+ // =============================================================================
53
+ // Context
54
+ // =============================================================================
55
+
56
+ const BracketedPasteContext = createContext<BracketedPasteContextValue | null>(null);
57
+
58
+ // =============================================================================
59
+ // Provider
60
+ // =============================================================================
61
+
62
+ interface BracketedPasteProviderProps {
63
+ children: ReactNode;
64
+ /**
65
+ * Whether bracketed paste should be enabled
66
+ * @default true
67
+ */
68
+ enabled?: boolean;
69
+ }
70
+
71
+ /**
72
+ * Provider component that enables bracketed paste mode and dispatches
73
+ * paste events to subscribers.
74
+ *
75
+ * This provider intercepts stdin data events to detect paste sequences
76
+ * before Ink's useInput processes them. When a paste is detected, the
77
+ * content is buffered and dispatched as a single event to all subscribers.
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * function App() {
82
+ * return (
83
+ * <BracketedPasteProvider>
84
+ * <MyComponents />
85
+ * </BracketedPasteProvider>
86
+ * );
87
+ * }
88
+ * ```
89
+ */
90
+ export function BracketedPasteProvider({ children, enabled = true }: BracketedPasteProviderProps) {
91
+ const { stdin } = useStdin();
92
+ const [isPasting, setIsPasting] = useState(false);
93
+
94
+ // Subscribers for paste events
95
+ const subscribersRef = useRef<Set<PasteHandler>>(new Set());
96
+
97
+ // Buffer for accumulating paste content
98
+ const pasteBufferRef = useRef<string>("");
99
+
100
+ // Timeout handle for incomplete pastes
101
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
102
+
103
+ // Track paste state without triggering re-renders (for emit override)
104
+ const isPastingRef = useRef(false);
105
+
106
+ /**
107
+ * Subscribe to paste events
108
+ */
109
+ const subscribe = useCallback((handler: PasteHandler) => {
110
+ subscribersRef.current.add(handler);
111
+ return () => {
112
+ subscribersRef.current.delete(handler);
113
+ };
114
+ }, []);
115
+
116
+ /**
117
+ * Dispatch paste content to all subscribers
118
+ */
119
+ const dispatchPaste = useCallback((content: string) => {
120
+ if (content.length === 0) return;
121
+
122
+ for (const handler of subscribersRef.current) {
123
+ try {
124
+ handler(content);
125
+ } catch {
126
+ // Ignore handler errors
127
+ }
128
+ }
129
+ }, []);
130
+
131
+ /**
132
+ * Clear paste state
133
+ */
134
+ const clearPasteState = useCallback(() => {
135
+ isPastingRef.current = false;
136
+ setIsPasting(false);
137
+ pasteBufferRef.current = "";
138
+ if (timeoutRef.current) {
139
+ clearTimeout(timeoutRef.current);
140
+ timeoutRef.current = null;
141
+ }
142
+ }, []);
143
+
144
+ /**
145
+ * Handle paste start sequence found in data
146
+ */
147
+ const handlePasteStart = useCallback(
148
+ (afterStart: string) => {
149
+ isPastingRef.current = true;
150
+ setIsPasting(true);
151
+ pasteBufferRef.current = "";
152
+
153
+ // Set timeout for incomplete paste
154
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
155
+ timeoutRef.current = setTimeout(() => {
156
+ if (pasteBufferRef.current.length > 0) {
157
+ dispatchPaste(pasteBufferRef.current);
158
+ }
159
+ clearPasteState();
160
+ }, PASTE_TIMEOUT_MS);
161
+
162
+ // Check if paste end is in this chunk
163
+ const endIdx = afterStart.indexOf(PASTE_END);
164
+ if (endIdx !== -1) {
165
+ // Complete paste in single data event
166
+ dispatchPaste(afterStart.slice(0, endIdx));
167
+ clearPasteState();
168
+ } else {
169
+ // Partial paste - buffer content
170
+ pasteBufferRef.current = afterStart;
171
+ }
172
+ },
173
+ [dispatchPaste, clearPasteState]
174
+ );
175
+
176
+ /**
177
+ * Handle data while paste is in progress
178
+ */
179
+ const handlePasteInProgress = useCallback(
180
+ (str: string) => {
181
+ const endIdx = str.indexOf(PASTE_END);
182
+ if (endIdx !== -1) {
183
+ // Found end - complete the paste
184
+ dispatchPaste(pasteBufferRef.current + str.slice(0, endIdx));
185
+ clearPasteState();
186
+ } else {
187
+ // Still pasting - accumulate
188
+ pasteBufferRef.current += str;
189
+ }
190
+ },
191
+ [dispatchPaste, clearPasteState]
192
+ );
193
+
194
+ /**
195
+ * Set up stdin listener for paste detection with emit override.
196
+ * This intercepts data events BEFORE they reach Ink, preventing
197
+ * paste sequences from triggering character-by-character rendering.
198
+ *
199
+ * In test environments (non-TTY), we fall back to prependListener which
200
+ * is less complete but doesn't interfere with test mocks.
201
+ */
202
+ useEffect(() => {
203
+ if (!enabled || !stdin) return;
204
+
205
+ // Check if this is a real TTY (not a test mock)
206
+ // In tests, stdin is often a mock that doesn't support emit override well
207
+ const isRealTty = stdin.isTTY === true && typeof stdin.setRawMode === "function";
208
+
209
+ // Enable bracketed paste mode (only for real TTYs)
210
+ if (isRealTty) {
211
+ enableBracketedPaste();
212
+ }
213
+
214
+ if (isRealTty) {
215
+ // Real TTY: Use emit override for complete blocking
216
+ const originalEmit = stdin.emit.bind(stdin);
217
+
218
+ /**
219
+ * Override emit to intercept and block paste data.
220
+ * This ensures paste sequences never reach Ink's input handlers.
221
+ */
222
+ // biome-ignore lint/suspicious/noExplicitAny: stdin.emit has complex overloaded signature
223
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Paste sequence state machine with multiple transitions
224
+ (stdin as any).emit = (event: string | symbol, ...args: unknown[]): boolean => {
225
+ // Only intercept 'data' events
226
+ if (event !== "data") {
227
+ return originalEmit(event, ...args);
228
+ }
229
+
230
+ const data = args[0];
231
+ const str = typeof data === "string" ? data : (data as Buffer).toString("utf8");
232
+
233
+ // Check for paste start
234
+ const startIdx = str.indexOf(PASTE_START);
235
+ if (startIdx !== -1 && !isPastingRef.current) {
236
+ // Found paste start - handle it and block from Ink
237
+ // Extract any data before the paste sequence (let it through)
238
+ const beforePaste = str.slice(0, startIdx);
239
+ if (beforePaste.length > 0) {
240
+ originalEmit("data", beforePaste);
241
+ }
242
+ // Process paste start (this sets isPastingRef.current = true)
243
+ handlePasteStart(str.slice(startIdx + PASTE_START.length));
244
+ // Block the paste data from reaching Ink
245
+ return true;
246
+ }
247
+
248
+ // If we're in a paste, accumulate and block
249
+ if (isPastingRef.current) {
250
+ handlePasteInProgress(str);
251
+ // Block paste data from reaching Ink
252
+ return true;
253
+ }
254
+
255
+ // Normal data - pass through to Ink
256
+ return originalEmit(event, ...args);
257
+ };
258
+
259
+ // Cleanup for real TTY
260
+ return () => {
261
+ // biome-ignore lint/suspicious/noExplicitAny: restoring original emit
262
+ (stdin as any).emit = originalEmit;
263
+ clearPasteState();
264
+ disableBracketedPaste();
265
+ };
266
+ }
267
+
268
+ // Test environment: Use prependListener (less complete but test-compatible)
269
+ const handleData = (data: Buffer | string) => {
270
+ const str = typeof data === "string" ? data : data.toString("utf8");
271
+
272
+ // Check for paste start
273
+ const startIdx = str.indexOf(PASTE_START);
274
+ if (startIdx !== -1 && !isPastingRef.current) {
275
+ handlePasteStart(str.slice(startIdx + PASTE_START.length));
276
+ return;
277
+ }
278
+
279
+ // If we're in a paste, look for end or accumulate
280
+ if (isPastingRef.current) {
281
+ handlePasteInProgress(str);
282
+ }
283
+ };
284
+
285
+ stdin.prependListener("data", handleData);
286
+
287
+ return () => {
288
+ stdin.removeListener("data", handleData);
289
+ clearPasteState();
290
+ };
291
+ }, [enabled, stdin, handlePasteStart, handlePasteInProgress, clearPasteState]);
292
+
293
+ // Handle process exit signals
294
+ useEffect(() => {
295
+ if (!enabled) return;
296
+
297
+ const cleanup = () => {
298
+ disableBracketedPaste();
299
+ };
300
+
301
+ process.on("SIGINT", cleanup);
302
+ process.on("SIGTERM", cleanup);
303
+ process.on("exit", cleanup);
304
+
305
+ return () => {
306
+ process.off("SIGINT", cleanup);
307
+ process.off("SIGTERM", cleanup);
308
+ process.off("exit", cleanup);
309
+ };
310
+ }, [enabled]);
311
+
312
+ const value: BracketedPasteContextValue = {
313
+ subscribe,
314
+ isPasting,
315
+ };
316
+
317
+ return <BracketedPasteContext.Provider value={value}>{children}</BracketedPasteContext.Provider>;
318
+ }
319
+
320
+ // =============================================================================
321
+ // Hook
322
+ // =============================================================================
323
+
324
+ /**
325
+ * Hook to access the bracketed paste context.
326
+ *
327
+ * @throws Error if used outside of BracketedPasteProvider
328
+ */
329
+ export function useBracketedPasteContext(): BracketedPasteContextValue {
330
+ const context = useContext(BracketedPasteContext);
331
+ if (!context) {
332
+ throw new Error("useBracketedPasteContext must be used within BracketedPasteProvider");
333
+ }
334
+ return context;
335
+ }
336
+
337
+ /**
338
+ * Hook to subscribe to paste events.
339
+ *
340
+ * The provided callback will be called whenever a paste operation completes.
341
+ * The callback receives the full pasted content as a single string.
342
+ *
343
+ * @example
344
+ * ```tsx
345
+ * function MyInput({ value, onChange }) {
346
+ * // Handle paste events
347
+ * usePasteHandler((pastedText) => {
348
+ * onChange(value + pastedText);
349
+ * });
350
+ *
351
+ * return <Text>{value}</Text>;
352
+ * }
353
+ * ```
354
+ */
355
+ export function usePasteHandler(handler: PasteHandler): void {
356
+ const context = useContext(BracketedPasteContext);
357
+
358
+ useEffect(() => {
359
+ if (!context) return;
360
+ return context.subscribe(handler);
361
+ }, [context, handler]);
362
+ }
363
+
364
+ /**
365
+ * Hook to check if a paste operation is in progress.
366
+ *
367
+ * @returns true if paste is in progress, false otherwise, or undefined if outside provider
368
+ */
369
+ export function useIsPasting(): boolean {
370
+ const context = useContext(BracketedPasteContext);
371
+ return context?.isPasting ?? false;
372
+ }