@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,383 @@
1
+ /**
2
+ * Plugin System Integration for TUI
3
+ *
4
+ * Provides initialization and integration of the plugin system with the CLI.
5
+ * Handles plugin loading from default search paths, command registration,
6
+ * and agent/hooks aggregation.
7
+ *
8
+ * @module cli/tui/plugins
9
+ */
10
+
11
+ import {
12
+ getSearchPaths,
13
+ type HookContext,
14
+ type HookEvent,
15
+ type PluginAgentDefinition,
16
+ PluginManager,
17
+ } from "@vellum/plugin";
18
+
19
+ import type { CommandContext, CommandResult, SlashCommand } from "../commands/types.js";
20
+
21
+ // =============================================================================
22
+ // Types
23
+ // =============================================================================
24
+
25
+ /**
26
+ * Plugin initialization options
27
+ */
28
+ export interface PluginInitOptions {
29
+ /**
30
+ * Project root directory for project-local plugins.
31
+ * If provided, plugins from `${projectRoot}/.vellum/plugins/` will be loaded.
32
+ * @default process.cwd()
33
+ */
34
+ projectRoot?: string;
35
+
36
+ /**
37
+ * Whether to automatically trust new plugins.
38
+ * @default false
39
+ */
40
+ autoTrust?: boolean;
41
+
42
+ /**
43
+ * Whether to eagerly load all plugin components.
44
+ * If false, only manifests are loaded initially.
45
+ * @default false
46
+ */
47
+ eagerLoad?: boolean;
48
+
49
+ /**
50
+ * Whether to include builtin plugins.
51
+ * @default true
52
+ */
53
+ includeBuiltin?: boolean;
54
+
55
+ /**
56
+ * Whether to include user plugins from ~/.vellum/plugins/
57
+ * @default true
58
+ */
59
+ includeUser?: boolean;
60
+
61
+ /**
62
+ * Whether to include global system plugins.
63
+ * @default true
64
+ */
65
+ includeGlobal?: boolean;
66
+ }
67
+
68
+ /**
69
+ * Result of plugin initialization
70
+ */
71
+ export interface PluginInitResult {
72
+ /** Initialized plugin manager instance */
73
+ manager: PluginManager;
74
+
75
+ /** Number of plugins loaded */
76
+ pluginCount: number;
77
+
78
+ /** Number of commands available */
79
+ commandCount: number;
80
+
81
+ /** Number of agents available */
82
+ agentCount: number;
83
+
84
+ /** Any errors encountered during loading */
85
+ errors: PluginLoadError[];
86
+ }
87
+
88
+ /**
89
+ * Plugin load error information
90
+ */
91
+ export interface PluginLoadError {
92
+ /** Plugin name */
93
+ name: string;
94
+
95
+ /** Plugin path */
96
+ path: string;
97
+
98
+ /** Error message */
99
+ message: string;
100
+ }
101
+
102
+ // =============================================================================
103
+ // Plugin Manager Singleton
104
+ // =============================================================================
105
+
106
+ let _pluginManager: PluginManager | null = null;
107
+
108
+ /**
109
+ * Gets the global plugin manager instance.
110
+ *
111
+ * Returns null if plugins have not been initialized yet.
112
+ *
113
+ * @returns The plugin manager or null
114
+ */
115
+ export function getPluginManager(): PluginManager | null {
116
+ return _pluginManager;
117
+ }
118
+
119
+ // =============================================================================
120
+ // Plugin Initialization
121
+ // =============================================================================
122
+
123
+ /**
124
+ * Initializes the plugin system.
125
+ *
126
+ * Loads plugins from:
127
+ * 1. Project plugins: `${projectRoot}/.vellum/plugins/`
128
+ * 2. User plugins: `~/.vellum/plugins/`
129
+ * 3. Global plugins: Platform-specific system directory
130
+ * 4. Builtin plugins: Shipped with the package
131
+ *
132
+ * @param options - Initialization options
133
+ * @returns Initialization result with manager and stats
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const result = await initializePlugins({ projectRoot: process.cwd() });
138
+ * console.log(`Loaded ${result.pluginCount} plugins`);
139
+ *
140
+ * // Access plugin commands
141
+ * const commands = getPluginCommands(result.manager);
142
+ * ```
143
+ */
144
+ export async function initializePlugins(
145
+ options: PluginInitOptions = {}
146
+ ): Promise<PluginInitResult> {
147
+ const {
148
+ projectRoot = process.cwd(),
149
+ autoTrust = false,
150
+ eagerLoad = false,
151
+ includeBuiltin = true,
152
+ includeUser = true,
153
+ includeGlobal = true,
154
+ } = options;
155
+
156
+ // Get search paths based on options
157
+ const searchPaths = getSearchPaths({
158
+ projectRoot,
159
+ includeBuiltin,
160
+ includeUser,
161
+ includeGlobal,
162
+ filterNonExistent: true,
163
+ });
164
+
165
+ // Create plugin manager with resolved paths
166
+ const manager = new PluginManager({
167
+ searchPaths,
168
+ autoTrust,
169
+ eagerLoad,
170
+ });
171
+ const errors: PluginLoadError[] = [];
172
+
173
+ // Initialize and load plugins
174
+ try {
175
+ await manager.initialize();
176
+ } catch (error) {
177
+ // Log initialization error but continue - manager may have partial success
178
+ console.warn("[plugins] Initialization warning:", error);
179
+ }
180
+
181
+ // Collect any failed plugins
182
+ for (const failed of manager.getFailedPlugins()) {
183
+ errors.push({
184
+ name: failed.name,
185
+ path: failed.path,
186
+ message: failed.error.message,
187
+ });
188
+ }
189
+
190
+ // Store manager globally for access by other modules
191
+ _pluginManager = manager;
192
+
193
+ // Return initialization result
194
+ const plugins = manager.getPlugins();
195
+ const commands = manager.getCommands();
196
+ const agents = manager.getAgents();
197
+
198
+ return {
199
+ manager,
200
+ pluginCount: plugins.length,
201
+ commandCount: commands.size,
202
+ agentCount: agents.size,
203
+ errors,
204
+ };
205
+ }
206
+
207
+ // =============================================================================
208
+ // Command Integration
209
+ // =============================================================================
210
+
211
+ /**
212
+ * Gets plugin commands in CLI SlashCommand format.
213
+ *
214
+ * Converts plugin commands to the SlashCommand format used by the
215
+ * CommandRegistry. Commands are returned with `kind: 'plugin'` and
216
+ * include the source plugin name.
217
+ *
218
+ * @param manager - Plugin manager instance
219
+ * @returns Array of SlashCommand definitions
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * const commands = getPluginCommands(manager);
224
+ * for (const cmd of commands) {
225
+ * commandRegistry.register(cmd);
226
+ * }
227
+ * ```
228
+ */
229
+ export function getPluginCommands(manager: PluginManager): SlashCommand[] {
230
+ const pluginCommands = manager.getCommands();
231
+ const commands: SlashCommand[] = [];
232
+
233
+ for (const [_name, cmd] of pluginCommands) {
234
+ // Convert plugin SlashCommand to CLI SlashCommand
235
+ // Need to wrap execute to adapt context types
236
+ const cliCommand: SlashCommand = {
237
+ name: cmd.name,
238
+ description: cmd.description,
239
+ kind: "plugin",
240
+ category: "tools", // Plugin commands go to tools category
241
+ aliases: cmd.aliases,
242
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
243
+ // Adapt CLI CommandContext to plugin CommandContext
244
+ const pluginCtx = {
245
+ rawArgs: ctx.parsedArgs.raw,
246
+ parsedArgs: {
247
+ positional: ctx.parsedArgs.positional,
248
+ named: ctx.parsedArgs.named,
249
+ },
250
+ allowedTools: undefined,
251
+ signal: ctx.signal,
252
+ };
253
+
254
+ // Execute the plugin command
255
+ const result = await cmd.execute(pluginCtx);
256
+
257
+ // Adapt plugin result to CLI result
258
+ if (result.kind === "success") {
259
+ return {
260
+ kind: "success",
261
+ message: result.message,
262
+ data: result.data,
263
+ };
264
+ } else {
265
+ return {
266
+ kind: "error",
267
+ code: "INTERNAL_ERROR",
268
+ message: result.message,
269
+ suggestions: result.suggestions,
270
+ };
271
+ }
272
+ },
273
+ };
274
+
275
+ commands.push(cliCommand);
276
+ }
277
+
278
+ return commands;
279
+ }
280
+
281
+ /**
282
+ * Gets the count of available plugin commands.
283
+ *
284
+ * Useful for status displays without loading full command data.
285
+ *
286
+ * @param manager - Plugin manager instance
287
+ * @returns Number of registered plugin commands
288
+ */
289
+ export function getPluginCommandCount(manager: PluginManager): number {
290
+ return manager.getCommands().size;
291
+ }
292
+
293
+ // =============================================================================
294
+ // Agent Integration
295
+ // =============================================================================
296
+
297
+ /**
298
+ * Gets plugin agents.
299
+ *
300
+ * Returns all agents defined by loaded plugins, keyed by their
301
+ * qualified slug (pluginName:agentSlug).
302
+ *
303
+ * @param manager - Plugin manager instance
304
+ * @returns Map of agent slug to agent definition
305
+ *
306
+ * @example
307
+ * ```typescript
308
+ * const agents = getPluginAgents(manager);
309
+ * for (const [slug, agent] of agents) {
310
+ * console.log(`Agent: ${agent.name} (${slug})`);
311
+ * }
312
+ * ```
313
+ */
314
+ export function getPluginAgents(manager: PluginManager): Map<string, PluginAgentDefinition> {
315
+ return manager.getAgents();
316
+ }
317
+
318
+ /**
319
+ * Gets a list of plugin agent names for display.
320
+ *
321
+ * @param manager - Plugin manager instance
322
+ * @returns Array of agent display names
323
+ */
324
+ export function getPluginAgentNames(manager: PluginManager): string[] {
325
+ const agents = manager.getAgents();
326
+ const names: string[] = [];
327
+
328
+ for (const [_slug, agent] of agents) {
329
+ names.push(agent.name);
330
+ }
331
+
332
+ return names;
333
+ }
334
+
335
+ // =============================================================================
336
+ // Hook Integration
337
+ // =============================================================================
338
+
339
+ /**
340
+ * Executes plugin hooks for a given event.
341
+ *
342
+ * Delegates to the PluginManager's hook execution system.
343
+ * Results from all plugins are returned.
344
+ *
345
+ * @param manager - Plugin manager instance
346
+ * @param event - Hook event type
347
+ * @param context - Context for hook execution
348
+ * @returns Array of hook results from all plugins
349
+ *
350
+ * @example
351
+ * ```typescript
352
+ * const results = await executePluginHooks(manager, "PreToolUse", {
353
+ * input: "user query",
354
+ * sessionId: "session-123",
355
+ * pluginName: "my-plugin",
356
+ * });
357
+ *
358
+ * // Check if any hook blocked the action
359
+ * const blocked = results.some(r => !r.allowed);
360
+ * ```
361
+ */
362
+ export async function executePluginHooks(
363
+ manager: PluginManager,
364
+ event: HookEvent,
365
+ context: HookContext
366
+ ): Promise<Array<{ allowed: boolean; message?: string }>> {
367
+ const results = await manager.executeHook(event, context);
368
+ return results;
369
+ }
370
+
371
+ // =============================================================================
372
+ // Cleanup
373
+ // =============================================================================
374
+
375
+ /**
376
+ * Disposes the plugin system.
377
+ *
378
+ * Cleans up resources and clears the global manager instance.
379
+ * Should be called during application shutdown.
380
+ */
381
+ export function disposePlugins(): void {
382
+ _pluginManager = null;
383
+ }
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Resilience Integration
3
+ *
4
+ * Wires circuit breaker, rate limiter, and provider fallback chain
5
+ * to create resilient provider connections.
6
+ *
7
+ * @module cli/tui/resilience
8
+ */
9
+
10
+ import {
11
+ CircuitBreaker,
12
+ type CircuitBreakerOptions,
13
+ CircuitOpenError,
14
+ // Rate limiter (from rate-limit module, now exported via core index)
15
+ createRateLimiter,
16
+ type FallbackConfig,
17
+ type FallbackProvider,
18
+ type FallbackResult,
19
+ ProviderFallbackChain,
20
+ RateLimiter,
21
+ type RateLimiterConfig,
22
+ } from "@vellum/core";
23
+
24
+ // =============================================================================
25
+ // Types
26
+ // =============================================================================
27
+
28
+ /**
29
+ * Provider interface for resilience wrapping
30
+ */
31
+ export interface Provider {
32
+ /** Provider identifier */
33
+ id: string;
34
+ /** Provider name for display */
35
+ name: string;
36
+ /** Priority (lower = higher priority) */
37
+ priority?: number;
38
+ /** Execute a request */
39
+ execute: <T>(request: () => Promise<T>) => Promise<T>;
40
+ /** Check if provider is healthy */
41
+ isHealthy?: () => boolean;
42
+ }
43
+
44
+ /**
45
+ * Resilient provider configuration
46
+ */
47
+ export interface ResilientProviderConfig {
48
+ /** Circuit breaker options */
49
+ circuitBreaker?: Partial<CircuitBreakerOptions>;
50
+ /** Rate limiter options */
51
+ rateLimiter?: Partial<RateLimiterConfig>;
52
+ /** Fallback configuration */
53
+ fallback?: Partial<FallbackConfig>;
54
+ /** Enable metrics collection */
55
+ enableMetrics?: boolean;
56
+ }
57
+
58
+ /**
59
+ * Resilient provider wrapper
60
+ */
61
+ export interface ResilientProvider {
62
+ /** Circuit breaker instance */
63
+ breaker: CircuitBreaker;
64
+ /** Rate limiter instance */
65
+ limiter: RateLimiter;
66
+ /** Execute a request with full resilience stack */
67
+ execute: <T>(
68
+ providerId: string,
69
+ request: () => Promise<T>
70
+ ) => Promise<{ success: boolean; value?: T; error?: Error }>;
71
+ /** Get circuit breaker state for a provider */
72
+ getCircuitState: (providerId: string) => string;
73
+ /** Get rate limiter stats */
74
+ getRateLimiterStats: () => { allowedRequests: number; throttledRequests: number };
75
+ /** Reset all circuits */
76
+ resetAllCircuits: () => void;
77
+ /** Dispose resources */
78
+ dispose: () => void;
79
+ }
80
+
81
+ /**
82
+ * Metrics for resilience tracking
83
+ */
84
+ export interface ResilienceMetrics {
85
+ /** Total requests */
86
+ totalRequests: number;
87
+ /** Successful requests */
88
+ successfulRequests: number;
89
+ /** Failed requests */
90
+ failedRequests: number;
91
+ /** Circuit breaker trips */
92
+ circuitTrips: number;
93
+ /** Rate limited requests */
94
+ rateLimitedRequests: number;
95
+ /** Fallback activations */
96
+ fallbackActivations: number;
97
+ }
98
+
99
+ // =============================================================================
100
+ // Default Configuration
101
+ // =============================================================================
102
+
103
+ const DEFAULT_CIRCUIT_BREAKER_OPTIONS: CircuitBreakerOptions = {
104
+ failureThreshold: 5,
105
+ resetTimeoutMs: 30000, // 30 seconds
106
+ halfOpenMaxAttempts: 3,
107
+ windowMs: 60000, // 1 minute
108
+ };
109
+
110
+ const DEFAULT_RATE_LIMITER_CONFIG: Partial<RateLimiterConfig> = {
111
+ defaultBucket: {
112
+ capacity: 60,
113
+ refillRate: 1, // 1 token per second = 60 RPM
114
+ },
115
+ };
116
+
117
+ // =============================================================================
118
+ // Resilience Factory
119
+ // =============================================================================
120
+
121
+ /**
122
+ * Create a resilient provider wrapper with circuit breaker and rate limiter.
123
+ *
124
+ * @param providers - Array of providers to wrap
125
+ * @param config - Resilience configuration
126
+ * @returns Resilient provider wrapper
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * const resilient = createResilientProvider([
131
+ * { id: "anthropic", name: "Anthropic Claude", execute: anthropicExecute },
132
+ * { id: "openai", name: "OpenAI GPT", execute: openaiExecute, priority: 1 },
133
+ * ]);
134
+ *
135
+ * const result = await resilient.execute("anthropic", () => client.chat(...));
136
+ * if (result.success) {
137
+ * console.log(result.value);
138
+ * } else {
139
+ * console.error("Request failed:", result.error);
140
+ * }
141
+ * ```
142
+ */
143
+ export function createResilientProvider(
144
+ providers: Provider[],
145
+ config: ResilientProviderConfig = {}
146
+ ): ResilientProvider {
147
+ // Create circuit breakers for each provider
148
+ const breakerOptions: CircuitBreakerOptions = {
149
+ ...DEFAULT_CIRCUIT_BREAKER_OPTIONS,
150
+ ...config.circuitBreaker,
151
+ };
152
+
153
+ const breakers = new Map<string, CircuitBreaker>();
154
+ for (const provider of providers) {
155
+ breakers.set(provider.id, new CircuitBreaker(provider.id, breakerOptions));
156
+ }
157
+
158
+ // Create rate limiter
159
+ const limiterConfig: RateLimiterConfig = {
160
+ ...DEFAULT_RATE_LIMITER_CONFIG,
161
+ ...config.rateLimiter,
162
+ } as RateLimiterConfig;
163
+ const limiter = createRateLimiter(limiterConfig);
164
+
165
+ // Metrics tracking
166
+ const metrics: ResilienceMetrics = {
167
+ totalRequests: 0,
168
+ successfulRequests: 0,
169
+ failedRequests: 0,
170
+ circuitTrips: 0,
171
+ rateLimitedRequests: 0,
172
+ fallbackActivations: 0,
173
+ };
174
+
175
+ // Get a circuit breaker for a provider (or create default)
176
+ const getBreaker = (providerId: string): CircuitBreaker => {
177
+ let breaker = breakers.get(providerId);
178
+ if (!breaker) {
179
+ breaker = new CircuitBreaker(providerId, breakerOptions);
180
+ breakers.set(providerId, breaker);
181
+ }
182
+ return breaker;
183
+ };
184
+
185
+ // Get primary breaker
186
+ const primaryBreaker = getBreaker(providers[0]?.id ?? "default");
187
+
188
+ return {
189
+ breaker: primaryBreaker,
190
+ limiter,
191
+
192
+ async execute<T>(
193
+ providerId: string,
194
+ request: () => Promise<T>
195
+ ): Promise<{ success: boolean; value?: T; error?: Error }> {
196
+ metrics.totalRequests++;
197
+
198
+ // Check rate limit first
199
+ const canProceed = limiter.tryAcquire(providerId);
200
+ if (!canProceed) {
201
+ metrics.rateLimitedRequests++;
202
+ // Wait for rate limit or throw
203
+ try {
204
+ await limiter.acquire(providerId, 1);
205
+ } catch {
206
+ return {
207
+ success: false,
208
+ error: new Error("Rate limit exceeded"),
209
+ };
210
+ }
211
+ }
212
+
213
+ // Get circuit breaker for provider
214
+ const breaker = getBreaker(providerId);
215
+
216
+ // Check circuit state
217
+ if (breaker.getState() === "OPEN") {
218
+ metrics.circuitTrips++;
219
+ return {
220
+ success: false,
221
+ error: new CircuitOpenError(providerId, breaker.getTimeUntilReset()),
222
+ };
223
+ }
224
+
225
+ // Execute through circuit breaker
226
+ try {
227
+ const result = await breaker.execute(request);
228
+ metrics.successfulRequests++;
229
+ return {
230
+ success: true,
231
+ value: result,
232
+ };
233
+ } catch (error) {
234
+ metrics.failedRequests++;
235
+
236
+ // If circuit opened, record it
237
+ if (error instanceof CircuitOpenError) {
238
+ metrics.circuitTrips++;
239
+ }
240
+
241
+ return {
242
+ success: false,
243
+ error: error as Error,
244
+ };
245
+ }
246
+ },
247
+
248
+ getCircuitState(providerId: string): string {
249
+ const breaker = breakers.get(providerId);
250
+ return breaker?.getState() ?? "unknown";
251
+ },
252
+
253
+ getRateLimiterStats() {
254
+ return {
255
+ allowedRequests: metrics.totalRequests - metrics.rateLimitedRequests,
256
+ throttledRequests: metrics.rateLimitedRequests,
257
+ };
258
+ },
259
+
260
+ resetAllCircuits() {
261
+ for (const breaker of breakers.values()) {
262
+ breaker.reset();
263
+ }
264
+ metrics.circuitTrips = 0;
265
+ },
266
+
267
+ dispose() {
268
+ breakers.clear();
269
+ },
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Create a simple circuit breaker for a single operation.
275
+ *
276
+ * @param id - Circuit breaker identifier
277
+ * @param options - Circuit breaker options
278
+ * @returns Circuit breaker instance
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * const breaker = createCircuitBreaker("api", { failureThreshold: 3 });
283
+ * try {
284
+ * const result = await breaker.execute(() => apiCall());
285
+ * } catch (error) {
286
+ * if (error instanceof CircuitOpenError) {
287
+ * console.log("Circuit is open, service unavailable");
288
+ * }
289
+ * }
290
+ * ```
291
+ */
292
+ export function createCircuitBreaker(
293
+ id: string = "default",
294
+ options: Partial<CircuitBreakerOptions> = {}
295
+ ): CircuitBreaker {
296
+ return new CircuitBreaker(id, {
297
+ ...DEFAULT_CIRCUIT_BREAKER_OPTIONS,
298
+ ...options,
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Create a rate limiter for API calls.
304
+ *
305
+ * @param config - Rate limiter configuration
306
+ * @returns Rate limiter instance
307
+ *
308
+ * @example
309
+ * ```typescript
310
+ * const limiter = createApiRateLimiter({
311
+ * defaultBucket: { capacity: 100, refillRate: 10 },
312
+ * });
313
+ *
314
+ * if (limiter.tryAcquire("api-key")) {
315
+ * await makeApiCall();
316
+ * } else {
317
+ * console.log("Rate limited, please wait");
318
+ * }
319
+ * ```
320
+ */
321
+ export function createApiRateLimiter(config: Partial<RateLimiterConfig> = {}): RateLimiter {
322
+ return createRateLimiter({
323
+ ...DEFAULT_RATE_LIMITER_CONFIG,
324
+ ...config,
325
+ } as RateLimiterConfig);
326
+ }
327
+
328
+ // =============================================================================
329
+ // Exports
330
+ // =============================================================================
331
+
332
+ export {
333
+ CircuitBreaker,
334
+ type CircuitBreakerOptions,
335
+ CircuitOpenError,
336
+ ProviderFallbackChain,
337
+ type FallbackConfig,
338
+ type FallbackResult,
339
+ type FallbackProvider,
340
+ RateLimiter,
341
+ type RateLimiterConfig,
342
+ };