@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,630 @@
1
+ /**
2
+ * User Command Loader (CLI Adapter)
3
+ *
4
+ * Adapts @vellum/core's UserCommandLoader for CLI's CommandRegistry.
5
+ * This provides the same public API while leveraging Core's comprehensive
6
+ * implementation including:
7
+ * - YAML command support (.yaml, .yml)
8
+ * - TypeScript command support (.ts, .mts)
9
+ * - Trust verification for security
10
+ * - Project and user directory support
11
+ *
12
+ * Also maintains backward compatibility with JavaScript command files (.js, .mjs)
13
+ * for CLI-only usage.
14
+ *
15
+ * @module cli/commands/user-commands
16
+ * @see @vellum/core/command/user-command-loader
17
+ */
18
+
19
+ import * as fs from "node:fs/promises";
20
+ import * as os from "node:os";
21
+ import * as path from "node:path";
22
+ import { pathToFileURL } from "node:url";
23
+
24
+ import {
25
+ type UserCommand as CoreUserCommand,
26
+ type UserCommandArgs as CoreUserCommandArgs,
27
+ type UserCommandContext as CoreUserCommandContext,
28
+ type UserCommandDefinition as CoreUserCommandDefinition,
29
+ type UserCommandLoadResult as CoreUserCommandLoadResult,
30
+ type UserCommandResult as CoreUserCommandResult,
31
+ createUserCommandLoader,
32
+ getTypeScriptCommandTemplate,
33
+ type UserCommandLoaderOptions,
34
+ type YamlPromptCommand,
35
+ type YamlShellCommand,
36
+ } from "@vellum/core";
37
+
38
+ import type { CommandRegistry } from "./registry.js";
39
+ import type { CommandCategory, CommandContext, CommandResult, SlashCommand } from "./types.js";
40
+
41
+ // =============================================================================
42
+ // Re-export Core Types (for backward compatibility)
43
+ // =============================================================================
44
+
45
+ /**
46
+ * User command definition format
47
+ *
48
+ * This is the structure users export from their command files.
49
+ * Re-exported from @vellum/core for backward compatibility.
50
+ */
51
+ export type UserCommandDefinition = CoreUserCommandDefinition;
52
+
53
+ /**
54
+ * Simplified args passed to user commands
55
+ * Re-exported from @vellum/core for backward compatibility.
56
+ */
57
+ export type UserCommandArgs = CoreUserCommandArgs;
58
+
59
+ /**
60
+ * Simplified context passed to user commands
61
+ * Re-exported from @vellum/core for backward compatibility.
62
+ */
63
+ export type UserCommandContext = CoreUserCommandContext;
64
+
65
+ /**
66
+ * Result from user command execution
67
+ * Re-exported from @vellum/core for backward compatibility.
68
+ */
69
+ export type UserCommandResult = CoreUserCommandResult;
70
+
71
+ /**
72
+ * Validation error for user commands
73
+ */
74
+ export interface UserCommandValidationError {
75
+ /** File path that failed validation */
76
+ file: string;
77
+ /** Error message */
78
+ error: string;
79
+ }
80
+
81
+ /**
82
+ * Result of loading user commands
83
+ */
84
+ export interface UserCommandLoadResult {
85
+ /** Successfully loaded commands */
86
+ commands: SlashCommand[];
87
+ /** Validation errors encountered */
88
+ errors: UserCommandValidationError[];
89
+ /** Total files scanned */
90
+ scanned: number;
91
+ }
92
+
93
+ // =============================================================================
94
+ // Constants
95
+ // =============================================================================
96
+
97
+ /** Default commands directory under user home */
98
+ const COMMANDS_DIR = "commands";
99
+
100
+ /** JavaScript extensions (CLI backward compatibility) */
101
+ const JS_EXTENSIONS = [".js", ".mjs"] as const;
102
+
103
+ /** Valid command categories */
104
+ const VALID_CATEGORIES: CommandCategory[] = [
105
+ "system",
106
+ "workflow",
107
+ "auth",
108
+ "session",
109
+ "navigation",
110
+ "tools",
111
+ "config",
112
+ "debug",
113
+ ];
114
+
115
+ // =============================================================================
116
+ // Adapter Functions
117
+ // =============================================================================
118
+
119
+ /**
120
+ * Convert Core's UserCommand to CLI's SlashCommand
121
+ *
122
+ * Handles all command types:
123
+ * - YAML shell commands: Execute shell and return result
124
+ * - YAML prompt commands: Return prompt for injection
125
+ * - TypeScript commands: Execute with full context
126
+ */
127
+ function adaptToSlashCommand(command: CoreUserCommand): SlashCommand {
128
+ const baseCommand: Omit<SlashCommand, "execute"> = {
129
+ name: command.name,
130
+ description: command.description,
131
+ kind: "user",
132
+ category: (command.category as CommandCategory) ?? "tools",
133
+ aliases: command.aliases,
134
+ };
135
+
136
+ return {
137
+ ...baseCommand,
138
+ execute: createExecuteHandler(command),
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Create execution handler based on command type
144
+ */
145
+ function createExecuteHandler(
146
+ command: CoreUserCommand
147
+ ): (ctx: CommandContext) => Promise<CommandResult> {
148
+ return async (ctx: CommandContext): Promise<CommandResult> => {
149
+ // Build args for user command
150
+ const args: CoreUserCommandArgs = {
151
+ raw: ctx.parsedArgs.raw,
152
+ positional: ctx.parsedArgs.positional.map((p) => String(p)),
153
+ named: ctx.parsedArgs.named as Record<string, string | boolean>,
154
+ };
155
+
156
+ // Build context for user command
157
+ const userCtx: CoreUserCommandContext = {
158
+ cwd: ctx.session.cwd,
159
+ sessionId: ctx.session.id,
160
+ provider: ctx.session.provider,
161
+ homeDir: os.homedir(),
162
+ env: {}, // Security: Don't expose full env
163
+ };
164
+
165
+ try {
166
+ // Handle YAML shell commands
167
+ if (command.type === "yaml" && command.commandType === "shell") {
168
+ const shellCmd = command as YamlShellCommand;
169
+ return {
170
+ kind: "success",
171
+ message: `Shell command: ${shellCmd.shell}`,
172
+ data: { shell: shellCmd.shell, args },
173
+ };
174
+ }
175
+
176
+ // Handle YAML prompt commands
177
+ if (command.type === "yaml" && command.commandType === "prompt") {
178
+ const promptCmd = command as YamlPromptCommand;
179
+ return {
180
+ kind: "success",
181
+ message: promptCmd.prompt,
182
+ data: { prompt: promptCmd.prompt },
183
+ };
184
+ }
185
+
186
+ // Handle TypeScript commands
187
+ if (command.type === "typescript" && command.execute) {
188
+ const result = await command.execute(args, userCtx);
189
+
190
+ if (result.success) {
191
+ // Handle prompt injection
192
+ if (result.prompt) {
193
+ return {
194
+ kind: "success",
195
+ message: result.prompt,
196
+ data: { prompt: result.prompt, ...((result.data as object) ?? {}) },
197
+ };
198
+ }
199
+
200
+ // Handle shell command
201
+ if (result.shell) {
202
+ return {
203
+ kind: "success",
204
+ message: `Shell command: ${result.shell}`,
205
+ data: { shell: result.shell, ...((result.data as object) ?? {}) },
206
+ };
207
+ }
208
+
209
+ return {
210
+ kind: "success",
211
+ message: result.message,
212
+ data: result.data,
213
+ };
214
+ }
215
+
216
+ return {
217
+ kind: "error",
218
+ code: "INTERNAL_ERROR",
219
+ message: result.error ?? "Command failed",
220
+ };
221
+ }
222
+
223
+ // Untrusted TypeScript command
224
+ if (command.type === "typescript" && !command.trusted) {
225
+ return {
226
+ kind: "error",
227
+ code: "PERMISSION_DENIED",
228
+ message: `Command '${command.name}' requires trust verification. Run /trust to approve.`,
229
+ };
230
+ }
231
+
232
+ return {
233
+ kind: "error",
234
+ code: "INTERNAL_ERROR",
235
+ message: `Unknown command type: ${command.type}`,
236
+ };
237
+ } catch (err) {
238
+ return {
239
+ kind: "error",
240
+ code: "INTERNAL_ERROR",
241
+ message: err instanceof Error ? err.message : String(err),
242
+ };
243
+ }
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Convert Core's load result to CLI's format
249
+ */
250
+ function adaptLoadResult(coreResult: CoreUserCommandLoadResult): UserCommandLoadResult {
251
+ // Convert trusted commands to SlashCommands
252
+ const commands = coreResult.commands.map(adaptToSlashCommand);
253
+
254
+ // Also include pending trust commands (they'll show permission error when executed)
255
+ const pendingCommands = coreResult.pendingTrust.map(adaptToSlashCommand);
256
+
257
+ // Convert error format
258
+ const errors: UserCommandValidationError[] = coreResult.errors.map((e) => ({
259
+ file: e.file,
260
+ error: e.error,
261
+ }));
262
+
263
+ return {
264
+ commands: [...commands, ...pendingCommands],
265
+ errors,
266
+ scanned: coreResult.scanned,
267
+ };
268
+ }
269
+
270
+ // =============================================================================
271
+ // JavaScript Command Loader (Backward Compatibility)
272
+ // =============================================================================
273
+
274
+ /**
275
+ * Load a JavaScript command file directly (backward compatibility).
276
+ * Core loader only supports .ts/.yaml, so we handle .js files here.
277
+ */
278
+ async function loadJsCommandFile(filePath: string): Promise<CoreUserCommandDefinition> {
279
+ const fileUrl = pathToFileURL(filePath).href;
280
+ const module = await import(fileUrl);
281
+ const definition = module.default;
282
+
283
+ if (!definition) {
284
+ throw new Error("No default export found");
285
+ }
286
+
287
+ return definition as CoreUserCommandDefinition;
288
+ }
289
+
290
+ /**
291
+ * Validate a JavaScript command definition
292
+ */
293
+ function validateJsDefinition(
294
+ definition: unknown,
295
+ _filePath: string
296
+ ): { valid: true } | { valid: false; error: string } {
297
+ if (!definition || typeof definition !== "object") {
298
+ return { valid: false, error: "Invalid command definition: not an object" };
299
+ }
300
+
301
+ const def = definition as Record<string, unknown>;
302
+
303
+ // Check required fields
304
+ if (typeof def.name !== "string" || def.name.trim() === "") {
305
+ return { valid: false, error: "Missing or invalid 'name' field" };
306
+ }
307
+
308
+ if (typeof def.description !== "string" || def.description.trim() === "") {
309
+ return { valid: false, error: "Missing or invalid 'description' field" };
310
+ }
311
+
312
+ if (typeof def.execute !== "function") {
313
+ return { valid: false, error: "Missing or invalid 'execute' function" };
314
+ }
315
+
316
+ // Validate name format (should start with /)
317
+ const name = def.name as string;
318
+ if (!name.startsWith("/")) {
319
+ return {
320
+ valid: false,
321
+ error: `Command name must start with '/': got '${name}'`,
322
+ };
323
+ }
324
+
325
+ // Validate aliases if present
326
+ if (def.aliases !== undefined) {
327
+ if (!Array.isArray(def.aliases)) {
328
+ return { valid: false, error: "'aliases' must be an array" };
329
+ }
330
+ for (const alias of def.aliases) {
331
+ if (typeof alias !== "string") {
332
+ return { valid: false, error: "All aliases must be strings" };
333
+ }
334
+ }
335
+ }
336
+
337
+ // Validate category if present
338
+ if (def.category !== undefined) {
339
+ if (!VALID_CATEGORIES.includes(def.category as CommandCategory)) {
340
+ return {
341
+ valid: false,
342
+ error: `Invalid category '${def.category}'. Must be one of: ${VALID_CATEGORIES.join(", ")}`,
343
+ };
344
+ }
345
+ }
346
+
347
+ return { valid: true };
348
+ }
349
+
350
+ /**
351
+ * Convert a JavaScript command definition to SlashCommand
352
+ */
353
+ function convertJsToSlashCommand(definition: CoreUserCommandDefinition): SlashCommand {
354
+ // Strip leading / from name for internal use
355
+ const name = definition.name.startsWith("/") ? definition.name.slice(1) : definition.name;
356
+
357
+ return {
358
+ name,
359
+ description: definition.description,
360
+ kind: "user",
361
+ category: (definition.category as CommandCategory) ?? "tools",
362
+ aliases: definition.aliases,
363
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
364
+ // Build simplified args for user command
365
+ const args: CoreUserCommandArgs = {
366
+ raw: ctx.parsedArgs.raw,
367
+ positional: ctx.parsedArgs.positional.map((p) => String(p)),
368
+ named: ctx.parsedArgs.named as Record<string, string | boolean>,
369
+ };
370
+
371
+ // Build simplified context for user command
372
+ const userCtx: CoreUserCommandContext = {
373
+ cwd: ctx.session.cwd,
374
+ sessionId: ctx.session.id,
375
+ provider: ctx.session.provider,
376
+ homeDir: os.homedir(),
377
+ env: {},
378
+ };
379
+
380
+ try {
381
+ const result = await definition.execute(args, userCtx);
382
+
383
+ if (result.success) {
384
+ return {
385
+ kind: "success",
386
+ message: result.message,
387
+ data: result.data,
388
+ };
389
+ } else {
390
+ return {
391
+ kind: "error",
392
+ code: "INTERNAL_ERROR",
393
+ message: result.error ?? "Command failed",
394
+ };
395
+ }
396
+ } catch (err) {
397
+ return {
398
+ kind: "error",
399
+ code: "INTERNAL_ERROR",
400
+ message: err instanceof Error ? err.message : String(err),
401
+ };
402
+ }
403
+ },
404
+ };
405
+ }
406
+
407
+ // =============================================================================
408
+ // UserCommandLoader Class
409
+ // =============================================================================
410
+
411
+ /**
412
+ * Loads and validates user-defined commands
413
+ *
414
+ * Adapts @vellum/core's UserCommandLoader for CLI's CommandRegistry.
415
+ * Scans both project (.vellum/commands/) and user (~/.vellum/commands/)
416
+ * directories for command files.
417
+ *
418
+ * Supports:
419
+ * - JavaScript files (.js, .mjs) - loaded directly for backward compatibility
420
+ * - TypeScript files (.ts, .mts) - via Core loader with trust verification
421
+ * - YAML files (.yaml, .yml) - via Core loader
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * const loader = new UserCommandLoader();
426
+ * const result = await loader.load();
427
+ *
428
+ * if (result.errors.length > 0) {
429
+ * console.warn('Some commands failed to load:', result.errors);
430
+ * }
431
+ *
432
+ * for (const cmd of result.commands) {
433
+ * registry.register(cmd);
434
+ * }
435
+ * ```
436
+ */
437
+ export class UserCommandLoader {
438
+ /** Base directory for user commands */
439
+ private readonly commandsDir: string;
440
+ /** Core loader options */
441
+ private readonly coreOptions: UserCommandLoaderOptions;
442
+
443
+ /**
444
+ * Create a new UserCommandLoader
445
+ *
446
+ * @param baseDir - Optional base directory (defaults to ~/.vellum)
447
+ */
448
+ constructor(baseDir?: string) {
449
+ const vellumDir = baseDir ?? path.join(os.homedir(), ".vellum");
450
+ this.commandsDir = path.join(vellumDir, COMMANDS_DIR);
451
+
452
+ // Configure core loader
453
+ this.coreOptions = {
454
+ cwd: process.cwd(),
455
+ loadUserCommands: true,
456
+ userHomeDir: baseDir ? path.dirname(baseDir) : os.homedir(),
457
+ autoTrust: false, // Require explicit trust for TypeScript commands
458
+ };
459
+
460
+ // If baseDir is specified, we're in test mode
461
+ if (baseDir) {
462
+ this.coreOptions.cwd = baseDir;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Get the commands directory path
468
+ */
469
+ getCommandsDir(): string {
470
+ return this.commandsDir;
471
+ }
472
+
473
+ /**
474
+ * Load all user commands from the commands directory
475
+ *
476
+ * @returns Load result with commands and any errors
477
+ */
478
+ async load(): Promise<UserCommandLoadResult> {
479
+ // Check if directory exists first (backward compatibility)
480
+ const dirExists = await this.directoryExists();
481
+ if (!dirExists) {
482
+ return {
483
+ commands: [],
484
+ errors: [],
485
+ scanned: 0,
486
+ };
487
+ }
488
+
489
+ // Load JavaScript files directly (backward compatibility)
490
+ const jsResult = await this.loadJavaScriptCommands();
491
+
492
+ // Use Core's loader for TypeScript and YAML
493
+ const coreLoader = createUserCommandLoader(this.coreOptions);
494
+ const coreResult = await coreLoader.load();
495
+ const coreAdapted = adaptLoadResult(coreResult);
496
+
497
+ // Merge results (JS commands + Core commands)
498
+ return {
499
+ commands: [...jsResult.commands, ...coreAdapted.commands],
500
+ errors: [...jsResult.errors, ...coreAdapted.errors],
501
+ scanned: jsResult.scanned + coreAdapted.scanned,
502
+ };
503
+ }
504
+
505
+ /**
506
+ * Load JavaScript command files directly (backward compatibility)
507
+ */
508
+ private async loadJavaScriptCommands(): Promise<UserCommandLoadResult> {
509
+ const result: UserCommandLoadResult = {
510
+ commands: [],
511
+ errors: [],
512
+ scanned: 0,
513
+ };
514
+
515
+ try {
516
+ const entries = await fs.readdir(this.commandsDir, { withFileTypes: true });
517
+
518
+ for (const entry of entries) {
519
+ if (!entry.isFile()) continue;
520
+
521
+ const ext = path.extname(entry.name).toLowerCase();
522
+ if (!JS_EXTENSIONS.includes(ext as (typeof JS_EXTENSIONS)[number])) {
523
+ continue;
524
+ }
525
+
526
+ result.scanned++;
527
+ const filePath = path.join(this.commandsDir, entry.name);
528
+
529
+ try {
530
+ const definition = await loadJsCommandFile(filePath);
531
+ const validation = validateJsDefinition(definition, filePath);
532
+
533
+ if (validation.valid) {
534
+ const command = convertJsToSlashCommand(definition);
535
+ result.commands.push(command);
536
+ } else {
537
+ result.errors.push({
538
+ file: filePath,
539
+ error: validation.error,
540
+ });
541
+ }
542
+ } catch (err) {
543
+ result.errors.push({
544
+ file: filePath,
545
+ error: err instanceof Error ? err.message : String(err),
546
+ });
547
+ }
548
+ }
549
+ } catch {
550
+ // Directory doesn't exist or can't be read
551
+ }
552
+
553
+ return result;
554
+ }
555
+
556
+ /**
557
+ * Check if the commands directory exists
558
+ */
559
+ async directoryExists(): Promise<boolean> {
560
+ try {
561
+ const stat = await fs.stat(this.commandsDir);
562
+ return stat.isDirectory();
563
+ } catch {
564
+ return false;
565
+ }
566
+ }
567
+ }
568
+
569
+ // =============================================================================
570
+ // Registry Integration
571
+ // =============================================================================
572
+
573
+ /**
574
+ * Load and register all user commands
575
+ *
576
+ * @param registry - Command registry to register commands in
577
+ * @param options - Optional configuration
578
+ * @returns Load result with commands and errors
579
+ *
580
+ * @example
581
+ * ```typescript
582
+ * const registry = new CommandRegistry();
583
+ * const result = await registerUserCommands(registry);
584
+ *
585
+ * console.log(`Loaded ${result.commands.length} user commands`);
586
+ * if (result.errors.length > 0) {
587
+ * console.warn('Failed to load:', result.errors);
588
+ * }
589
+ * ```
590
+ */
591
+ export async function registerUserCommands(
592
+ registry: CommandRegistry,
593
+ options?: { baseDir?: string }
594
+ ): Promise<UserCommandLoadResult> {
595
+ const loader = new UserCommandLoader(options?.baseDir);
596
+ const result = await loader.load();
597
+
598
+ // Register all successfully loaded commands
599
+ for (const command of result.commands) {
600
+ registry.register(command);
601
+ }
602
+
603
+ return result;
604
+ }
605
+
606
+ /**
607
+ * Create the commands directory if it doesn't exist
608
+ *
609
+ * @param baseDir - Optional base directory (defaults to ~/.vellum)
610
+ * @returns Path to the commands directory
611
+ */
612
+ export async function ensureCommandsDirectory(baseDir?: string): Promise<string> {
613
+ const vellumDir = baseDir ?? path.join(os.homedir(), ".vellum");
614
+ const commandsDir = path.join(vellumDir, COMMANDS_DIR);
615
+
616
+ await fs.mkdir(commandsDir, { recursive: true });
617
+
618
+ return commandsDir;
619
+ }
620
+
621
+ /**
622
+ * Get example command template
623
+ *
624
+ * Returns TypeScript command template from @vellum/core
625
+ *
626
+ * @returns Example command file content
627
+ */
628
+ export function getCommandTemplate(): string {
629
+ return getTypeScriptCommandTemplate();
630
+ }