@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,331 @@
1
+ /**
2
+ * Mode Slash Commands (T041)
3
+ *
4
+ * Provides slash commands for coding mode management:
5
+ * - /mode - Show current mode and options
6
+ * - /vibe - Switch to vibe mode
7
+ * - /plan - Switch to plan mode
8
+ * - /spec - Switch to spec mode (with confirmation)
9
+ *
10
+ * @module cli/commands/mode
11
+ */
12
+
13
+ import {
14
+ BUILTIN_CODING_MODES,
15
+ CODING_MODES,
16
+ type CodingMode,
17
+ type ModeManager,
18
+ } from "@vellum/core";
19
+ import { getModeIcon } from "../utils/icons.js";
20
+ import type { CommandContext, CommandResult, SlashCommand } from "./types.js";
21
+ import { error, interactive, success } from "./types.js";
22
+
23
+ // =============================================================================
24
+ // Module State
25
+ // =============================================================================
26
+
27
+ /**
28
+ * Reference to the active ModeManager instance.
29
+ * Set by the App component when initialized.
30
+ */
31
+ let modeManager: ModeManager | null = null;
32
+
33
+ /**
34
+ * Set the ModeManager instance for mode commands.
35
+ * Called by the App component during initialization.
36
+ *
37
+ * @param manager - The ModeManager instance to use
38
+ */
39
+ export function setModeCommandsManager(manager: ModeManager | null): void {
40
+ modeManager = manager;
41
+ }
42
+
43
+ /**
44
+ * Get the current ModeManager instance.
45
+ * Returns null if not yet initialized.
46
+ */
47
+ export function getModeCommandsManager(): ModeManager | null {
48
+ return modeManager;
49
+ }
50
+
51
+ // =============================================================================
52
+ // Mode Description Helpers
53
+ // =============================================================================
54
+
55
+ /**
56
+ * Get a human-readable description for a coding mode.
57
+ */
58
+ function getModeDescription(mode: CodingMode): string {
59
+ const config = BUILTIN_CODING_MODES[mode];
60
+ return config?.description ?? mode;
61
+ }
62
+
63
+ /**
64
+ * Get text-based icon indicator for a coding mode.
65
+ */
66
+ function getModeIndicator(mode: CodingMode): string {
67
+ return getModeIcon(mode);
68
+ }
69
+
70
+ /**
71
+ * Format mode information for display.
72
+ */
73
+ function formatModeInfo(mode: CodingMode, isCurrent: boolean): string {
74
+ const icon = getModeIndicator(mode);
75
+ const desc = getModeDescription(mode);
76
+ const marker = isCurrent ? " (current)" : "";
77
+ return ` ${icon} ${mode}${marker} - ${desc}`;
78
+ }
79
+
80
+ // =============================================================================
81
+ // /mode Command - Show Current Mode and Options
82
+ // =============================================================================
83
+
84
+ /**
85
+ * /mode command - Display current mode and available options.
86
+ *
87
+ * Shows the current coding mode and lists all available modes
88
+ * with their descriptions. Without a ModeManager, shows a
89
+ * placeholder indicating the mode system is not initialized.
90
+ */
91
+ export const modeCommand: SlashCommand = {
92
+ name: "mode",
93
+ description: "Show current coding mode and options",
94
+ kind: "builtin",
95
+ category: "workflow",
96
+ aliases: ["modes"],
97
+ positionalArgs: [
98
+ {
99
+ name: "mode",
100
+ type: "string",
101
+ description: "Mode to switch to (vibe, plan, spec)",
102
+ required: false,
103
+ },
104
+ ],
105
+ examples: [
106
+ "/mode - Show current mode and options",
107
+ "/mode vibe - Switch to vibe mode",
108
+ "/mode plan - Switch to plan mode",
109
+ "/mode spec - Switch to spec mode",
110
+ ],
111
+ subcommands: [
112
+ { name: "vibe", description: "Quick autonomous mode" },
113
+ { name: "plan", description: "Plan before execute mode" },
114
+ { name: "spec", description: "Full specification workflow" },
115
+ ],
116
+
117
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
118
+ const requestedMode = ctx.parsedArgs.positional[0] as string | undefined;
119
+
120
+ // If a mode is specified, delegate to switch logic
121
+ if (requestedMode) {
122
+ return switchToMode(requestedMode, ctx);
123
+ }
124
+
125
+ // Show current mode and options
126
+ if (!modeManager) {
127
+ // No manager available - show static info
128
+ const lines = [
129
+ "Coding Modes",
130
+ "",
131
+ "Available modes:",
132
+ ...CODING_MODES.map((m) => formatModeInfo(m, m === "vibe")),
133
+ "",
134
+ "Mode system not yet initialized. Using default: vibe",
135
+ "",
136
+ "Use /vibe, /plan, or /spec to switch modes.",
137
+ ];
138
+ return success(lines.join("\n"));
139
+ }
140
+
141
+ const currentMode = modeManager.getCurrentMode();
142
+ const lines = [
143
+ "Coding Modes",
144
+ "",
145
+ `Current mode: ${getModeIndicator(currentMode)} ${currentMode}`,
146
+ "",
147
+ "Available modes:",
148
+ ...CODING_MODES.map((m) => formatModeInfo(m, m === currentMode)),
149
+ "",
150
+ "Use /vibe, /plan, or /spec to switch modes.",
151
+ ];
152
+
153
+ return success(lines.join("\n"));
154
+ },
155
+ };
156
+
157
+ // =============================================================================
158
+ // Mode Switch Helper
159
+ // =============================================================================
160
+
161
+ /**
162
+ * Switch to a specified mode with validation.
163
+ *
164
+ * @param mode - Mode name to switch to
165
+ * @param ctx - Command context
166
+ * @returns Command result
167
+ */
168
+ async function switchToMode(mode: string, _ctx: CommandContext): Promise<CommandResult> {
169
+ // Validate mode name
170
+ if (!CODING_MODES.includes(mode as CodingMode)) {
171
+ return error("INVALID_ARGUMENT", `Invalid mode: ${mode}`, [
172
+ `Valid modes: ${CODING_MODES.join(", ")}`,
173
+ ]);
174
+ }
175
+
176
+ const targetMode = mode as CodingMode;
177
+
178
+ // If no manager, return a message about it
179
+ if (!modeManager) {
180
+ return success(
181
+ `Mode system not initialized. Would switch to ${getModeIndicator(targetMode)} ${targetMode}.`
182
+ );
183
+ }
184
+
185
+ // Check if already in this mode
186
+ const currentMode = modeManager.getCurrentMode();
187
+ if (currentMode === targetMode) {
188
+ return success(`Already in ${getModeIndicator(targetMode)} ${targetMode} mode.`);
189
+ }
190
+
191
+ // Spec mode requires confirmation
192
+ if (targetMode === "spec") {
193
+ return interactive({
194
+ inputType: "confirm",
195
+ message: `[WARN] Switch to spec mode? This enables a 6-phase structured workflow.`,
196
+ defaultValue: "n",
197
+ handler: async (value: string): Promise<CommandResult> => {
198
+ const confirmed = value.toLowerCase() === "y" || value.toLowerCase() === "yes";
199
+ if (confirmed) {
200
+ return await executeSwitch(targetMode);
201
+ }
202
+ return success("Mode switch cancelled.");
203
+ },
204
+ onCancel: () => success("Mode switch cancelled."),
205
+ });
206
+ }
207
+
208
+ // Direct switch for vibe and plan modes
209
+ return await executeSwitch(targetMode);
210
+ }
211
+
212
+ /**
213
+ * Execute the actual mode switch via ModeManager.
214
+ *
215
+ * @param targetMode - Mode to switch to
216
+ * @returns Command result
217
+ */
218
+ async function executeSwitch(targetMode: CodingMode): Promise<CommandResult> {
219
+ if (!modeManager) {
220
+ return error("OPERATION_NOT_ALLOWED", "Mode system not initialized", []);
221
+ }
222
+
223
+ const result = await modeManager.switchMode(targetMode, {
224
+ skipConfirmation: targetMode === "spec",
225
+ });
226
+
227
+ if (result.success) {
228
+ const icon = getModeIndicator(targetMode);
229
+ return success(`${icon} Switched to ${targetMode} mode.`, {
230
+ previousMode: result.previousMode,
231
+ currentMode: result.currentMode,
232
+ });
233
+ }
234
+
235
+ return error(
236
+ "OPERATION_NOT_ALLOWED",
237
+ `Cannot switch to ${targetMode}: ${result.reason ?? "Unknown error"}`,
238
+ []
239
+ );
240
+ }
241
+
242
+ // =============================================================================
243
+ // /vibe Command - Switch to Vibe Mode
244
+ // =============================================================================
245
+
246
+ /**
247
+ * /vibe command - Switch to vibe (fast autonomous) mode.
248
+ *
249
+ * Vibe mode enables fast, autonomous coding with full tool access.
250
+ * No checkpoints or confirmations required - the AI executes
251
+ * tasks directly.
252
+ */
253
+ export const vibeCommand: SlashCommand = {
254
+ name: "vibe",
255
+ description: "Switch to vibe mode (fast autonomous coding)",
256
+ kind: "builtin",
257
+ category: "workflow",
258
+ aliases: [],
259
+ examples: ["/vibe - Switch to fast autonomous mode"],
260
+
261
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
262
+ return switchToMode("vibe", ctx);
263
+ },
264
+ };
265
+
266
+ // =============================================================================
267
+ // /plan Command - Switch to Plan Mode
268
+ // =============================================================================
269
+
270
+ /**
271
+ * /plan command - Switch to plan mode.
272
+ *
273
+ * Plan mode requires the AI to create a plan before execution.
274
+ * One checkpoint for plan approval, then execution proceeds.
275
+ */
276
+ export const planCommand: SlashCommand = {
277
+ name: "plan",
278
+ description: "Switch to plan mode (plan-then-execute)",
279
+ kind: "builtin",
280
+ category: "workflow",
281
+ aliases: [],
282
+ examples: ["/plan - Switch to plan-then-execute mode"],
283
+
284
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
285
+ return switchToMode("plan", ctx);
286
+ },
287
+ };
288
+
289
+ // =============================================================================
290
+ // /spec Command - Switch to Spec Mode
291
+ // =============================================================================
292
+
293
+ /**
294
+ * /spec command - Switch to spec mode.
295
+ *
296
+ * Spec mode enables a 6-phase structured workflow:
297
+ * 1. Research
298
+ * 2. Requirements
299
+ * 3. Design
300
+ * 4. Tasks
301
+ * 5. Implementation
302
+ * 6. Validation
303
+ *
304
+ * Requires confirmation before switching due to its structured nature.
305
+ */
306
+ export const specCommand: SlashCommand = {
307
+ name: "spec",
308
+ description: "Switch to spec mode (6-phase structured workflow)",
309
+ kind: "builtin",
310
+ category: "workflow",
311
+ aliases: [],
312
+ examples: ["/spec - Switch to 6-phase structured workflow"],
313
+
314
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
315
+ return switchToMode("spec", ctx);
316
+ },
317
+ };
318
+
319
+ // =============================================================================
320
+ // Export All Mode Commands
321
+ // =============================================================================
322
+
323
+ /**
324
+ * All mode-related slash commands for registration.
325
+ */
326
+ export const modeSlashCommands: SlashCommand[] = [
327
+ modeCommand,
328
+ vibeCommand,
329
+ planCommand,
330
+ specCommand,
331
+ ];
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Model Slash Command (Chain 22)
3
+ *
4
+ * Provides slash commands for AI model management:
5
+ * - /model - Show current model and options
6
+ * - /model <provider>/<model> - Switch to a specific model
7
+ *
8
+ * @module cli/commands/model
9
+ */
10
+
11
+ import {
12
+ getModelInfo,
13
+ getProviderModels,
14
+ getSupportedProviders,
15
+ type ModelInfo,
16
+ } from "@vellum/provider";
17
+ import type { CommandContext, CommandResult, SlashCommand } from "./types.js";
18
+ import { error, success } from "./types.js";
19
+
20
+ // =============================================================================
21
+ // Module State
22
+ // =============================================================================
23
+
24
+ /**
25
+ * Current model configuration.
26
+ * Set by the App component when initialized.
27
+ */
28
+ interface ModelConfig {
29
+ provider: string;
30
+ model: string;
31
+ }
32
+
33
+ let currentConfig: ModelConfig | null = null;
34
+
35
+ /**
36
+ * Callback to update model selection.
37
+ */
38
+ let onModelChange: ((provider: string, model: string) => void) | null = null;
39
+
40
+ /**
41
+ * Set the current model configuration.
42
+ *
43
+ * @param provider - Current provider
44
+ * @param model - Current model ID
45
+ * @param onChange - Callback when model changes
46
+ */
47
+ export function setModelCommandConfig(
48
+ provider: string,
49
+ model: string,
50
+ onChange?: (provider: string, model: string) => void
51
+ ): void {
52
+ currentConfig = { provider, model };
53
+ onModelChange = onChange ?? null;
54
+ }
55
+
56
+ /**
57
+ * Get the current model configuration.
58
+ */
59
+ export function getModelCommandConfig(): ModelConfig | null {
60
+ return currentConfig;
61
+ }
62
+
63
+ // =============================================================================
64
+ // Helper Functions
65
+ // =============================================================================
66
+
67
+ /**
68
+ * Format context window for display.
69
+ */
70
+ function formatContextWindow(tokens: number): string {
71
+ if (tokens >= 1_000_000) {
72
+ return `${(tokens / 1_000_000).toFixed(1)}M`;
73
+ }
74
+ if (tokens >= 1_000) {
75
+ return `${(tokens / 1_000).toFixed(0)}K`;
76
+ }
77
+ return `${tokens}`;
78
+ }
79
+
80
+ /**
81
+ * Format price for display.
82
+ */
83
+ function formatPrice(pricePer1M: number): string {
84
+ if (pricePer1M === 0) {
85
+ return "Free";
86
+ }
87
+ return `$${pricePer1M.toFixed(2)}/M`;
88
+ }
89
+
90
+ /**
91
+ * Format model info for display.
92
+ */
93
+ function formatModelInfo(_provider: string, model: ModelInfo, isCurrent: boolean): string {
94
+ const marker = isCurrent ? " (current)" : "";
95
+ const ctx = formatContextWindow(model.contextWindow);
96
+ const priceIn = formatPrice(model.inputPrice ?? 0);
97
+ const priceOut = formatPrice(model.outputPrice ?? 0);
98
+ return ` ${model.name}${marker} [${ctx} ctx, ${priceIn}/${priceOut}]`;
99
+ }
100
+
101
+ // =============================================================================
102
+ // /model Command
103
+ // =============================================================================
104
+
105
+ /**
106
+ * /model command - Display current model and available options.
107
+ *
108
+ * Usage:
109
+ * - /model - Show current model and list all available models
110
+ * - /model <provider>/<model> - Switch to a specific model
111
+ */
112
+ export const modelCommand: SlashCommand = {
113
+ name: "model",
114
+ description: "Show or change the current AI model",
115
+ kind: "builtin",
116
+ category: "system",
117
+ aliases: ["models"],
118
+ positionalArgs: [
119
+ {
120
+ name: "model",
121
+ type: "string",
122
+ description: "Model to switch to (format: provider/model-id)",
123
+ required: false,
124
+ },
125
+ ],
126
+ examples: [
127
+ "/model - Show current model and options",
128
+ "/model anthropic/claude-sonnet-4-20250514 - Switch to Claude Sonnet 4",
129
+ "/model openai/gpt-4o - Switch to GPT-4o",
130
+ ],
131
+ // Dynamic subcommands: list all providers for autocomplete
132
+ subcommands: getSupportedProviders().map((provider) => ({
133
+ name: provider,
134
+ description: `${provider} models`,
135
+ })),
136
+
137
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
138
+ const positional = ctx.parsedArgs.positional as (string | undefined)[];
139
+
140
+ let requestedModel: string | undefined;
141
+
142
+ if (positional[0] && positional[1]) {
143
+ // Two args: provider and model from autocomplete flow
144
+ requestedModel = `${positional[0]}/${positional[1]}`;
145
+ } else if (positional[0]?.includes("/")) {
146
+ // Single arg with / separator (backwards compatible)
147
+ requestedModel = positional[0];
148
+ } else if (positional[0]) {
149
+ // Provider only - show available models for that provider
150
+ return showProviderModels(positional[0]);
151
+ }
152
+
153
+ if (requestedModel) {
154
+ return switchToModel(requestedModel);
155
+ }
156
+
157
+ // Show current model and options
158
+ return showModelInfo();
159
+ },
160
+ };
161
+
162
+ /**
163
+ * Show current model and available options.
164
+ */
165
+ function showModelInfo(): CommandResult {
166
+ const providers = getSupportedProviders();
167
+ const lines: string[] = ["AI Models", ""];
168
+
169
+ // Show current model if configured
170
+ if (currentConfig) {
171
+ const info = getModelInfo(currentConfig.provider, currentConfig.model);
172
+ lines.push(`Current: ${currentConfig.provider}/${info.name}`);
173
+ lines.push(
174
+ ` Context: ${formatContextWindow(info.contextWindow)} | ` +
175
+ `Input: ${formatPrice(info.inputPrice ?? 0)} | ` +
176
+ `Output: ${formatPrice(info.outputPrice ?? 0)}`
177
+ );
178
+ lines.push("");
179
+ } else {
180
+ lines.push("Current: Not configured");
181
+ lines.push("");
182
+ }
183
+
184
+ lines.push("Available models:");
185
+ lines.push("");
186
+
187
+ // List all providers and their models
188
+ for (const provider of providers) {
189
+ const models = getProviderModels(provider);
190
+
191
+ if (models.length === 0) continue;
192
+
193
+ lines.push(` ${provider}`);
194
+
195
+ for (const model of models) {
196
+ const isCurrent = currentConfig?.provider === provider && currentConfig?.model === model.id;
197
+ lines.push(formatModelInfo(provider, model, isCurrent));
198
+ }
199
+ lines.push("");
200
+ }
201
+
202
+ lines.push("Usage: /model <provider>/<model-id>");
203
+
204
+ return success(lines.join("\n"));
205
+ }
206
+
207
+ /**
208
+ * Show available models for a specific provider.
209
+ */
210
+ function showProviderModels(provider: string): CommandResult {
211
+ const providers = getSupportedProviders();
212
+ const normalizedProvider = provider.toLowerCase();
213
+
214
+ if (!providers.includes(normalizedProvider)) {
215
+ return error("INVALID_ARGUMENT", `Unknown provider: ${provider}`, [
216
+ `Valid providers: ${providers.join(", ")}`,
217
+ ]);
218
+ }
219
+
220
+ const models = getProviderModels(normalizedProvider);
221
+
222
+ if (models.length === 0) {
223
+ return error("INVALID_ARGUMENT", `No models available for ${provider}`);
224
+ }
225
+
226
+ const lines: string[] = [`${provider} Models`, ""];
227
+
228
+ for (const model of models) {
229
+ const isCurrent =
230
+ currentConfig?.provider === normalizedProvider && currentConfig?.model === model.id;
231
+ lines.push(formatModelInfo(normalizedProvider, model, isCurrent));
232
+ }
233
+
234
+ lines.push("");
235
+ lines.push(`Usage: /model ${provider}/<model-id>`);
236
+ lines.push(`Example: /model ${provider}/${models[0]?.id ?? "model-id"}`);
237
+
238
+ return success(lines.join("\n"));
239
+ }
240
+
241
+ /**
242
+ * Switch to a specified model.
243
+ */
244
+ function switchToModel(modelSpec: string): CommandResult {
245
+ // Parse provider/model format
246
+ const parts = modelSpec.split("/");
247
+ if (parts.length < 2) {
248
+ return error("INVALID_ARGUMENT", `Invalid model format: ${modelSpec}`, [
249
+ "Expected format: provider/model-id",
250
+ "Example: anthropic/claude-sonnet-4-20250514",
251
+ ]);
252
+ }
253
+
254
+ const [provider, ...modelParts] = parts;
255
+ const modelId = modelParts.join("/"); // Handle models with / in name
256
+
257
+ if (!provider || !modelId) {
258
+ return error("INVALID_ARGUMENT", `Invalid model format: ${modelSpec}`, [
259
+ "Expected format: provider/model-id",
260
+ ]);
261
+ }
262
+
263
+ // Validate provider
264
+ const providers = getSupportedProviders();
265
+ if (!providers.includes(provider.toLowerCase())) {
266
+ return error("INVALID_ARGUMENT", `Unknown provider: ${provider}`, [
267
+ `Valid providers: ${providers.join(", ")}`,
268
+ ]);
269
+ }
270
+
271
+ // Get model info (validates model exists or returns default)
272
+ const info = getModelInfo(provider, modelId);
273
+
274
+ // Check if already using this model
275
+ if (currentConfig?.provider === provider.toLowerCase() && currentConfig?.model === modelId) {
276
+ return success(`Already using ${info.name}`);
277
+ }
278
+
279
+ // Update via callback if available
280
+ if (onModelChange) {
281
+ onModelChange(provider.toLowerCase(), modelId);
282
+ return success(
283
+ `Switched to ${info.name}\n` +
284
+ ` Context: ${formatContextWindow(info.contextWindow)} | ` +
285
+ `Input: ${formatPrice(info.inputPrice ?? 0)} | ` +
286
+ `Output: ${formatPrice(info.outputPrice ?? 0)}`
287
+ );
288
+ }
289
+
290
+ // No callback - just report what would happen
291
+ return success(`Would switch to ${info.name}\n(Model system not fully initialized)`);
292
+ }
293
+
294
+ // =============================================================================
295
+ // Exports
296
+ // =============================================================================
297
+
298
+ export default modelCommand;