@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,883 @@
1
+ /**
2
+ * Authentication Slash Commands
3
+ *
4
+ * Provides slash commands for credential management in TUI:
5
+ * - /auth - Unified authentication command (set, delete, list)
6
+ * - /credentials - Show credential status
7
+ *
8
+ * Supports both legacy interface (SlashCommandResult) and
9
+ * enhanced interface (SlashCommand from types.ts) for backward compatibility.
10
+ *
11
+ * @module cli/commands/auth
12
+ */
13
+
14
+ import {
15
+ CredentialManager,
16
+ type CredentialRef,
17
+ EncryptedFileStore,
18
+ EnvCredentialStore,
19
+ KeychainStore,
20
+ } from "@vellum/core";
21
+
22
+ import type {
23
+ CommandContext,
24
+ CommandResult,
25
+ SlashCommand as EnhancedSlashCommand,
26
+ } from "./types.js";
27
+ import { error, interactive, success } from "./types.js";
28
+
29
+ // =============================================================================
30
+ // Types
31
+ // =============================================================================
32
+
33
+ /**
34
+ * Result of a slash command execution
35
+ * @deprecated Use CommandResult from ./types.ts for new commands
36
+ */
37
+ export interface SlashCommandResult {
38
+ /** Whether the command succeeded */
39
+ success: boolean;
40
+ /** Message to display to user */
41
+ message: string;
42
+ /** Additional data (for programmatic use) */
43
+ data?: Record<string, unknown>;
44
+ /** Whether to prompt for input */
45
+ promptForInput?: {
46
+ type: "api_key";
47
+ provider: string;
48
+ placeholder: string;
49
+ onSubmit: (value: string) => Promise<SlashCommandResult>;
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Slash command handler function type
55
+ */
56
+ export type SlashCommandHandler = (
57
+ args: string[],
58
+ context: SlashCommandContext
59
+ ) => Promise<SlashCommandResult>;
60
+
61
+ /**
62
+ * Context provided to slash command handlers
63
+ */
64
+ export interface SlashCommandContext {
65
+ /** Current provider (from chat session) */
66
+ currentProvider?: string;
67
+ /** Credential manager instance */
68
+ credentialManager: CredentialManager;
69
+ }
70
+
71
+ /**
72
+ * Registered slash command
73
+ */
74
+ export interface SlashCommand {
75
+ /** Command name (without slash) */
76
+ name: string;
77
+ /** Command aliases */
78
+ aliases?: string[];
79
+ /** Command description */
80
+ description: string;
81
+ /** Usage pattern */
82
+ usage: string;
83
+ /** Handler function */
84
+ handler: SlashCommandHandler;
85
+ }
86
+
87
+ // =============================================================================
88
+ // Credential Manager Factory
89
+ // =============================================================================
90
+
91
+ /**
92
+ * Create a credential manager instance with default stores
93
+ */
94
+ export async function createCredentialManager(): Promise<CredentialManager> {
95
+ const stores = [
96
+ new EnvCredentialStore(),
97
+ new KeychainStore(),
98
+ new EncryptedFileStore({
99
+ filePath: `${process.env.HOME ?? process.env.USERPROFILE}/.vellum/credentials.enc`,
100
+ password: process.env.VELLUM_CREDENTIAL_PASSWORD ?? "vellum-default-key",
101
+ }),
102
+ ];
103
+
104
+ return new CredentialManager(stores, {
105
+ preferredWriteStore: "keychain",
106
+ });
107
+ }
108
+
109
+ // =============================================================================
110
+ // Slash Command Handlers
111
+ // =============================================================================
112
+
113
+ /**
114
+ * /credentials command handler
115
+ *
116
+ * Shows credential status for all or a specific provider.
117
+ * Usage: /credentials [provider]
118
+ */
119
+ async function handleCredentials(
120
+ args: string[],
121
+ context: SlashCommandContext
122
+ ): Promise<SlashCommandResult> {
123
+ const filterProvider = args[0]?.toLowerCase();
124
+
125
+ try {
126
+ // Get store availability
127
+ const availability = await context.credentialManager.getStoreAvailability();
128
+
129
+ // List credentials
130
+ const listResult = await context.credentialManager.list(filterProvider);
131
+
132
+ if (!listResult.ok) {
133
+ return {
134
+ success: false,
135
+ message: `❌ Failed to list credentials: ${listResult.error.message}`,
136
+ };
137
+ }
138
+
139
+ const credentials = listResult.value;
140
+
141
+ // Build message
142
+ const lines: string[] = [];
143
+ lines.push("🔐 Credential Status");
144
+ lines.push("━".repeat(40));
145
+
146
+ // Store availability
147
+ lines.push("\n📦 Storage Backends:");
148
+ for (const [store, available] of Object.entries(availability)) {
149
+ lines.push(` ${available ? "✓" : "✗"} ${store}`);
150
+ }
151
+
152
+ // Credentials
153
+ lines.push("\n🔑 Credentials:");
154
+ if (credentials.length === 0) {
155
+ if (filterProvider) {
156
+ lines.push(` No credential found for ${filterProvider}`);
157
+ } else {
158
+ lines.push(" No credentials stored");
159
+ lines.push(" Use /auth set <provider> to add one");
160
+ }
161
+ } else {
162
+ for (const cred of credentials) {
163
+ const maskedValue = cred.maskedHint ?? "***";
164
+ lines.push(` • ${cred.provider} (${cred.source}): ${maskedValue} [${cred.type}]`);
165
+ }
166
+ }
167
+
168
+ lines.push("━".repeat(40));
169
+
170
+ return {
171
+ success: true,
172
+ message: lines.join("\n"),
173
+ data: {
174
+ availability,
175
+ credentials: credentials.map((c: CredentialRef) => ({
176
+ provider: c.provider,
177
+ source: c.source,
178
+ type: c.type,
179
+ maskedHint: c.maskedHint,
180
+ })),
181
+ },
182
+ };
183
+ } catch (err) {
184
+ return {
185
+ success: false,
186
+ message: `❌ Error: ${err instanceof Error ? err.message : String(err)}`,
187
+ };
188
+ }
189
+ }
190
+
191
+ // =============================================================================
192
+ // Command Registry
193
+ // =============================================================================
194
+
195
+ /**
196
+ * All registered slash commands for authentication
197
+ * @deprecated Use enhancedAuthCommands for new code
198
+ */
199
+ export const authSlashCommands: SlashCommand[] = [
200
+ {
201
+ name: "credentials",
202
+ aliases: ["creds", "keys"],
203
+ description: "Show credential status",
204
+ usage: "/credentials [provider]",
205
+ handler: handleCredentials,
206
+ },
207
+ ];
208
+
209
+ // =============================================================================
210
+ // T034: Enhanced Auth Commands (New Interface)
211
+ // =============================================================================
212
+
213
+ /**
214
+ * Credentials command with enhanced interface
215
+ *
216
+ * Displays available stores and credential status.
217
+ *
218
+ * @example
219
+ * ```
220
+ * /credentials # Show all credentials
221
+ * /credentials anthropic # Show specific provider
222
+ * ```
223
+ */
224
+ export const credentialsCommand: EnhancedSlashCommand = {
225
+ name: "credentials",
226
+ description: "Show credential status and available stores",
227
+ kind: "builtin",
228
+ category: "auth",
229
+ aliases: ["creds", "keys"],
230
+ positionalArgs: [
231
+ {
232
+ name: "provider",
233
+ type: "string",
234
+ description: "Filter by provider name",
235
+ required: false,
236
+ },
237
+ ],
238
+ namedArgs: [],
239
+ examples: ["/credentials", "/credentials anthropic", "/creds"],
240
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
241
+ const filterProvider = ctx.parsedArgs.positional[0] as string | undefined;
242
+ const normalizedFilter = filterProvider?.toLowerCase();
243
+
244
+ try {
245
+ // Get store availability
246
+ const availability = await ctx.credentials.getStoreAvailability();
247
+
248
+ // List credentials
249
+ const listResult = await ctx.credentials.list(normalizedFilter);
250
+
251
+ if (!listResult.ok) {
252
+ return error(
253
+ "INTERNAL_ERROR",
254
+ `❌ Failed to list credentials: ${listResult.error.message}`
255
+ );
256
+ }
257
+
258
+ const credentials = listResult.value;
259
+
260
+ // Build message
261
+ const lines: string[] = [];
262
+ lines.push("🔐 Credential Status");
263
+ lines.push("━".repeat(40));
264
+
265
+ // Store availability
266
+ lines.push("\n📦 Storage Backends:");
267
+ for (const [store, available] of Object.entries(availability)) {
268
+ lines.push(` ${available ? "✓" : "✗"} ${store}`);
269
+ }
270
+
271
+ // Credentials
272
+ lines.push("\n🔑 Credentials:");
273
+ if (credentials.length === 0) {
274
+ if (normalizedFilter) {
275
+ lines.push(` No credential found for ${normalizedFilter}`);
276
+ } else {
277
+ lines.push(" No credentials stored");
278
+ lines.push(" Use /auth set <provider> to add one");
279
+ }
280
+ } else {
281
+ for (const cred of credentials) {
282
+ const maskedValue = cred.maskedHint ?? "***";
283
+ lines.push(` • ${cred.provider} (${cred.source}): ${maskedValue} [${cred.type}]`);
284
+ }
285
+ }
286
+
287
+ lines.push("━".repeat(40));
288
+
289
+ return success(lines.join("\n"), {
290
+ availability,
291
+ credentials: credentials.map((c: CredentialRef) => ({
292
+ provider: c.provider,
293
+ source: c.source,
294
+ type: c.type,
295
+ maskedHint: c.maskedHint,
296
+ })),
297
+ });
298
+ } catch (err) {
299
+ return error(
300
+ "INTERNAL_ERROR",
301
+ `❌ Error: ${err instanceof Error ? err.message : String(err)}`
302
+ );
303
+ }
304
+ },
305
+ };
306
+
307
+ // =============================================================================
308
+ // T035: Unified Auth Command
309
+ // =============================================================================
310
+
311
+ /**
312
+ * Unified auth command with subcommands
313
+ *
314
+ * Provides a single entry point for all authentication operations:
315
+ * - /auth (or /auth status) - Show authentication status
316
+ * - /auth set [provider] - Add or update API credential
317
+ * - /auth clear [provider] - Remove credential
318
+ *
319
+ * @example
320
+ * ```
321
+ * /auth # Show current auth status
322
+ * /auth status # Same as /auth
323
+ * /auth set anthropic # Add/update credential for anthropic
324
+ * /auth clear openai # Remove credential for openai
325
+ * ```
326
+ */
327
+ export const authCommand: EnhancedSlashCommand = {
328
+ name: "auth",
329
+ description: "Manage API credentials (status, set, clear)",
330
+ kind: "builtin",
331
+ category: "auth",
332
+ aliases: [],
333
+ subcommands: [
334
+ {
335
+ name: "status",
336
+ description: "Show current authentication status (default)",
337
+ aliases: ["st", "list"],
338
+ },
339
+ {
340
+ name: "set",
341
+ description: "Add or update API credential for a provider",
342
+ aliases: ["add", "login"],
343
+ },
344
+ {
345
+ name: "clear",
346
+ description: "Remove credential for a provider",
347
+ aliases: ["remove", "delete", "logout"],
348
+ },
349
+ ],
350
+ positionalArgs: [
351
+ {
352
+ name: "subcommand",
353
+ type: "string",
354
+ description: "Subcommand: status, set, clear (default: status)",
355
+ required: false,
356
+ },
357
+ {
358
+ name: "provider",
359
+ type: "string",
360
+ description: "Provider name (e.g., anthropic, openai)",
361
+ required: false,
362
+ },
363
+ ],
364
+ namedArgs: [
365
+ {
366
+ name: "store",
367
+ shorthand: "s",
368
+ type: "string",
369
+ description: "Credential store to use (keychain, encrypted-file, env)",
370
+ required: false,
371
+ default: "keychain",
372
+ },
373
+ {
374
+ name: "force",
375
+ shorthand: "f",
376
+ type: "boolean",
377
+ description: "Skip confirmation prompt for clear",
378
+ required: false,
379
+ default: false,
380
+ },
381
+ ],
382
+ examples: [
383
+ "/auth",
384
+ "/auth status",
385
+ "/auth set anthropic",
386
+ "/auth set openai --store keychain",
387
+ "/auth clear anthropic",
388
+ "/auth clear openai --force",
389
+ ],
390
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
391
+ const args = ctx.parsedArgs.positional;
392
+ const subcommand = (args[0] as string | undefined)?.toLowerCase() ?? "status";
393
+ const provider =
394
+ (args[1] as string | undefined) ??
395
+ // If subcommand is a provider name (not a known subcommand), treat it as provider
396
+ (![
397
+ "status",
398
+ "st",
399
+ "list",
400
+ "set",
401
+ "add",
402
+ "login",
403
+ "clear",
404
+ "remove",
405
+ "delete",
406
+ "logout",
407
+ ].includes(subcommand)
408
+ ? subcommand
409
+ : undefined);
410
+ const store = ctx.parsedArgs.named.store as string | undefined;
411
+ const force = ctx.parsedArgs.named.force === true;
412
+
413
+ // Determine actual subcommand (handle case where provider was passed as first arg)
414
+ const actualSubcommand = [
415
+ "status",
416
+ "st",
417
+ "list",
418
+ "set",
419
+ "add",
420
+ "login",
421
+ "clear",
422
+ "remove",
423
+ "delete",
424
+ "logout",
425
+ ].includes(subcommand)
426
+ ? subcommand
427
+ : "status";
428
+
429
+ // Route to appropriate handler
430
+ switch (actualSubcommand) {
431
+ case "set":
432
+ case "add":
433
+ case "login":
434
+ return authSet(ctx.credentials, provider ?? ctx.session.provider, store);
435
+
436
+ case "clear":
437
+ case "remove":
438
+ case "delete":
439
+ case "logout":
440
+ return authClear(ctx.credentials, provider ?? ctx.session.provider, force);
441
+ default:
442
+ return authStatus(ctx.credentials, provider);
443
+ }
444
+ },
445
+ };
446
+
447
+ /**
448
+ * /auth status - Show authentication status
449
+ */
450
+ async function authStatus(
451
+ credentials: CredentialManager,
452
+ filterProvider?: string
453
+ ): Promise<CommandResult> {
454
+ const normalizedFilter = filterProvider?.toLowerCase();
455
+
456
+ try {
457
+ // Get store availability
458
+ const availability = await credentials.getStoreAvailability();
459
+
460
+ // List credentials
461
+ const listResult = await credentials.list(normalizedFilter);
462
+
463
+ if (!listResult.ok) {
464
+ return error("INTERNAL_ERROR", `❌ Failed to list credentials: ${listResult.error.message}`);
465
+ }
466
+
467
+ const credentialsList = listResult.value;
468
+
469
+ // Build message
470
+ const lines: string[] = [];
471
+ lines.push("🔐 Authentication Status");
472
+ lines.push("━".repeat(40));
473
+
474
+ // Store availability
475
+ lines.push("\n📦 Storage Backends:");
476
+ for (const [store, available] of Object.entries(availability)) {
477
+ lines.push(` ${available ? "✓" : "✗"} ${store}`);
478
+ }
479
+
480
+ // Credentials
481
+ lines.push("\n🔑 Configured Providers:");
482
+ if (credentialsList.length === 0) {
483
+ if (normalizedFilter) {
484
+ lines.push(` No credential found for ${normalizedFilter}`);
485
+ } else {
486
+ lines.push(" No credentials stored");
487
+ lines.push(" Use /auth set <provider> to add one");
488
+ }
489
+ } else {
490
+ for (const cred of credentialsList) {
491
+ const maskedValue = cred.maskedHint ?? "***";
492
+ lines.push(` • ${cred.provider} (${cred.source}): ${maskedValue} [${cred.type}]`);
493
+ }
494
+ }
495
+
496
+ lines.push(`\n${"━".repeat(40)}`);
497
+ lines.push("💡 Commands: /auth set <provider> | /auth clear <provider>");
498
+
499
+ return success(lines.join("\n"), {
500
+ availability,
501
+ credentials: credentialsList.map((c: CredentialRef) => ({
502
+ provider: c.provider,
503
+ source: c.source,
504
+ type: c.type,
505
+ maskedHint: c.maskedHint,
506
+ })),
507
+ });
508
+ } catch (err) {
509
+ return error("INTERNAL_ERROR", `❌ Error: ${err instanceof Error ? err.message : String(err)}`);
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Provider-specific API key format hints
515
+ */
516
+ const PROVIDER_KEY_HINTS: Record<
517
+ string,
518
+ { formatHint: string; helpText: string; documentationUrl?: string }
519
+ > = {
520
+ anthropic: {
521
+ formatHint: "sk-ant-api03-...",
522
+ helpText: "Your Anthropic key starts with sk-ant-",
523
+ documentationUrl: "https://console.anthropic.com/settings/keys",
524
+ },
525
+ openai: {
526
+ formatHint: "sk-proj-...",
527
+ helpText: "Your OpenAI key starts with sk-proj- or sk-",
528
+ documentationUrl: "https://platform.openai.com/api-keys",
529
+ },
530
+ google: {
531
+ formatHint: "AIza...",
532
+ helpText: "Your Google AI key starts with AIza",
533
+ documentationUrl: "https://aistudio.google.com/apikey",
534
+ },
535
+ gemini: {
536
+ formatHint: "AIza...",
537
+ helpText: "Your Gemini key starts with AIza",
538
+ documentationUrl: "https://aistudio.google.com/apikey",
539
+ },
540
+ bedrock: {
541
+ formatHint: "AKIA...",
542
+ helpText: "Enter your AWS access key ID",
543
+ documentationUrl: "https://docs.aws.amazon.com/bedrock/latest/userguide/setting-up.html",
544
+ },
545
+ cohere: {
546
+ formatHint: "...",
547
+ helpText: "Your Cohere API key",
548
+ documentationUrl: "https://dashboard.cohere.com/api-keys",
549
+ },
550
+ mistral: {
551
+ formatHint: "...",
552
+ helpText: "Your Mistral API key",
553
+ documentationUrl: "https://console.mistral.ai/api-keys",
554
+ },
555
+ groq: {
556
+ formatHint: "gsk_...",
557
+ helpText: "Your Groq key starts with gsk_",
558
+ documentationUrl: "https://console.groq.com/keys",
559
+ },
560
+ fireworks: {
561
+ formatHint: "fw_...",
562
+ helpText: "Your Fireworks key starts with fw_",
563
+ documentationUrl: "https://fireworks.ai/account/api-keys",
564
+ },
565
+ together: {
566
+ formatHint: "...",
567
+ helpText: "Your Together AI API key",
568
+ documentationUrl: "https://api.together.xyz/settings/api-keys",
569
+ },
570
+ perplexity: {
571
+ formatHint: "pplx-...",
572
+ helpText: "Your Perplexity key starts with pplx-",
573
+ documentationUrl: "https://www.perplexity.ai/settings/api",
574
+ },
575
+ deepseek: {
576
+ formatHint: "sk-...",
577
+ helpText: "Your DeepSeek API key",
578
+ documentationUrl: "https://platform.deepseek.com/api_keys",
579
+ },
580
+ openrouter: {
581
+ formatHint: "sk-or-...",
582
+ helpText: "Your OpenRouter key starts with sk-or-",
583
+ documentationUrl: "https://openrouter.ai/keys",
584
+ },
585
+ ollama: {
586
+ formatHint: "(optional)",
587
+ helpText: "Ollama typically runs locally without an API key",
588
+ },
589
+ };
590
+
591
+ /**
592
+ * Get provider-specific hints, with fallback for unknown providers
593
+ */
594
+ function getProviderHints(provider: string): {
595
+ formatHint: string;
596
+ helpText: string;
597
+ documentationUrl?: string;
598
+ } {
599
+ return (
600
+ PROVIDER_KEY_HINTS[provider] ?? {
601
+ formatHint: "...",
602
+ helpText: `Enter your ${provider} API key`,
603
+ }
604
+ );
605
+ }
606
+
607
+ /**
608
+ * /auth set - Add or update API credential
609
+ */
610
+ async function authSet(
611
+ credentials: CredentialManager,
612
+ provider: string | undefined,
613
+ store?: string
614
+ ): Promise<CommandResult> {
615
+ if (!provider) {
616
+ return error(
617
+ "MISSING_ARGUMENT",
618
+ "❌ Provider required. Usage: /auth set <provider>\n Example: /auth set anthropic"
619
+ );
620
+ }
621
+
622
+ const normalizedProvider = provider.toLowerCase();
623
+
624
+ // Check if credential already exists
625
+ const existsResult = await credentials.exists(normalizedProvider);
626
+ const alreadyExists = existsResult.ok && existsResult.value;
627
+
628
+ // Get provider-specific hints
629
+ const hints = getProviderHints(normalizedProvider);
630
+
631
+ // Capitalize provider name for display
632
+ const displayName = normalizedProvider.charAt(0).toUpperCase() + normalizedProvider.slice(1);
633
+
634
+ const title = alreadyExists
635
+ ? `🔐 Update API Key for ${displayName}`
636
+ : `🔐 Set API Key for ${displayName}`;
637
+
638
+ const promptMessage = `${displayName} API Key:`;
639
+
640
+ return interactive({
641
+ inputType: "password",
642
+ message: promptMessage,
643
+ placeholder: hints.formatHint,
644
+ provider: normalizedProvider,
645
+ title,
646
+ helpText: hints.helpText,
647
+ formatHint: hints.formatHint,
648
+ documentationUrl: hints.documentationUrl,
649
+ handler: async (value: string): Promise<CommandResult> => {
650
+ if (!value.trim()) {
651
+ return error("INVALID_ARGUMENT", "❌ API key cannot be empty");
652
+ }
653
+
654
+ try {
655
+ const result = await credentials.store(
656
+ {
657
+ provider: normalizedProvider,
658
+ type: "api_key",
659
+ value: value.trim(),
660
+ metadata: {
661
+ label: `${normalizedProvider} API Key`,
662
+ },
663
+ },
664
+ store as "keychain" | "file" | undefined
665
+ );
666
+
667
+ if (!result.ok) {
668
+ return error("INTERNAL_ERROR", `❌ Failed to save credential: ${result.error.message}`);
669
+ }
670
+
671
+ return success(`✅ Credential for ${normalizedProvider} saved to ${result.value.source}`, {
672
+ provider: normalizedProvider,
673
+ source: result.value.source,
674
+ });
675
+ } catch (err) {
676
+ return error(
677
+ "INTERNAL_ERROR",
678
+ `❌ Error: ${err instanceof Error ? err.message : String(err)}`
679
+ );
680
+ }
681
+ },
682
+ onCancel: () => ({
683
+ kind: "error" as const,
684
+ code: "COMMAND_ABORTED" as const,
685
+ message: "Credential setup cancelled",
686
+ }),
687
+ });
688
+ }
689
+
690
+ /**
691
+ * /auth clear - Remove credential
692
+ */
693
+ async function authClear(
694
+ credentials: CredentialManager,
695
+ provider: string | undefined,
696
+ force: boolean
697
+ ): Promise<CommandResult> {
698
+ if (!provider) {
699
+ return error(
700
+ "MISSING_ARGUMENT",
701
+ "❌ Provider required. Usage: /auth clear <provider>\n Example: /auth clear anthropic"
702
+ );
703
+ }
704
+
705
+ const normalizedProvider = provider.toLowerCase();
706
+
707
+ // Check if credential exists
708
+ const existsResult = await credentials.exists(normalizedProvider);
709
+ if (!existsResult.ok || !existsResult.value) {
710
+ return error("CREDENTIAL_NOT_FOUND", `⚠️ No credential found for ${normalizedProvider}`);
711
+ }
712
+
713
+ // If not forced, return confirmation prompt
714
+ if (!force) {
715
+ return interactive({
716
+ inputType: "confirm",
717
+ message: `Are you sure you want to remove credential for ${normalizedProvider}?`,
718
+ handler: async (value: string): Promise<CommandResult> => {
719
+ if (value.toLowerCase() !== "yes" && value.toLowerCase() !== "y") {
720
+ return error("COMMAND_ABORTED", "Credential removal cancelled");
721
+ }
722
+
723
+ return performAuthClear(credentials, normalizedProvider);
724
+ },
725
+ onCancel: () => ({
726
+ kind: "error" as const,
727
+ code: "COMMAND_ABORTED" as const,
728
+ message: "Credential removal cancelled",
729
+ }),
730
+ });
731
+ }
732
+
733
+ // Forced clear - delete immediately
734
+ return performAuthClear(credentials, normalizedProvider);
735
+ }
736
+
737
+ /**
738
+ * Perform the actual credential clear operation
739
+ */
740
+ async function performAuthClear(
741
+ credentials: CredentialManager,
742
+ provider: string
743
+ ): Promise<CommandResult> {
744
+ try {
745
+ const result = await credentials.delete(provider);
746
+
747
+ if (!result.ok) {
748
+ return error("INTERNAL_ERROR", `❌ Failed to remove credential: ${result.error.message}`);
749
+ }
750
+
751
+ if (result.value === 0) {
752
+ return error("CREDENTIAL_NOT_FOUND", `⚠️ No credential found for ${provider}`);
753
+ }
754
+
755
+ return success(`✅ Credential for ${provider} removed from ${result.value} store(s)`, {
756
+ provider,
757
+ deletedCount: result.value,
758
+ });
759
+ } catch (err) {
760
+ return error("INTERNAL_ERROR", `❌ Error: ${err instanceof Error ? err.message : String(err)}`);
761
+ }
762
+ }
763
+
764
+ /**
765
+ * All enhanced auth commands using the new SlashCommand interface
766
+ */
767
+ export const enhancedAuthCommands: EnhancedSlashCommand[] = [authCommand, credentialsCommand];
768
+
769
+ // =============================================================================
770
+ // Command Dispatcher
771
+ // =============================================================================
772
+
773
+ /**
774
+ * Check if input is a slash command
775
+ */
776
+ export function isSlashCommand(input: string): boolean {
777
+ return input.trim().startsWith("/");
778
+ }
779
+
780
+ /**
781
+ * Parse slash command input
782
+ */
783
+ export function parseSlashCommand(input: string): { command: string; args: string[] } | null {
784
+ const trimmed = input.trim();
785
+ if (!trimmed.startsWith("/")) {
786
+ return null;
787
+ }
788
+
789
+ const parts = trimmed.slice(1).split(/\s+/);
790
+ const command = parts[0]?.toLowerCase() ?? "";
791
+ const args = parts.slice(1);
792
+
793
+ return { command, args };
794
+ }
795
+
796
+ /**
797
+ * Find a slash command by name or alias
798
+ */
799
+ export function findSlashCommand(name: string): SlashCommand | undefined {
800
+ const lowerName = name.toLowerCase();
801
+
802
+ return authSlashCommands.find(
803
+ (cmd) => cmd.name === lowerName || cmd.aliases?.includes(lowerName)
804
+ );
805
+ }
806
+
807
+ /**
808
+ * Execute a slash command
809
+ */
810
+ export async function executeSlashCommand(
811
+ input: string,
812
+ context: Partial<SlashCommandContext> = {}
813
+ ): Promise<SlashCommandResult> {
814
+ const parsed = parseSlashCommand(input);
815
+
816
+ if (!parsed) {
817
+ return {
818
+ success: false,
819
+ message: "Invalid slash command format",
820
+ };
821
+ }
822
+
823
+ const command = findSlashCommand(parsed.command);
824
+
825
+ if (!command) {
826
+ // Check if it's a help request
827
+ if (parsed.command === "help" && parsed.args[0]) {
828
+ const helpCmd = findSlashCommand(parsed.args[0]);
829
+ if (helpCmd) {
830
+ return {
831
+ success: true,
832
+ message: `📖 ${helpCmd.name}\n ${helpCmd.description}\n Usage: ${helpCmd.usage}`,
833
+ };
834
+ }
835
+ }
836
+
837
+ // Unknown command - show available commands
838
+ const available = authSlashCommands.map((c) => `/${c.name}`).join(", ");
839
+ return {
840
+ success: false,
841
+ message: `❓ Unknown command: /${parsed.command}\n Available: ${available}`,
842
+ };
843
+ }
844
+
845
+ // Create credential manager if not provided
846
+ const credentialManager = context.credentialManager ?? (await createCredentialManager());
847
+
848
+ const fullContext: SlashCommandContext = {
849
+ currentProvider: context.currentProvider,
850
+ credentialManager,
851
+ };
852
+
853
+ return command.handler(parsed.args, fullContext);
854
+ }
855
+
856
+ /**
857
+ * Get help for all slash commands
858
+ */
859
+ export function getSlashCommandHelp(): string {
860
+ const lines: string[] = [];
861
+ lines.push("📖 Available Commands:");
862
+ lines.push("━".repeat(40));
863
+
864
+ for (const cmd of authSlashCommands) {
865
+ lines.push(`\n/${cmd.name}`);
866
+ if (cmd.aliases?.length) {
867
+ lines.push(` Aliases: ${cmd.aliases.map((a) => `/${a}`).join(", ")}`);
868
+ }
869
+ lines.push(` ${cmd.description}`);
870
+ lines.push(` Usage: ${cmd.usage}`);
871
+ }
872
+
873
+ lines.push(`\n${"━".repeat(40)}`);
874
+ lines.push("Tip: Use /help <command> for detailed help");
875
+
876
+ return lines.join("\n");
877
+ }
878
+
879
+ // =============================================================================
880
+ // Exports
881
+ // =============================================================================
882
+
883
+ export { handleCredentials };