@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 @@
1
+ # Placeholder to track empty directory in Git
@@ -0,0 +1,606 @@
1
+ /**
2
+ * Agents Command Group Tests
3
+ *
4
+ * Tests for the agents command group (T042, T047):
5
+ * - Command registration and help display
6
+ * - Subcommand routing
7
+ * - Options parsing
8
+ * - Integration tests with temp directories
9
+ *
10
+ * @module cli/commands/__tests__/agents
11
+ */
12
+
13
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
14
+ import { readFile } from "node:fs/promises";
15
+ import { tmpdir } from "node:os";
16
+ import { join } from "node:path";
17
+
18
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
19
+ import { handleAgentsGenerate } from "../agents/generate.js";
20
+ import { agentsCommand, executeAgents, getAgentsHelp } from "../agents/index.js";
21
+ import { handleAgentsShow } from "../agents/show.js";
22
+ import { handleAgentsValidate } from "../agents/validate.js";
23
+ import type { CommandContext, ParsedArgs } from "../types.js";
24
+
25
+ // =============================================================================
26
+ // Test Fixtures
27
+ // =============================================================================
28
+
29
+ const TEST_DIR = join(tmpdir(), "vellum-agents-test");
30
+
31
+ /**
32
+ * Valid AGENTS.md content for testing
33
+ */
34
+ const VALID_AGENTS_MD = `---
35
+ name: "test-project"
36
+ version: "1.0.0"
37
+ description: "Test project for agents command"
38
+ priority: 100
39
+ merge:
40
+ strategy: extend
41
+ arrays: append
42
+ ---
43
+
44
+ # Instructions
45
+
46
+ You are an AI coding assistant.
47
+
48
+ ## Allowed Tools
49
+
50
+ allowed-tools:
51
+ - "@readonly"
52
+ - "@edit"
53
+ `;
54
+
55
+ /**
56
+ * Invalid AGENTS.md content for testing (malformed YAML)
57
+ */
58
+ const INVALID_AGENTS_MD = `---
59
+ name: test-project
60
+ version: "1.0.0
61
+ missing end quote and bad indent
62
+ - this will break
63
+ priority: [invalid yaml
64
+ ---
65
+
66
+ # Instructions
67
+
68
+ This file has malformed YAML frontmatter.
69
+ `;
70
+
71
+ /**
72
+ * Create a mock CommandContext for testing
73
+ */
74
+ function createMockContext(overrides: Partial<ParsedArgs> = {}): CommandContext {
75
+ return {
76
+ session: {
77
+ id: "test-session",
78
+ provider: "anthropic",
79
+ cwd: TEST_DIR,
80
+ },
81
+ credentials: {
82
+ resolve: vi.fn(),
83
+ store: vi.fn(),
84
+ delete: vi.fn(),
85
+ list: vi.fn(),
86
+ } as unknown as CommandContext["credentials"],
87
+ toolRegistry: {
88
+ get: vi.fn(),
89
+ list: vi.fn(),
90
+ } as unknown as CommandContext["toolRegistry"],
91
+ parsedArgs: {
92
+ command: overrides.command ?? "agents",
93
+ positional: overrides.positional ?? [],
94
+ named: overrides.named ?? {},
95
+ raw: overrides.raw ?? "/agents",
96
+ },
97
+ emit: vi.fn(),
98
+ };
99
+ }
100
+
101
+ // =============================================================================
102
+ // Setup/Teardown for Integration Tests
103
+ // =============================================================================
104
+
105
+ beforeEach(() => {
106
+ // Create temp directory
107
+ if (!existsSync(TEST_DIR)) {
108
+ mkdirSync(TEST_DIR, { recursive: true });
109
+ }
110
+ // Change to test directory for tests
111
+ vi.spyOn(process, "cwd").mockReturnValue(TEST_DIR);
112
+ });
113
+
114
+ afterEach(() => {
115
+ // Clean up temp directory
116
+ if (existsSync(TEST_DIR)) {
117
+ rmSync(TEST_DIR, { recursive: true, force: true });
118
+ }
119
+ vi.restoreAllMocks();
120
+ });
121
+
122
+ // =============================================================================
123
+ // T042: Agents Command Group Tests
124
+ // =============================================================================
125
+
126
+ describe("agentsCommand", () => {
127
+ describe("command definition", () => {
128
+ it("should have correct name and aliases", () => {
129
+ expect(agentsCommand.name).toBe("agents");
130
+ expect(agentsCommand.aliases).toContain("agent");
131
+ });
132
+
133
+ it("should be a builtin config command", () => {
134
+ expect(agentsCommand.kind).toBe("builtin");
135
+ expect(agentsCommand.category).toBe("config");
136
+ });
137
+
138
+ it("should define subcommand positional argument", () => {
139
+ const subArg = agentsCommand.positionalArgs?.find((a) => a.name === "subcommand");
140
+ expect(subArg).toBeDefined();
141
+ expect(subArg?.required).toBe(false);
142
+ });
143
+
144
+ it("should define named arguments for options", () => {
145
+ const namedArgs = agentsCommand.namedArgs ?? [];
146
+ expect(namedArgs.some((a) => a.name === "json")).toBe(true);
147
+ expect(namedArgs.some((a) => a.name === "verbose")).toBe(true);
148
+ expect(namedArgs.some((a) => a.name === "scope")).toBe(true);
149
+ expect(namedArgs.some((a) => a.name === "output")).toBe(true);
150
+ expect(namedArgs.some((a) => a.name === "merge")).toBe(true);
151
+ expect(namedArgs.some((a) => a.name === "dry-run")).toBe(true);
152
+ });
153
+
154
+ it("should have examples", () => {
155
+ expect(agentsCommand.examples).toBeDefined();
156
+ expect(agentsCommand.examples?.length).toBeGreaterThan(0);
157
+ });
158
+ });
159
+ });
160
+
161
+ describe("getAgentsHelp", () => {
162
+ it("should return help text with subcommands", () => {
163
+ const help = getAgentsHelp();
164
+
165
+ expect(help).toContain("Agents Commands");
166
+ expect(help).toContain("show");
167
+ expect(help).toContain("validate");
168
+ expect(help).toContain("generate");
169
+ });
170
+
171
+ it("should include usage examples", () => {
172
+ const help = getAgentsHelp();
173
+
174
+ expect(help).toContain("/agents show");
175
+ expect(help).toContain("/agents validate");
176
+ expect(help).toContain("/agents generate");
177
+ });
178
+
179
+ it("should reference init command", () => {
180
+ const help = getAgentsHelp();
181
+ expect(help).toContain("/init");
182
+ });
183
+ });
184
+
185
+ describe("executeAgents", () => {
186
+ describe("help (no subcommand)", () => {
187
+ it("should return help text when no subcommand provided", async () => {
188
+ const ctx = createMockContext({ positional: [] });
189
+ const result = await executeAgents(ctx);
190
+
191
+ expect(result.kind).toBe("success");
192
+ if (result.kind === "success") {
193
+ expect(result.message).toContain("Agents Commands");
194
+ expect(result.message).toContain("show");
195
+ }
196
+ });
197
+
198
+ it("should return help for unknown subcommand", async () => {
199
+ const ctx = createMockContext({ positional: ["unknown"] });
200
+ const result = await executeAgents(ctx);
201
+
202
+ expect(result.kind).toBe("success");
203
+ if (result.kind === "success") {
204
+ expect(result.message).toContain("Agents Commands");
205
+ }
206
+ });
207
+ });
208
+
209
+ describe("show subcommand", () => {
210
+ it("should route to show handler", async () => {
211
+ const ctx = createMockContext({ positional: ["show"] });
212
+ const result = await executeAgents(ctx);
213
+
214
+ // Command is now implemented - returns success with config or "no config found" message
215
+ expect(result.kind).toBe("success");
216
+ });
217
+
218
+ it("should parse show options", async () => {
219
+ const ctx = createMockContext({
220
+ positional: ["show"],
221
+ named: { json: true, verbose: true },
222
+ });
223
+ const result = await executeAgents(ctx);
224
+
225
+ // With --json flag, should return JSON output
226
+ expect(result.kind).toBe("success");
227
+ });
228
+ });
229
+
230
+ describe("validate subcommand", () => {
231
+ it("should route to validate handler", async () => {
232
+ const ctx = createMockContext({ positional: ["validate"] });
233
+ const result = await executeAgents(ctx);
234
+
235
+ // Validate returns success if no invalid files found (or no files at all)
236
+ expect(["success", "error"]).toContain(result.kind);
237
+ });
238
+
239
+ it("should accept file path argument", async () => {
240
+ const ctx = createMockContext({
241
+ positional: ["validate", "./AGENTS.md"],
242
+ });
243
+ const result = await executeAgents(ctx);
244
+
245
+ // Validate with specific file - may succeed or error depending on file existence
246
+ expect(["success", "error"]).toContain(result.kind);
247
+ });
248
+ });
249
+
250
+ describe("generate subcommand", () => {
251
+ it("should route to generate handler", async () => {
252
+ const ctx = createMockContext({ positional: ["generate"] });
253
+ const result = await executeAgents(ctx);
254
+
255
+ // Generate may fail if AGENTS.md already exists, or succeed with dry-run
256
+ expect(["success", "error"]).toContain(result.kind);
257
+ });
258
+
259
+ it("should parse generate options", async () => {
260
+ const ctx = createMockContext({
261
+ positional: ["generate"],
262
+ named: { output: "./AGENTS.md", merge: true, "dry-run": true },
263
+ });
264
+ const result = await executeAgents(ctx);
265
+
266
+ // With --dry-run, should succeed with preview output
267
+ expect(result.kind).toBe("success");
268
+ });
269
+ });
270
+
271
+ describe("case insensitivity", () => {
272
+ it("should handle uppercase subcommands", async () => {
273
+ const ctx = createMockContext({ positional: ["SHOW"] });
274
+ const result = await executeAgents(ctx);
275
+
276
+ // Should route correctly regardless of case
277
+ expect(result.kind).toBe("success");
278
+ });
279
+
280
+ it("should handle mixed case subcommands", async () => {
281
+ const ctx = createMockContext({ positional: ["Validate"] });
282
+ const result = await executeAgents(ctx);
283
+
284
+ // Should route correctly regardless of case
285
+ expect(["success", "error"]).toContain(result.kind);
286
+ });
287
+ });
288
+ });
289
+
290
+ // =============================================================================
291
+ // T047: Integration Tests - agents show
292
+ // =============================================================================
293
+
294
+ describe("agents show integration", () => {
295
+ describe("with valid AGENTS.md", () => {
296
+ beforeEach(() => {
297
+ writeFileSync(join(TEST_DIR, "AGENTS.md"), VALID_AGENTS_MD);
298
+ });
299
+
300
+ it("should display config correctly", async () => {
301
+ const result = await handleAgentsShow({});
302
+
303
+ expect(result.kind).toBe("success");
304
+ if (result.kind === "success") {
305
+ expect(result.message).toContain("test-project");
306
+ expect(result.message).toContain("Instructions");
307
+ }
308
+ });
309
+
310
+ it("should output valid JSON with --json flag", async () => {
311
+ const result = await handleAgentsShow({ json: true });
312
+
313
+ expect(result.kind).toBe("success");
314
+ if (result.kind === "success" && result.message) {
315
+ // Parse JSON to verify it's valid
316
+ const jsonData = JSON.parse(result.message);
317
+ expect(jsonData.success).toBe(true);
318
+ expect(jsonData.config).toBeDefined();
319
+ expect(jsonData.config.name).toBe("test-project");
320
+ }
321
+ });
322
+
323
+ it("should show verbose details with --verbose flag", async () => {
324
+ const result = await handleAgentsShow({ verbose: true });
325
+
326
+ expect(result.kind).toBe("success");
327
+ if (result.kind === "success") {
328
+ expect(result.message).toContain("Merge Configuration");
329
+ expect(result.message).toContain("Strategy");
330
+ }
331
+ });
332
+ });
333
+
334
+ describe("without AGENTS.md", () => {
335
+ it("should indicate no config found", async () => {
336
+ const result = await handleAgentsShow({});
337
+
338
+ expect(result.kind).toBe("success");
339
+ if (result.kind === "success") {
340
+ expect(result.message).toContain("No AGENTS.md");
341
+ }
342
+ });
343
+
344
+ it("should return valid JSON indicating no config", async () => {
345
+ const result = await handleAgentsShow({ json: true });
346
+
347
+ expect(result.kind).toBe("success");
348
+ if (result.kind === "success" && result.message) {
349
+ const jsonData = JSON.parse(result.message);
350
+ expect(jsonData.success).toBe(false);
351
+ expect(jsonData.config).toBeNull();
352
+ }
353
+ });
354
+ });
355
+ });
356
+
357
+ // =============================================================================
358
+ // T047: Integration Tests - agents validate
359
+ // =============================================================================
360
+
361
+ describe("agents validate integration", () => {
362
+ describe("with valid AGENTS.md", () => {
363
+ beforeEach(() => {
364
+ writeFileSync(join(TEST_DIR, "AGENTS.md"), VALID_AGENTS_MD);
365
+ });
366
+
367
+ it("should return exit code 0 (success) for valid files", async () => {
368
+ // Validate specific file to avoid discovery of other files
369
+ const result = await handleAgentsValidate({
370
+ file: join(TEST_DIR, "AGENTS.md"),
371
+ });
372
+
373
+ expect(result.kind).toBe("success");
374
+ if (result.kind === "success") {
375
+ expect(result.message).toContain("Valid");
376
+ }
377
+ });
378
+
379
+ it("should validate specific file path", async () => {
380
+ const result = await handleAgentsValidate({
381
+ file: join(TEST_DIR, "AGENTS.md"),
382
+ });
383
+
384
+ expect(result.kind).toBe("success");
385
+ });
386
+
387
+ it("should return valid JSON for valid files", async () => {
388
+ // Validate specific file for consistent count
389
+ const result = await handleAgentsValidate({
390
+ file: join(TEST_DIR, "AGENTS.md"),
391
+ json: true,
392
+ });
393
+
394
+ expect(result.kind).toBe("success");
395
+ if (result.kind === "success" && result.message) {
396
+ const jsonData = JSON.parse(result.message);
397
+ expect(jsonData.success).toBe(true);
398
+ expect(jsonData.valid).toBe(1);
399
+ expect(jsonData.invalid).toBe(0);
400
+ }
401
+ });
402
+ });
403
+
404
+ describe("with invalid AGENTS.md", () => {
405
+ beforeEach(() => {
406
+ writeFileSync(join(TEST_DIR, "AGENTS.md"), INVALID_AGENTS_MD);
407
+ });
408
+
409
+ it("should return exit code 1 (error) for invalid files", async () => {
410
+ // Validate specific file to ensure we test the invalid one
411
+ const result = await handleAgentsValidate({
412
+ file: join(TEST_DIR, "AGENTS.md"),
413
+ });
414
+
415
+ expect(result.kind).toBe("error");
416
+ });
417
+
418
+ it("should include error details in output", async () => {
419
+ const result = await handleAgentsValidate({
420
+ file: join(TEST_DIR, "AGENTS.md"),
421
+ verbose: true,
422
+ });
423
+
424
+ expect(result.kind).toBe("error");
425
+ if (result.kind === "error") {
426
+ expect(result.message).toContain("Invalid");
427
+ }
428
+ });
429
+
430
+ it("should return JSON with invalid count", async () => {
431
+ const result = await handleAgentsValidate({
432
+ file: join(TEST_DIR, "AGENTS.md"),
433
+ json: true,
434
+ });
435
+
436
+ expect(result.kind).toBe("error");
437
+ if (result.kind === "error") {
438
+ const jsonData = JSON.parse(result.message);
439
+ expect(jsonData.success).toBe(false);
440
+ expect(jsonData.invalid).toBeGreaterThan(0);
441
+ }
442
+ });
443
+ });
444
+
445
+ describe("with nonexistent file", () => {
446
+ it("should return error for missing file", async () => {
447
+ const result = await handleAgentsValidate({
448
+ file: join(TEST_DIR, "nonexistent.md"),
449
+ });
450
+
451
+ expect(result.kind).toBe("error");
452
+ });
453
+ });
454
+
455
+ describe("with no AGENTS.md files", () => {
456
+ it("should handle empty project gracefully", async () => {
457
+ const result = await handleAgentsValidate({});
458
+
459
+ // No files to validate - should succeed with message
460
+ expect(result.kind).toBe("success");
461
+ if (result.kind === "success") {
462
+ expect(result.message).toContain("No AGENTS.md files found");
463
+ }
464
+ });
465
+ });
466
+ });
467
+
468
+ // =============================================================================
469
+ // T047: Integration Tests - agents generate
470
+ // =============================================================================
471
+
472
+ describe("agents generate integration", () => {
473
+ describe("dry-run mode", () => {
474
+ it("should show content without writing file", async () => {
475
+ const result = await handleAgentsGenerate({ dryRun: true });
476
+
477
+ expect(result.kind).toBe("success");
478
+ if (result.kind === "success") {
479
+ // Should contain preview content
480
+ expect(result.message).toContain("Preview");
481
+ expect(result.message).toContain("Would write to");
482
+ // File should NOT be created
483
+ expect(existsSync(join(TEST_DIR, "AGENTS.md"))).toBe(false);
484
+ }
485
+ });
486
+
487
+ it("should show detected project info in dry-run", async () => {
488
+ // Create package.json for detection
489
+ writeFileSync(
490
+ join(TEST_DIR, "package.json"),
491
+ JSON.stringify({
492
+ name: "my-test-project",
493
+ description: "A test project",
494
+ devDependencies: { typescript: "^5.0.0" },
495
+ })
496
+ );
497
+
498
+ const result = await handleAgentsGenerate({ dryRun: true });
499
+
500
+ expect(result.kind).toBe("success");
501
+ if (result.kind === "success") {
502
+ expect(result.message).toContain("my-test-project");
503
+ expect(result.message).toContain("TypeScript");
504
+ }
505
+ });
506
+ });
507
+
508
+ describe("file creation", () => {
509
+ it("should create AGENTS.md when none exists", async () => {
510
+ // Create package.json for detection
511
+ writeFileSync(join(TEST_DIR, "package.json"), JSON.stringify({ name: "new-project" }));
512
+
513
+ const result = await handleAgentsGenerate({});
514
+
515
+ expect(result.kind).toBe("success");
516
+ expect(existsSync(join(TEST_DIR, "AGENTS.md"))).toBe(true);
517
+
518
+ const content = await readFile(join(TEST_DIR, "AGENTS.md"), "utf-8");
519
+ expect(content).toContain("new-project");
520
+ });
521
+
522
+ it("should fail when AGENTS.md already exists without merge", async () => {
523
+ writeFileSync(join(TEST_DIR, "AGENTS.md"), "existing content");
524
+
525
+ const result = await handleAgentsGenerate({});
526
+
527
+ expect(result.kind).toBe("error");
528
+ if (result.kind === "error") {
529
+ expect(result.message).toContain("already exists");
530
+ }
531
+ });
532
+
533
+ it("should write to custom output path", async () => {
534
+ writeFileSync(join(TEST_DIR, "package.json"), JSON.stringify({ name: "custom-output" }));
535
+ const customPath = join(TEST_DIR, "custom-agents.md");
536
+
537
+ const result = await handleAgentsGenerate({ output: customPath });
538
+
539
+ expect(result.kind).toBe("success");
540
+ expect(existsSync(customPath)).toBe(true);
541
+ });
542
+ });
543
+
544
+ describe("merge mode", () => {
545
+ it("should merge with existing file when --merge flag is set", async () => {
546
+ writeFileSync(join(TEST_DIR, "AGENTS.md"), "# Existing content\n");
547
+ writeFileSync(join(TEST_DIR, "package.json"), JSON.stringify({ name: "merge-test" }));
548
+
549
+ const result = await handleAgentsGenerate({ merge: true });
550
+
551
+ expect(result.kind).toBe("success");
552
+
553
+ const content = await readFile(join(TEST_DIR, "AGENTS.md"), "utf-8");
554
+ expect(content).toContain("Existing content");
555
+ expect(content).toContain("merge-test");
556
+ });
557
+ });
558
+
559
+ describe("project detection", () => {
560
+ it("should detect TypeScript projects", async () => {
561
+ writeFileSync(join(TEST_DIR, "tsconfig.json"), "{}");
562
+ writeFileSync(join(TEST_DIR, "package.json"), JSON.stringify({ name: "ts-project" }));
563
+
564
+ const result = await handleAgentsGenerate({ dryRun: true });
565
+
566
+ expect(result.kind).toBe("success");
567
+ if (result.kind === "success") {
568
+ expect(result.message).toContain("TypeScript");
569
+ }
570
+ });
571
+
572
+ it("should detect React framework", async () => {
573
+ writeFileSync(
574
+ join(TEST_DIR, "package.json"),
575
+ JSON.stringify({
576
+ name: "react-app",
577
+ dependencies: { react: "^18.0.0" },
578
+ })
579
+ );
580
+
581
+ const result = await handleAgentsGenerate({ dryRun: true });
582
+
583
+ expect(result.kind).toBe("success");
584
+ if (result.kind === "success") {
585
+ expect(result.message).toContain("React");
586
+ }
587
+ });
588
+
589
+ it("should detect test framework", async () => {
590
+ writeFileSync(
591
+ join(TEST_DIR, "package.json"),
592
+ JSON.stringify({
593
+ name: "tested-app",
594
+ devDependencies: { vitest: "^1.0.0" },
595
+ })
596
+ );
597
+
598
+ const result = await handleAgentsGenerate({ dryRun: true });
599
+
600
+ expect(result.kind).toBe("success");
601
+ if (result.kind === "success") {
602
+ expect(result.message).toContain("Vitest");
603
+ }
604
+ });
605
+ });
606
+ });