@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,414 @@
1
+ /**
2
+ * useDesktopNotification Hook (T059)
3
+ *
4
+ * React hook for sending desktop notifications from the TUI.
5
+ * Detects node-notifier availability and provides graceful fallback.
6
+ *
7
+ * @module @vellum/cli
8
+ */
9
+
10
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
11
+
12
+ import { ICONS } from "../../utils/icons.js";
13
+
14
+ // =============================================================================
15
+ // Types
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Notification priority levels.
20
+ */
21
+ export type NotificationPriority = "low" | "normal" | "high" | "urgent";
22
+
23
+ /**
24
+ * Notification type categorization.
25
+ */
26
+ export type NotificationType =
27
+ | "task-complete"
28
+ | "permission-request"
29
+ | "error"
30
+ | "warning"
31
+ | "info";
32
+
33
+ /**
34
+ * Options for a notification.
35
+ */
36
+ export interface NotificationOptions {
37
+ /** Title of the notification */
38
+ readonly title: string;
39
+ /** Body message */
40
+ readonly message: string;
41
+ /** Optional subtitle (macOS) */
42
+ readonly subtitle?: string;
43
+ /** Notification type for categorization */
44
+ readonly type?: NotificationType;
45
+ /** Priority level */
46
+ readonly priority?: NotificationPriority;
47
+ /** Whether the notification should make a sound */
48
+ readonly sound?: boolean;
49
+ /** Time in ms after which to auto-dismiss (0 = no auto-dismiss) */
50
+ readonly timeout?: number;
51
+ /** Optional icon path */
52
+ readonly icon?: string;
53
+ /** Optional click action callback */
54
+ readonly onClick?: () => void;
55
+ /** Optional close action callback */
56
+ readonly onClose?: () => void;
57
+ }
58
+
59
+ /**
60
+ * Configuration for the notification hook.
61
+ */
62
+ export interface UseDesktopNotificationOptions {
63
+ /** Whether notifications are enabled (default: true) */
64
+ readonly enabled?: boolean;
65
+ /** Default sound setting (default: true) */
66
+ readonly defaultSound?: boolean;
67
+ /** Minimum time between notifications in ms (default: 1000) */
68
+ readonly throttleMs?: number;
69
+ /** Application name for notifications */
70
+ readonly appName?: string;
71
+ /** Whether to show notifications only when terminal is not focused */
72
+ readonly onlyWhenUnfocused?: boolean;
73
+ }
74
+
75
+ /**
76
+ * Return value of useDesktopNotification hook.
77
+ */
78
+ export interface UseDesktopNotificationReturn {
79
+ /** Whether notifications are available (node-notifier detected) */
80
+ readonly isAvailable: boolean;
81
+ /** Whether notifications are currently enabled */
82
+ readonly isEnabled: boolean;
83
+ /** Send a notification */
84
+ readonly notify: (options: NotificationOptions) => void;
85
+ /** Notify when a long task completes */
86
+ readonly notifyTaskComplete: (taskName: string, duration?: number) => void;
87
+ /** Notify for a permission request */
88
+ readonly notifyPermissionRequest: (permission: string) => void;
89
+ /** Notify for an error */
90
+ readonly notifyError: (message: string) => void;
91
+ /** Toggle notifications on/off */
92
+ readonly toggle: () => void;
93
+ /** Enable notifications */
94
+ readonly enable: () => void;
95
+ /** Disable notifications */
96
+ readonly disable: () => void;
97
+ }
98
+
99
+ // =============================================================================
100
+ // Constants
101
+ // =============================================================================
102
+
103
+ /** Default throttle time between notifications */
104
+ const DEFAULT_THROTTLE_MS = 1000;
105
+
106
+ /** Default app name */
107
+ const DEFAULT_APP_NAME = "Vellum";
108
+
109
+ /** Icons for different notification types */
110
+ const TYPE_ICONS: Record<NotificationType, string> = {
111
+ "task-complete": ICONS.success,
112
+ "permission-request": "[Auth]",
113
+ error: ICONS.error,
114
+ warning: ICONS.warning,
115
+ info: ICONS.info,
116
+ };
117
+
118
+ // =============================================================================
119
+ // Node-Notifier Detection & Wrapper
120
+ // =============================================================================
121
+
122
+ /**
123
+ * Lazy-loaded notifier module.
124
+ */
125
+ let notifierModule: NotifierModule | null = null;
126
+ let notifierChecked = false;
127
+
128
+ interface NotifierModule {
129
+ notify(
130
+ options: {
131
+ title?: string;
132
+ message?: string;
133
+ subtitle?: string;
134
+ sound?: boolean;
135
+ icon?: string;
136
+ timeout?: number;
137
+ appID?: string;
138
+ },
139
+ callback?: (err: Error | null, response: string) => void
140
+ ): void;
141
+ }
142
+
143
+ /**
144
+ * Attempt to load node-notifier dynamically.
145
+ */
146
+ async function loadNotifier(): Promise<NotifierModule | null> {
147
+ if (notifierChecked) {
148
+ return notifierModule;
149
+ }
150
+
151
+ notifierChecked = true;
152
+
153
+ try {
154
+ // Dynamic import to avoid hard dependency
155
+ // Using a variable to prevent TypeScript from resolving the module at compile time
156
+ const moduleName = "node-notifier";
157
+ const module = await import(moduleName);
158
+ notifierModule = module.default ?? module;
159
+ return notifierModule;
160
+ } catch {
161
+ // node-notifier not available - that's fine
162
+ return null;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Check if we're running in a focused terminal.
168
+ * This is a heuristic check - not 100% reliable.
169
+ */
170
+ function isTerminalFocused(): boolean {
171
+ // Check if running interactively
172
+ if (!process.stdin.isTTY) {
173
+ return false;
174
+ }
175
+
176
+ // In most cases, if we're running in a TTY and stdout is a TTY,
177
+ // the terminal is likely focused. This is imperfect but reasonable.
178
+ return process.stdout.isTTY ?? false;
179
+ }
180
+
181
+ // =============================================================================
182
+ // Hook Implementation
183
+ // =============================================================================
184
+
185
+ /**
186
+ * Hook for sending desktop notifications.
187
+ *
188
+ * Automatically detects if node-notifier is available and provides
189
+ * a graceful fallback when it's not. Useful for notifying users
190
+ * when long-running tasks complete or when permission is needed.
191
+ *
192
+ * @example
193
+ * ```tsx
194
+ * function LongTaskRunner() {
195
+ * const { notify, notifyTaskComplete, isAvailable } = useDesktopNotification({
196
+ * appName: 'Vellum',
197
+ * });
198
+ *
199
+ * const runTask = async () => {
200
+ * const start = Date.now();
201
+ * await performLongTask();
202
+ * notifyTaskComplete('Build', Date.now() - start);
203
+ * };
204
+ *
205
+ * return (
206
+ * <Box>
207
+ * <Text>Notifications: {isAvailable ? 'Available' : 'Not available'}</Text>
208
+ * </Box>
209
+ * );
210
+ * }
211
+ * ```
212
+ */
213
+ export function useDesktopNotification(
214
+ options: UseDesktopNotificationOptions = {}
215
+ ): UseDesktopNotificationReturn {
216
+ const {
217
+ enabled: initialEnabled = true,
218
+ defaultSound = true,
219
+ throttleMs = DEFAULT_THROTTLE_MS,
220
+ appName = DEFAULT_APP_NAME,
221
+ onlyWhenUnfocused = true,
222
+ } = options;
223
+
224
+ const [isAvailable, setIsAvailable] = useState(false);
225
+ const [isEnabled, setIsEnabled] = useState(initialEnabled);
226
+ const lastNotificationTime = useRef(0);
227
+ const notifierRef = useRef<NotifierModule | null>(null);
228
+
229
+ // Load notifier on mount
230
+ useEffect(() => {
231
+ let mounted = true;
232
+
233
+ loadNotifier().then((notifier) => {
234
+ if (mounted && notifier) {
235
+ notifierRef.current = notifier;
236
+ setIsAvailable(true);
237
+ }
238
+ });
239
+
240
+ return () => {
241
+ mounted = false;
242
+ };
243
+ }, []);
244
+
245
+ // Core notification function
246
+ const notify = useCallback(
247
+ (notifOptions: NotificationOptions) => {
248
+ // Check if we should notify
249
+ if (!isEnabled || !isAvailable || !notifierRef.current) {
250
+ return;
251
+ }
252
+
253
+ // Check focus state
254
+ if (onlyWhenUnfocused && isTerminalFocused()) {
255
+ return;
256
+ }
257
+
258
+ // Throttle notifications
259
+ const now = Date.now();
260
+ if (now - lastNotificationTime.current < throttleMs) {
261
+ return;
262
+ }
263
+ lastNotificationTime.current = now;
264
+
265
+ const {
266
+ title,
267
+ message,
268
+ subtitle,
269
+ type,
270
+ sound = defaultSound,
271
+ timeout,
272
+ icon,
273
+ onClick,
274
+ onClose,
275
+ } = notifOptions;
276
+
277
+ // Build notification title with type icon
278
+ const typeIcon = type ? TYPE_ICONS[type] : "";
279
+ const fullTitle = typeIcon ? `${typeIcon} ${title}` : title;
280
+
281
+ // Send notification
282
+ notifierRef.current.notify(
283
+ {
284
+ title: fullTitle,
285
+ message,
286
+ subtitle,
287
+ sound,
288
+ icon,
289
+ timeout,
290
+ appID: appName,
291
+ },
292
+ (err, response) => {
293
+ if (err) {
294
+ // Silently handle errors - notifications are non-critical
295
+ return;
296
+ }
297
+
298
+ // Handle click/close callbacks based on response
299
+ if (response === "activate" || response === "clicked") {
300
+ onClick?.();
301
+ } else if (response === "dismissed" || response === "timeout") {
302
+ onClose?.();
303
+ }
304
+ }
305
+ );
306
+ },
307
+ [isEnabled, isAvailable, onlyWhenUnfocused, throttleMs, defaultSound, appName]
308
+ );
309
+
310
+ // Convenience: Notify task completion
311
+ const notifyTaskComplete = useCallback(
312
+ (taskName: string, duration?: number) => {
313
+ const durationText = duration ? ` (${formatDuration(duration)})` : "";
314
+ notify({
315
+ title: "Task Complete",
316
+ message: `${taskName} finished${durationText}`,
317
+ type: "task-complete",
318
+ priority: "normal",
319
+ });
320
+ },
321
+ [notify]
322
+ );
323
+
324
+ // Convenience: Notify permission request
325
+ const notifyPermissionRequest = useCallback(
326
+ (permission: string) => {
327
+ notify({
328
+ title: "Permission Required",
329
+ message: `${permission} - please review and approve`,
330
+ type: "permission-request",
331
+ priority: "high",
332
+ sound: true,
333
+ });
334
+ },
335
+ [notify]
336
+ );
337
+
338
+ // Convenience: Notify error
339
+ const notifyError = useCallback(
340
+ (message: string) => {
341
+ notify({
342
+ title: "Error",
343
+ message,
344
+ type: "error",
345
+ priority: "urgent",
346
+ sound: true,
347
+ });
348
+ },
349
+ [notify]
350
+ );
351
+
352
+ // Toggle/enable/disable
353
+ const toggle = useCallback(() => {
354
+ setIsEnabled((prev) => !prev);
355
+ }, []);
356
+
357
+ const enable = useCallback(() => {
358
+ setIsEnabled(true);
359
+ }, []);
360
+
361
+ const disable = useCallback(() => {
362
+ setIsEnabled(false);
363
+ }, []);
364
+
365
+ return useMemo(
366
+ () => ({
367
+ isAvailable,
368
+ isEnabled,
369
+ notify,
370
+ notifyTaskComplete,
371
+ notifyPermissionRequest,
372
+ notifyError,
373
+ toggle,
374
+ enable,
375
+ disable,
376
+ }),
377
+ [
378
+ isAvailable,
379
+ isEnabled,
380
+ notify,
381
+ notifyTaskComplete,
382
+ notifyPermissionRequest,
383
+ notifyError,
384
+ toggle,
385
+ enable,
386
+ disable,
387
+ ]
388
+ );
389
+ }
390
+
391
+ // =============================================================================
392
+ // Utility Functions
393
+ // =============================================================================
394
+
395
+ /**
396
+ * Format duration in milliseconds to human-readable string.
397
+ */
398
+ function formatDuration(ms: number): string {
399
+ if (ms < 1000) {
400
+ return `${ms}ms`;
401
+ }
402
+ const seconds = Math.floor(ms / 1000);
403
+ if (seconds < 60) {
404
+ return `${seconds}s`;
405
+ }
406
+ const minutes = Math.floor(seconds / 60);
407
+ const remainingSeconds = seconds % 60;
408
+ if (minutes < 60) {
409
+ return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
410
+ }
411
+ const hours = Math.floor(minutes / 60);
412
+ const remainingMinutes = minutes % 60;
413
+ return `${hours}h ${remainingMinutes}m`;
414
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * useDiffMode Hook
3
+ *
4
+ * React hook for subscribing to diff view mode changes.
5
+ * Connects DiffView components to the global diff mode state.
6
+ *
7
+ * @module tui/hooks/useDiffMode
8
+ */
9
+
10
+ import { useEffect, useState } from "react";
11
+ import { getDiffMode, subscribeDiffMode } from "../../commands/diff-mode.js";
12
+ import type { DiffViewMode } from "../i18n/index.js";
13
+
14
+ /**
15
+ * Hook return type for diff mode state.
16
+ */
17
+ export interface UseDiffModeReturn {
18
+ /** Current diff view mode */
19
+ readonly mode: DiffViewMode;
20
+ }
21
+
22
+ /**
23
+ * React hook for subscribing to diff view mode changes.
24
+ *
25
+ * @returns Current diff view mode that updates on change
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * function MyDiffComponent({ diff }: { diff: string }) {
30
+ * const { mode } = useDiffMode();
31
+ * return <DiffView diff={diff} mode={mode} />;
32
+ * }
33
+ * ```
34
+ */
35
+ export function useDiffMode(): UseDiffModeReturn {
36
+ const [mode, setMode] = useState<DiffViewMode>(getDiffMode());
37
+
38
+ useEffect(() => {
39
+ // Subscribe to mode changes and return unsubscribe function
40
+ return subscribeDiffMode(setMode);
41
+ }, []);
42
+
43
+ return { mode };
44
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * File Change Statistics Hook
3
+ *
4
+ * Aggregates diff metadata from tool executions to provide
5
+ * cumulative statistics about file changes in the current session.
6
+ *
7
+ * @module tui/hooks/useFileChangeStats
8
+ */
9
+
10
+ import { useMemo } from "react";
11
+ import { useTools } from "../context/ToolsContext.js";
12
+
13
+ // =============================================================================
14
+ // Types
15
+ // =============================================================================
16
+
17
+ /**
18
+ * Diff metadata from tool results (matches @vellum/core DiffMetadata)
19
+ */
20
+ interface DiffMetadata {
21
+ /** Unified diff string */
22
+ diff: string;
23
+ /** Number of lines added */
24
+ additions: number;
25
+ /** Number of lines deleted */
26
+ deletions: number;
27
+ }
28
+
29
+ /**
30
+ * Aggregated file change statistics
31
+ */
32
+ export interface FileChangeStats {
33
+ /** Total number of lines added across all files */
34
+ readonly additions: number;
35
+ /** Total number of lines deleted across all files */
36
+ readonly deletions: number;
37
+ /** Number of unique files modified */
38
+ readonly filesModified: number;
39
+ }
40
+
41
+ // =============================================================================
42
+ // Type Guards
43
+ // =============================================================================
44
+
45
+ /**
46
+ * Type guard for DiffMetadata
47
+ */
48
+ function isDiffMetadata(value: unknown): value is DiffMetadata {
49
+ if (!value || typeof value !== "object") return false;
50
+ const obj = value as Record<string, unknown>;
51
+ return (
52
+ typeof obj.additions === "number" &&
53
+ typeof obj.deletions === "number" &&
54
+ typeof obj.diff === "string"
55
+ );
56
+ }
57
+
58
+ // =============================================================================
59
+ // Hook
60
+ // =============================================================================
61
+
62
+ /**
63
+ * Aggregates file change statistics from completed tool executions.
64
+ *
65
+ * Extracts `diffMeta` from tool results and accumulates additions/deletions.
66
+ * Also tracks unique file paths modified.
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * const { additions, deletions, filesModified } = useFileChangeStats();
71
+ * // additions: 42, deletions: 15, filesModified: 3
72
+ * ```
73
+ */
74
+ export function useFileChangeStats(): FileChangeStats {
75
+ const { executions } = useTools();
76
+
77
+ return useMemo(() => {
78
+ let additions = 0;
79
+ let deletions = 0;
80
+ const paths = new Set<string>();
81
+
82
+ for (const exec of executions) {
83
+ // Only count completed executions
84
+ if (exec.status !== "complete") continue;
85
+
86
+ // Tool results may have diffMeta
87
+ const result = exec.result as Record<string, unknown> | undefined;
88
+ if (!result) continue;
89
+
90
+ // Extract diff metadata if present
91
+ const diffMeta = result.diffMeta;
92
+ if (isDiffMetadata(diffMeta)) {
93
+ additions += diffMeta.additions;
94
+ deletions += diffMeta.deletions;
95
+ }
96
+
97
+ // Track unique file paths
98
+ const path = result.path;
99
+ if (typeof path === "string" && path.length > 0) {
100
+ paths.add(path);
101
+ }
102
+ }
103
+
104
+ return {
105
+ additions,
106
+ deletions,
107
+ filesModified: paths.size,
108
+ };
109
+ }, [executions]);
110
+ }