@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,562 @@
1
+ /**
2
+ * Resume Command
3
+ *
4
+ * Resumes a previous session by ID or most recent session.
5
+ * Supports interactive session selection with pagination.
6
+ *
7
+ * @module cli/commands/session/resume
8
+ */
9
+
10
+ import { select } from "@inquirer/prompts";
11
+ import type { Session, SessionListService, SessionMetadata, StorageManager } from "@vellum/core";
12
+
13
+ import type { CommandContext, CommandResult, SlashCommand } from "../types.js";
14
+ import { error, pending, success } from "../types.js";
15
+
16
+ // =============================================================================
17
+ // Short ID Constants
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Number of characters in a short ID.
22
+ */
23
+ export const SHORT_ID_LENGTH = 8;
24
+
25
+ /**
26
+ * Number of sessions to show per page in interactive selector.
27
+ */
28
+ export const SESSIONS_PER_PAGE = 10;
29
+
30
+ // =============================================================================
31
+ // Session Selection Utilities
32
+ // =============================================================================
33
+
34
+ /**
35
+ * Options for session selection.
36
+ */
37
+ export interface SessionSelectOptions {
38
+ /** Storage manager for session data */
39
+ storage: StorageManager;
40
+ /** Session list service for queries */
41
+ listService: SessionListService;
42
+ /** Whether to show sessions from all directories */
43
+ showAllDirs?: boolean;
44
+ }
45
+
46
+ /**
47
+ * Format a date as "MM/dd HH:mm".
48
+ *
49
+ * @param date - Date to format
50
+ * @returns Formatted string like "12/30 14:30"
51
+ */
52
+ function formatSessionDate(date: Date): string {
53
+ const month = String(date.getMonth() + 1).padStart(2, "0");
54
+ const day = String(date.getDate()).padStart(2, "0");
55
+ const hours = String(date.getHours()).padStart(2, "0");
56
+ const minutes = String(date.getMinutes()).padStart(2, "0");
57
+ return `${month}/${day} ${hours}:${minutes}`;
58
+ }
59
+
60
+ /**
61
+ * Format a session metadata for display in the selector.
62
+ *
63
+ * @param session - Session metadata
64
+ * @param index - Display index (1-based)
65
+ * @returns Formatted string like "1. 调试API错误 (12/30 14:30) - 15条消息"
66
+ */
67
+ export function formatSessionChoice(session: SessionMetadata, index: number): string {
68
+ const date = formatSessionDate(session.lastActive);
69
+ return `${index}. ${session.title} (${date}) - ${session.messageCount}条消息`;
70
+ }
71
+
72
+ /**
73
+ * Group sessions by working directory.
74
+ *
75
+ * @param sessions - Array of session metadata
76
+ * @returns Map of working directory to sessions
77
+ */
78
+ export function groupSessionsByDirectory(
79
+ sessions: SessionMetadata[]
80
+ ): Map<string, SessionMetadata[]> {
81
+ const grouped = new Map<string, SessionMetadata[]>();
82
+
83
+ for (const session of sessions) {
84
+ const dir = session.workingDirectory;
85
+ const existing = grouped.get(dir);
86
+ if (existing) {
87
+ existing.push(session);
88
+ } else {
89
+ grouped.set(dir, [session]);
90
+ }
91
+ }
92
+
93
+ return grouped;
94
+ }
95
+
96
+ /**
97
+ * Interactively select a session from the list.
98
+ *
99
+ * Shows a paginated list of recent sessions for user selection.
100
+ * Returns null if the user cancels the selection.
101
+ *
102
+ * @param options - Selection options
103
+ * @returns Selected session or null if cancelled
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * const session = await selectSession({ storage, listService });
108
+ * if (session) {
109
+ * console.log("Selected:", session.metadata.title);
110
+ * }
111
+ * ```
112
+ */
113
+ export async function selectSession(options: SessionSelectOptions): Promise<Session | null> {
114
+ const { storage, listService, showAllDirs } = options;
115
+
116
+ // Get recent sessions
117
+ const sessions = await listService.getRecentSessions(SESSIONS_PER_PAGE * 5); // Get more for pagination
118
+
119
+ if (sessions.length === 0) {
120
+ return null;
121
+ }
122
+
123
+ // Build choices for the selector
124
+ interface SessionChoice {
125
+ name: string;
126
+ value: string;
127
+ description?: string;
128
+ }
129
+
130
+ const choices: SessionChoice[] = [];
131
+
132
+ if (showAllDirs) {
133
+ // Group by directory
134
+ const grouped = groupSessionsByDirectory(sessions);
135
+ let globalIndex = 1;
136
+
137
+ for (const [dir, dirSessions] of grouped) {
138
+ // Add separator for each directory
139
+ choices.push({
140
+ name: `── ${dir} ──`,
141
+ value: "__separator__",
142
+ description: `${dirSessions.length} 个会话`,
143
+ });
144
+
145
+ // Add sessions in this directory (up to page size)
146
+ const displaySessions = dirSessions.slice(0, SESSIONS_PER_PAGE);
147
+ for (const session of displaySessions) {
148
+ choices.push({
149
+ name: formatSessionChoice(session, globalIndex),
150
+ value: session.id,
151
+ description: session.summary,
152
+ });
153
+ globalIndex++;
154
+ }
155
+ }
156
+ } else {
157
+ // Flat list with pagination
158
+ const displaySessions = sessions.slice(0, SESSIONS_PER_PAGE);
159
+ displaySessions.forEach((session, idx) => {
160
+ choices.push({
161
+ name: formatSessionChoice(session, idx + 1),
162
+ value: session.id,
163
+ description: session.summary,
164
+ });
165
+ });
166
+ }
167
+
168
+ // Filter out separators for actual selection
169
+ const selectableChoices = choices.filter((c) => c.value !== "__separator__");
170
+
171
+ if (selectableChoices.length === 0) {
172
+ return null;
173
+ }
174
+
175
+ try {
176
+ const selectedId = await select({
177
+ message: "选择要恢复的会话:",
178
+ choices: selectableChoices,
179
+ pageSize: SESSIONS_PER_PAGE,
180
+ });
181
+
182
+ // Load the selected session
183
+ const session = await storage.load(selectedId);
184
+ return session;
185
+ } catch {
186
+ // User cancelled (Ctrl+C)
187
+ return null;
188
+ }
189
+ }
190
+
191
+ // =============================================================================
192
+ // Session Lookup Utilities
193
+ // =============================================================================
194
+
195
+ /**
196
+ * Result of a session lookup operation.
197
+ */
198
+ export interface SessionLookupResult {
199
+ /** Whether the lookup was successful */
200
+ ok: boolean;
201
+ /** The found session (if ok is true) */
202
+ session?: Session;
203
+ /** Error message (if ok is false) */
204
+ error?: string;
205
+ }
206
+
207
+ /**
208
+ * Options for session lookup operations.
209
+ */
210
+ export interface SessionLookupOptions {
211
+ /** Storage manager for session data */
212
+ storage: StorageManager;
213
+ /** Session list service for queries */
214
+ listService: SessionListService;
215
+ /** Whether to search all directories */
216
+ searchAllDirs?: boolean;
217
+ }
218
+
219
+ /**
220
+ * Finds a session by full ID or short ID (first 8 characters).
221
+ *
222
+ * Short IDs are matched against the prefix of full session IDs.
223
+ * If multiple sessions match a short ID, returns an error.
224
+ *
225
+ * @param id - Full session ID or short ID prefix
226
+ * @param options - Lookup options
227
+ * @returns Session lookup result
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * const result = await findSessionById("abc12345", { storage, listService });
232
+ * if (result.ok && result.session) {
233
+ * console.log(result.session.metadata.title);
234
+ * }
235
+ * ```
236
+ */
237
+ export async function findSessionById(
238
+ id: string,
239
+ options: SessionLookupOptions
240
+ ): Promise<SessionLookupResult> {
241
+ const { storage } = options;
242
+
243
+ // Try exact match first
244
+ try {
245
+ const session = await storage.load(id);
246
+ return { ok: true, session };
247
+ } catch {
248
+ // Not found by exact ID, continue to short ID search
249
+ }
250
+
251
+ // If ID is short, search by prefix
252
+ if (id.length <= SHORT_ID_LENGTH) {
253
+ const index = await storage.getIndex();
254
+ const matches: SessionMetadata[] = [];
255
+
256
+ for (const [sessionId, metadata] of index) {
257
+ if (sessionId.toLowerCase().startsWith(id.toLowerCase())) {
258
+ matches.push(metadata);
259
+ }
260
+ }
261
+
262
+ if (matches.length === 0) {
263
+ return { ok: false, error: `未找到会话: ${id}` };
264
+ }
265
+
266
+ if (matches.length > 1) {
267
+ const matchList = matches.slice(0, 5).map((m) => ` ${m.id.slice(0, 8)} - ${m.title}`);
268
+ return {
269
+ ok: false,
270
+ error: `多个会话匹配 "${id}":\n${matchList.join("\n")}\n请使用更长的ID以区分`,
271
+ };
272
+ }
273
+
274
+ // Single match - load the full session
275
+ const firstMatch = matches[0];
276
+ if (!firstMatch) {
277
+ return { ok: false, error: `未找到会话: ${id}` };
278
+ }
279
+ try {
280
+ const session = await storage.load(firstMatch.id);
281
+ return { ok: true, session };
282
+ } catch {
283
+ return { ok: false, error: "会话已损坏,无法恢复" };
284
+ }
285
+ }
286
+
287
+ return { ok: false, error: `未找到会话: ${id}` };
288
+ }
289
+
290
+ /**
291
+ * Gets the most recent session.
292
+ *
293
+ * @param options - Lookup options
294
+ * @returns Session lookup result
295
+ *
296
+ * @example
297
+ * ```typescript
298
+ * const result = await getMostRecentSession({ storage, listService });
299
+ * if (result.ok && result.session) {
300
+ * console.log("Most recent:", result.session.metadata.title);
301
+ * }
302
+ * ```
303
+ */
304
+ export async function getMostRecentSession(
305
+ options: SessionLookupOptions
306
+ ): Promise<SessionLookupResult> {
307
+ const { storage, listService } = options;
308
+
309
+ const recent = await listService.getRecentSessions(1);
310
+
311
+ if (recent.length === 0) {
312
+ return { ok: false, error: "没有可恢复的会话" };
313
+ }
314
+
315
+ const firstRecent = recent[0];
316
+ if (!firstRecent) {
317
+ return { ok: false, error: "没有可恢复的会话" };
318
+ }
319
+ try {
320
+ const session = await storage.load(firstRecent.id);
321
+ return { ok: true, session };
322
+ } catch {
323
+ return { ok: false, error: "会话已损坏,无法恢复" };
324
+ }
325
+ }
326
+
327
+ // =============================================================================
328
+ // Resume Session Event Data
329
+ // =============================================================================
330
+
331
+ /**
332
+ * Event data emitted when a session is resumed.
333
+ */
334
+ export interface ResumeSessionEventData {
335
+ /** The session being resumed */
336
+ session: Session;
337
+ /** Whether --last flag was used */
338
+ usedLastFlag: boolean;
339
+ }
340
+
341
+ // =============================================================================
342
+ // T032: ResumeCommand Definition
343
+ // =============================================================================
344
+
345
+ /**
346
+ * Creates the resume command with injected dependencies.
347
+ *
348
+ * This factory allows the command to be created with specific
349
+ * storage and list service instances, enabling testing and
350
+ * different storage backends.
351
+ *
352
+ * @param storage - Storage manager for session data
353
+ * @param listService - Session list service for queries
354
+ * @returns SlashCommand instance
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * const storage = await StorageManager.create();
359
+ * const listService = new SessionListService(storage);
360
+ * const resumeCmd = createResumeCommand(storage, listService);
361
+ * registry.register(resumeCmd);
362
+ * ```
363
+ */
364
+ export function createResumeCommand(
365
+ storage: StorageManager,
366
+ listService: SessionListService
367
+ ): SlashCommand {
368
+ return {
369
+ name: "resume",
370
+ description: "Resume a previous session",
371
+ kind: "builtin",
372
+ category: "session",
373
+ aliases: ["r", "restore"],
374
+ positionalArgs: [
375
+ {
376
+ name: "session-id",
377
+ type: "string",
378
+ description: "Session ID or short ID (first 8 characters)",
379
+ required: false,
380
+ },
381
+ ],
382
+ namedArgs: [
383
+ {
384
+ name: "last",
385
+ shorthand: "l",
386
+ type: "boolean",
387
+ description: "Resume most recent session",
388
+ required: false,
389
+ default: false,
390
+ },
391
+ {
392
+ name: "all",
393
+ shorthand: "a",
394
+ type: "boolean",
395
+ description: "Show sessions from all directories",
396
+ required: false,
397
+ default: false,
398
+ },
399
+ ],
400
+ examples: [
401
+ "/resume - Interactive session selector",
402
+ "/resume abc12345 - Resume session by short ID",
403
+ "/resume --last - Resume most recent session",
404
+ "/resume -l - Resume most recent session (short form)",
405
+ "/resume --all - Interactive selector with all directories",
406
+ ],
407
+
408
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
409
+ const sessionId = ctx.parsedArgs.positional[0] as string | undefined;
410
+ const useLast = ctx.parsedArgs.named.last as boolean | undefined;
411
+ const searchAllDirs = ctx.parsedArgs.named.all as boolean | undefined;
412
+
413
+ // Cannot use both session ID and --last
414
+ if (sessionId && useLast) {
415
+ return error("INVALID_ARGUMENT", "不能同时指定会话ID和 --last 标志", [
416
+ "/resume <session-id>",
417
+ "/resume --last",
418
+ ]);
419
+ }
420
+
421
+ const lookupOptions: SessionLookupOptions = {
422
+ storage,
423
+ listService,
424
+ searchAllDirs,
425
+ };
426
+
427
+ // Interactive selection: when no session ID and no --last flag
428
+ if (!sessionId && !useLast) {
429
+ return pending({
430
+ message: "正在加载会话列表...",
431
+ showProgress: true,
432
+ promise: (async (): Promise<CommandResult> => {
433
+ const session = await selectSession({
434
+ storage,
435
+ listService,
436
+ showAllDirs: searchAllDirs,
437
+ });
438
+
439
+ if (!session) {
440
+ return error("COMMAND_ABORTED", "已取消选择");
441
+ }
442
+
443
+ // Emit resume event
444
+ const eventData: ResumeSessionEventData = {
445
+ session,
446
+ usedLastFlag: false,
447
+ };
448
+ ctx.emit("session:resume", eventData);
449
+
450
+ return success(`正在恢复会话: ${session.metadata.title}`, {
451
+ session,
452
+ sessionId: session.metadata.id,
453
+ title: session.metadata.title,
454
+ });
455
+ })(),
456
+ });
457
+ }
458
+
459
+ // Use pending result for async operation
460
+ return pending({
461
+ message: useLast ? "正在查找最近的会话..." : `正在查找会话: ${sessionId}...`,
462
+ showProgress: true,
463
+ promise: (async (): Promise<CommandResult> => {
464
+ let result: SessionLookupResult;
465
+
466
+ if (useLast) {
467
+ result = await getMostRecentSession(lookupOptions);
468
+ } else if (sessionId) {
469
+ result = await findSessionById(sessionId, lookupOptions);
470
+ } else {
471
+ // This should never happen due to validation above
472
+ return error("INTERNAL_ERROR", "无效的命令状态");
473
+ }
474
+
475
+ if (!result.ok || !result.session) {
476
+ return error("RESOURCE_NOT_FOUND", result.error ?? "未找到会话");
477
+ }
478
+
479
+ const session = result.session;
480
+
481
+ // Emit resume event
482
+ const eventData: ResumeSessionEventData = {
483
+ session,
484
+ usedLastFlag: !!useLast,
485
+ };
486
+ ctx.emit("session:resume", eventData);
487
+
488
+ // Display appropriate message
489
+ const message = useLast
490
+ ? `正在恢复最近的会话: ${session.metadata.title}`
491
+ : `正在恢复会话: ${session.metadata.title}`;
492
+
493
+ return success(message, {
494
+ session,
495
+ sessionId: session.metadata.id,
496
+ title: session.metadata.title,
497
+ });
498
+ })(),
499
+ });
500
+ },
501
+ };
502
+ }
503
+
504
+ /**
505
+ * Default resume command (requires initialization with storage).
506
+ *
507
+ * This is a placeholder that throws if executed without proper initialization.
508
+ * Use `createResumeCommand` factory for production use.
509
+ *
510
+ * @example
511
+ * ```typescript
512
+ * // For testing only - use createResumeCommand in production
513
+ * registry.register(resumeCommand);
514
+ * ```
515
+ */
516
+ export const resumeCommand: SlashCommand = {
517
+ name: "resume",
518
+ description: "Resume a previous session",
519
+ kind: "builtin",
520
+ category: "session",
521
+ aliases: ["r", "restore"],
522
+ positionalArgs: [
523
+ {
524
+ name: "session-id",
525
+ type: "string",
526
+ description: "Session ID or short ID (first 8 characters)",
527
+ required: false,
528
+ },
529
+ ],
530
+ namedArgs: [
531
+ {
532
+ name: "last",
533
+ shorthand: "l",
534
+ type: "boolean",
535
+ description: "Resume most recent session",
536
+ required: false,
537
+ default: false,
538
+ },
539
+ {
540
+ name: "all",
541
+ shorthand: "a",
542
+ type: "boolean",
543
+ description: "Show sessions from all directories",
544
+ required: false,
545
+ default: false,
546
+ },
547
+ ],
548
+ examples: [
549
+ "/resume - Interactive session selector",
550
+ "/resume abc12345 - Resume session by short ID",
551
+ "/resume --last - Resume most recent session",
552
+ "/resume -l - Resume most recent session (short form)",
553
+ "/resume --all - Interactive selector with all directories",
554
+ ],
555
+
556
+ execute: async (_ctx: CommandContext): Promise<CommandResult> => {
557
+ return error(
558
+ "INTERNAL_ERROR",
559
+ "Resume command not initialized. Use createResumeCommand with storage dependencies."
560
+ );
561
+ },
562
+ };