@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,480 @@
1
+ /**
2
+ * usePersistence Hook
3
+ *
4
+ * React hook for managing session persistence with auto-save,
5
+ * checkpoints, and rollback support. Integrates with @vellum/core
6
+ * PersistenceManager for advanced persistence features.
7
+ *
8
+ * @module tui/hooks/usePersistence
9
+ */
10
+
11
+ import type { PersistenceManager, Session, SessionCheckpoint, StorageManager } from "@vellum/core";
12
+ import { PersistenceManager as PersistenceManagerClass } from "@vellum/core";
13
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
14
+ import {
15
+ createPersistenceBridge,
16
+ type PersistenceBridge,
17
+ type PersistenceBridgeCallbacks,
18
+ } from "../adapters/persistence-bridge.js";
19
+ import {
20
+ type SessionStorage,
21
+ type UseSessionAdapterOptions,
22
+ type UseSessionAdapterReturn,
23
+ useSessionAdapter,
24
+ } from "../adapters/session-adapter.js";
25
+ import { useMessages } from "../context/MessagesContext.js";
26
+
27
+ // =============================================================================
28
+ // Types
29
+ // =============================================================================
30
+
31
+ /**
32
+ * Persistence status for UI display
33
+ */
34
+ export type PersistenceStatus = "idle" | "saving" | "saved" | "error";
35
+
36
+ /**
37
+ * Options for the usePersistence hook
38
+ */
39
+ export interface UsePersistenceOptions {
40
+ /** Session ID for persistence */
41
+ readonly sessionId: string;
42
+ /** Storage implementation for basic persistence */
43
+ readonly storage: SessionStorage;
44
+ /** Whether to enable advanced persistence features */
45
+ readonly enableAdvancedPersistence?: boolean;
46
+ /** Storage manager for advanced persistence (required if enableAdvancedPersistence=true) */
47
+ readonly storageManager?: StorageManager;
48
+ /** PersistenceManager instance (optional, created if storageManager provided) */
49
+ readonly persistenceManager?: PersistenceManager;
50
+ /** Whether to auto-save on message changes */
51
+ readonly autoSave?: boolean;
52
+ /** Debounce delay for auto-save in milliseconds */
53
+ readonly saveDebounceMs?: number;
54
+ /** Whether to auto-load session on mount */
55
+ readonly autoLoad?: boolean;
56
+ /** Callback when save completes */
57
+ readonly onSave?: (session: Session) => void;
58
+ /** Callback when save fails */
59
+ readonly onError?: (error: Error) => void;
60
+ /** Callback when checkpoint is created */
61
+ readonly onCheckpointCreated?: (checkpointId: string) => void;
62
+ /** Callback when rollback completes */
63
+ readonly onRollbackComplete?: (success: boolean) => void;
64
+ }
65
+
66
+ /**
67
+ * Return value of the usePersistence hook
68
+ */
69
+ export interface UsePersistenceReturn extends UseSessionAdapterReturn {
70
+ /** Current persistence status */
71
+ readonly status: PersistenceStatus;
72
+ /** Number of unsaved messages */
73
+ readonly unsavedCount: number;
74
+ /** Whether auto-save is running */
75
+ readonly autoSaveRunning: boolean;
76
+ /** Timestamp of last successful save */
77
+ readonly lastSavedAt: Date | null;
78
+ /** All checkpoints for current session */
79
+ readonly checkpoints: readonly SessionCheckpoint[];
80
+ /** Create a new checkpoint */
81
+ readonly createCheckpoint: (description?: string) => Promise<string | null>;
82
+ /** Rollback to a checkpoint */
83
+ readonly rollbackToCheckpoint: (checkpointId: string) => Promise<boolean>;
84
+ /** Delete a checkpoint */
85
+ readonly deleteCheckpoint: (checkpointId: string) => Promise<boolean>;
86
+ /** Get messages that will be lost on rollback */
87
+ readonly getMessagesToLose: (checkpointId: string) => number;
88
+ /** Whether advanced persistence is enabled */
89
+ readonly isAdvancedEnabled: boolean;
90
+ /** Force an immediate save */
91
+ readonly forceSave: () => Promise<void>;
92
+ /** Start auto-save timer */
93
+ readonly startAutoSave: () => void;
94
+ /** Stop auto-save timer */
95
+ readonly stopAutoSave: () => void;
96
+ }
97
+
98
+ // =============================================================================
99
+ // Hook Implementation
100
+ // =============================================================================
101
+
102
+ /**
103
+ * usePersistence - Hook for session persistence management.
104
+ *
105
+ * Combines basic session adapter functionality with advanced
106
+ * persistence features when PersistenceManager is available.
107
+ *
108
+ * Features:
109
+ * - Basic: Auto-save, manual save, load, clear
110
+ * - Advanced: Checkpoints, rollback, incremental sync
111
+ *
112
+ * @example
113
+ * ```tsx
114
+ * function MyComponent() {
115
+ * const persistence = usePersistence({
116
+ * sessionId: 'session-123',
117
+ * storage: sessionStorage,
118
+ * enableAdvancedPersistence: true,
119
+ * storageManager: storageManager,
120
+ * onSave: (session) => console.log('Saved:', session.metadata.id),
121
+ * });
122
+ *
123
+ * return (
124
+ * <Box>
125
+ * <Text>Status: {persistence.status}</Text>
126
+ * <Text>Unsaved: {persistence.unsavedCount}</Text>
127
+ * <Button onClick={() => persistence.createCheckpoint('Manual save')}>
128
+ * Create Checkpoint
129
+ * </Button>
130
+ * </Box>
131
+ * );
132
+ * }
133
+ * ```
134
+ */
135
+ export function usePersistence(options: UsePersistenceOptions): UsePersistenceReturn {
136
+ const {
137
+ sessionId,
138
+ storage,
139
+ enableAdvancedPersistence = false,
140
+ storageManager,
141
+ persistenceManager: externalPersistenceManager,
142
+ autoSave = true,
143
+ saveDebounceMs = 500,
144
+ autoLoad = true,
145
+ onSave,
146
+ onError,
147
+ onCheckpointCreated,
148
+ onRollbackComplete,
149
+ } = options;
150
+
151
+ // Get messages context
152
+ const { messages, setMessages } = useMessages();
153
+
154
+ // ==========================================================================
155
+ // State
156
+ // ==========================================================================
157
+
158
+ const [status, setStatus] = useState<PersistenceStatus>("idle");
159
+ const [unsavedCount, setUnsavedCount] = useState(0);
160
+ const [autoSaveRunning, setAutoSaveRunning] = useState(false);
161
+ const [lastSavedAt, setLastSavedAt] = useState<Date | null>(null);
162
+ const [checkpoints, setCheckpoints] = useState<readonly SessionCheckpoint[]>([]);
163
+
164
+ // ==========================================================================
165
+ // Refs
166
+ // ==========================================================================
167
+
168
+ const persistenceManagerRef = useRef<PersistenceManager | null>(
169
+ externalPersistenceManager ?? null
170
+ );
171
+ const bridgeRef = useRef<PersistenceBridge | null>(null);
172
+ const previousMessageCountRef = useRef(0);
173
+
174
+ // ==========================================================================
175
+ // Basic Session Adapter
176
+ // ==========================================================================
177
+
178
+ const sessionAdapterOptions: UseSessionAdapterOptions = useMemo(
179
+ () => ({
180
+ sessionId,
181
+ storage,
182
+ autoSave: autoSave && !enableAdvancedPersistence,
183
+ saveDebounceMs,
184
+ autoLoad,
185
+ }),
186
+ [sessionId, storage, autoSave, enableAdvancedPersistence, saveDebounceMs, autoLoad]
187
+ );
188
+
189
+ const sessionAdapter = useSessionAdapter(sessionAdapterOptions);
190
+
191
+ // ==========================================================================
192
+ // Advanced Persistence Setup
193
+ // ==========================================================================
194
+
195
+ const isAdvancedEnabled = enableAdvancedPersistence && storageManager !== undefined;
196
+
197
+ // Create PersistenceManager and Bridge when advanced mode is enabled
198
+ useEffect(() => {
199
+ if (!isAdvancedEnabled || !storageManager) {
200
+ return;
201
+ }
202
+
203
+ // Use external manager or create new one
204
+ const manager =
205
+ externalPersistenceManager ??
206
+ new PersistenceManagerClass(storageManager, {
207
+ autoSaveEnabled: autoSave,
208
+ autoSaveIntervalSecs: Math.ceil(saveDebounceMs / 1000),
209
+ maxUnsavedMessages: 5,
210
+ });
211
+
212
+ persistenceManagerRef.current = manager;
213
+
214
+ // Create bridge with callbacks
215
+ const callbacks: PersistenceBridgeCallbacks = {
216
+ onSave: (session) => {
217
+ setStatus("saved");
218
+ setLastSavedAt(new Date());
219
+ setUnsavedCount(0);
220
+ setCheckpoints(session.checkpoints);
221
+ onSave?.(session);
222
+ },
223
+ onError: (error) => {
224
+ setStatus("error");
225
+ onError?.(error);
226
+ },
227
+ onCheckpointCreated: (checkpointId) => {
228
+ const session = manager.currentSession;
229
+ if (session) {
230
+ setCheckpoints(session.checkpoints);
231
+ }
232
+ onCheckpointCreated?.(checkpointId);
233
+ },
234
+ onRollbackComplete: (success) => {
235
+ if (success) {
236
+ const session = manager.currentSession;
237
+ if (session) {
238
+ setCheckpoints(session.checkpoints);
239
+ }
240
+ }
241
+ onRollbackComplete?.(success);
242
+ },
243
+ };
244
+
245
+ const bridge = createPersistenceBridge({
246
+ persistence: manager,
247
+ callbacks,
248
+ });
249
+
250
+ bridgeRef.current = bridge;
251
+
252
+ // Start auto-save if enabled
253
+ if (autoSave) {
254
+ manager.startAutoSave();
255
+ setAutoSaveRunning(true);
256
+ }
257
+
258
+ return () => {
259
+ bridge.dispose();
260
+ bridgeRef.current = null;
261
+
262
+ // Only dispose manager if we created it
263
+ if (!externalPersistenceManager) {
264
+ manager.dispose();
265
+ }
266
+ persistenceManagerRef.current = null;
267
+ setAutoSaveRunning(false);
268
+ };
269
+ }, [
270
+ isAdvancedEnabled,
271
+ storageManager,
272
+ externalPersistenceManager,
273
+ autoSave,
274
+ saveDebounceMs,
275
+ onSave,
276
+ onError,
277
+ onCheckpointCreated,
278
+ onRollbackComplete,
279
+ ]);
280
+
281
+ // Track unsaved message count
282
+ useEffect(() => {
283
+ if (!isAdvancedEnabled) {
284
+ return;
285
+ }
286
+
287
+ const manager = persistenceManagerRef.current;
288
+ if (!manager) {
289
+ return;
290
+ }
291
+
292
+ const newCount = messages.length - previousMessageCountRef.current;
293
+ if (newCount > 0) {
294
+ setUnsavedCount((prev) => prev + newCount);
295
+ setStatus("idle");
296
+ }
297
+ previousMessageCountRef.current = messages.length;
298
+ }, [messages.length, isAdvancedEnabled]);
299
+
300
+ // ==========================================================================
301
+ // Checkpoint Operations
302
+ // ==========================================================================
303
+
304
+ const createCheckpoint = useCallback(
305
+ async (description?: string): Promise<string | null> => {
306
+ if (!isAdvancedEnabled) {
307
+ return null;
308
+ }
309
+
310
+ const bridge = bridgeRef.current;
311
+ if (!bridge) {
312
+ return null;
313
+ }
314
+
315
+ setStatus("saving");
316
+ const checkpointId = await bridge.createCheckpoint(description);
317
+ if (!checkpointId) {
318
+ setStatus("error");
319
+ }
320
+ return checkpointId;
321
+ },
322
+ [isAdvancedEnabled]
323
+ );
324
+
325
+ const rollbackToCheckpoint = useCallback(
326
+ async (checkpointId: string): Promise<boolean> => {
327
+ if (!isAdvancedEnabled) {
328
+ return false;
329
+ }
330
+
331
+ const bridge = bridgeRef.current;
332
+ const manager = persistenceManagerRef.current;
333
+ if (!bridge || !manager) {
334
+ return false;
335
+ }
336
+
337
+ setStatus("saving");
338
+ const success = await bridge.rollbackToCheckpoint(checkpointId);
339
+
340
+ if (success) {
341
+ // Update UI messages from rolled-back session
342
+ const uiMessages = bridge.loadAsUIMessages();
343
+ setMessages(uiMessages);
344
+ previousMessageCountRef.current = uiMessages.length;
345
+ setStatus("saved");
346
+ } else {
347
+ setStatus("error");
348
+ }
349
+
350
+ return success;
351
+ },
352
+ [isAdvancedEnabled, setMessages]
353
+ );
354
+
355
+ const deleteCheckpoint = useCallback(
356
+ async (checkpointId: string): Promise<boolean> => {
357
+ if (!isAdvancedEnabled) {
358
+ return false;
359
+ }
360
+
361
+ const manager = persistenceManagerRef.current;
362
+ if (!manager) {
363
+ return false;
364
+ }
365
+
366
+ try {
367
+ const success = await manager.deleteCheckpoint(checkpointId);
368
+ if (success) {
369
+ setCheckpoints(manager.getCheckpoints());
370
+ }
371
+ return success;
372
+ } catch {
373
+ return false;
374
+ }
375
+ },
376
+ [isAdvancedEnabled]
377
+ );
378
+
379
+ const getMessagesToLose = useCallback(
380
+ (checkpointId: string): number => {
381
+ const checkpoint = checkpoints.find((cp) => cp.id === checkpointId);
382
+ if (!checkpoint) {
383
+ return 0;
384
+ }
385
+ return messages.length - checkpoint.messageIndex;
386
+ },
387
+ [checkpoints, messages.length]
388
+ );
389
+
390
+ // ==========================================================================
391
+ // Save Operations
392
+ // ==========================================================================
393
+
394
+ const forceSave = useCallback(async (): Promise<void> => {
395
+ if (isAdvancedEnabled) {
396
+ const manager = persistenceManagerRef.current;
397
+ if (!manager) {
398
+ return;
399
+ }
400
+
401
+ setStatus("saving");
402
+ try {
403
+ await manager.save();
404
+ setStatus("saved");
405
+ setLastSavedAt(new Date());
406
+ setUnsavedCount(0);
407
+ } catch {
408
+ setStatus("error");
409
+ }
410
+ } else {
411
+ await sessionAdapter.saveSession();
412
+ }
413
+ }, [isAdvancedEnabled, sessionAdapter]);
414
+
415
+ const startAutoSave = useCallback((): void => {
416
+ if (!isAdvancedEnabled) {
417
+ return;
418
+ }
419
+
420
+ const manager = persistenceManagerRef.current;
421
+ if (manager) {
422
+ manager.startAutoSave();
423
+ setAutoSaveRunning(true);
424
+ }
425
+ }, [isAdvancedEnabled]);
426
+
427
+ const stopAutoSave = useCallback((): void => {
428
+ if (!isAdvancedEnabled) {
429
+ return;
430
+ }
431
+
432
+ const manager = persistenceManagerRef.current;
433
+ if (manager) {
434
+ manager.stopAutoSave();
435
+ setAutoSaveRunning(false);
436
+ }
437
+ }, [isAdvancedEnabled]);
438
+
439
+ // ==========================================================================
440
+ // Return Value
441
+ // ==========================================================================
442
+
443
+ return {
444
+ // Basic session adapter methods
445
+ saveSession: forceSave,
446
+ loadSession: sessionAdapter.loadSession,
447
+ clearSession: sessionAdapter.clearSession,
448
+ isSaving: sessionAdapter.isSaving || status === "saving",
449
+ isLoading: sessionAdapter.isLoading,
450
+ error: sessionAdapter.error,
451
+
452
+ // Advanced persistence state
453
+ status,
454
+ unsavedCount,
455
+ autoSaveRunning,
456
+ lastSavedAt,
457
+ checkpoints,
458
+
459
+ // Checkpoint operations
460
+ createCheckpoint,
461
+ rollbackToCheckpoint,
462
+ deleteCheckpoint,
463
+ getMessagesToLose,
464
+
465
+ // Control methods
466
+ isAdvancedEnabled,
467
+ forceSave,
468
+ startAutoSave,
469
+ stopAutoSave,
470
+ };
471
+ }
472
+
473
+ // =============================================================================
474
+ // Exports
475
+ // =============================================================================
476
+
477
+ export type {
478
+ PersistenceBridge,
479
+ PersistenceBridgeCallbacks,
480
+ } from "../adapters/persistence-bridge.js";
@@ -0,0 +1,225 @@
1
+ /**
2
+ * usePersistenceShortcuts Hook
3
+ *
4
+ * React hook for handling keyboard shortcuts for persistence operations.
5
+ * Provides Ctrl+S, Ctrl+Shift+C, and Ctrl+Z shortcuts for save,
6
+ * checkpoint, and rollback panel operations.
7
+ *
8
+ * @module tui/hooks/usePersistenceShortcuts
9
+ */
10
+
11
+ import { useInput } from "ink";
12
+ import { useCallback, useRef } from "react";
13
+ import type { UsePersistenceReturn } from "./usePersistence.js";
14
+
15
+ // =============================================================================
16
+ // Types
17
+ // =============================================================================
18
+
19
+ /**
20
+ * Options for the usePersistenceShortcuts hook
21
+ */
22
+ export interface UsePersistenceShortcutsOptions {
23
+ /** Persistence hook instance */
24
+ readonly persistence: UsePersistenceReturn | null;
25
+ /** Whether shortcuts are enabled */
26
+ readonly enabled?: boolean;
27
+ /** Callback when save is triggered */
28
+ readonly onSave?: () => void;
29
+ /** Callback when checkpoint is created */
30
+ readonly onCheckpointCreated?: (checkpointId: string) => void;
31
+ /** Callback to open checkpoint panel */
32
+ readonly onOpenCheckpointPanel?: () => void;
33
+ /** Callback when an error occurs */
34
+ readonly onError?: (error: string) => void;
35
+ }
36
+
37
+ /**
38
+ * Return value of the usePersistenceShortcuts hook
39
+ */
40
+ export interface UsePersistenceShortcutsReturn {
41
+ /** Manually trigger a save */
42
+ readonly triggerSave: () => Promise<void>;
43
+ /** Manually trigger checkpoint creation */
44
+ readonly triggerCheckpoint: (description?: string) => Promise<string | null>;
45
+ /** Manually open checkpoint/rollback panel */
46
+ readonly openCheckpointPanel: () => void;
47
+ /** Whether shortcuts are currently active */
48
+ readonly isActive: boolean;
49
+ }
50
+
51
+ // =============================================================================
52
+ // Constants
53
+ // =============================================================================
54
+
55
+ /**
56
+ * Keyboard shortcut descriptions for help display
57
+ */
58
+ export const PERSISTENCE_SHORTCUTS = {
59
+ save: "Ctrl+S",
60
+ checkpoint: "Ctrl+Shift+C",
61
+ rollbackPanel: "Ctrl+Shift+Z",
62
+ } as const;
63
+
64
+ // =============================================================================
65
+ // Hook Implementation
66
+ // =============================================================================
67
+
68
+ /**
69
+ * usePersistenceShortcuts - Hook for persistence keyboard shortcuts.
70
+ *
71
+ * Shortcuts:
72
+ * - Ctrl+S: Save session immediately
73
+ * - Ctrl+Shift+C: Create a new checkpoint
74
+ * - Ctrl+Z: Open checkpoint/rollback panel
75
+ *
76
+ * @example
77
+ * ```tsx
78
+ * function MyComponent() {
79
+ * const persistence = usePersistence({ ... });
80
+ * const [showPanel, setShowPanel] = useState(false);
81
+ *
82
+ * const { isActive } = usePersistenceShortcuts({
83
+ * persistence,
84
+ * onSave: () => console.log('Saved'),
85
+ * onCheckpointCreated: (id) => console.log('Checkpoint:', id),
86
+ * onOpenCheckpointPanel: () => setShowPanel(true),
87
+ * });
88
+ *
89
+ * return (
90
+ * <Box>
91
+ * <Text>Shortcuts active: {isActive ? 'Yes' : 'No'}</Text>
92
+ * </Box>
93
+ * );
94
+ * }
95
+ * ```
96
+ */
97
+ export function usePersistenceShortcuts({
98
+ persistence,
99
+ enabled = true,
100
+ onSave,
101
+ onCheckpointCreated,
102
+ onOpenCheckpointPanel,
103
+ onError,
104
+ }: UsePersistenceShortcutsOptions): UsePersistenceShortcutsReturn {
105
+ // Prevent concurrent operations
106
+ const isOperatingRef = useRef(false);
107
+
108
+ // Check if shortcuts are active
109
+ const isActive = enabled && persistence !== null;
110
+
111
+ /**
112
+ * Trigger a save operation
113
+ */
114
+ const triggerSave = useCallback(async (): Promise<void> => {
115
+ if (!persistence || isOperatingRef.current) {
116
+ return;
117
+ }
118
+
119
+ isOperatingRef.current = true;
120
+
121
+ try {
122
+ await persistence.forceSave();
123
+ onSave?.();
124
+ } catch (error) {
125
+ onError?.(error instanceof Error ? error.message : "Save failed");
126
+ } finally {
127
+ isOperatingRef.current = false;
128
+ }
129
+ }, [persistence, onSave, onError]);
130
+
131
+ /**
132
+ * Trigger checkpoint creation
133
+ */
134
+ const triggerCheckpoint = useCallback(
135
+ async (description?: string): Promise<string | null> => {
136
+ if (!persistence || isOperatingRef.current) {
137
+ return null;
138
+ }
139
+
140
+ if (!persistence.isAdvancedEnabled) {
141
+ onError?.("Checkpoints require advanced persistence mode");
142
+ return null;
143
+ }
144
+
145
+ isOperatingRef.current = true;
146
+
147
+ try {
148
+ const checkpointId = await persistence.createCheckpoint(description);
149
+ if (checkpointId) {
150
+ onCheckpointCreated?.(checkpointId);
151
+ } else {
152
+ onError?.("Failed to create checkpoint");
153
+ }
154
+ return checkpointId;
155
+ } catch (error) {
156
+ onError?.(error instanceof Error ? error.message : "Checkpoint creation failed");
157
+ return null;
158
+ } finally {
159
+ isOperatingRef.current = false;
160
+ }
161
+ },
162
+ [persistence, onCheckpointCreated, onError]
163
+ );
164
+
165
+ /**
166
+ * Open the checkpoint/rollback panel
167
+ */
168
+ const openCheckpointPanel = useCallback((): void => {
169
+ if (!persistence) {
170
+ return;
171
+ }
172
+
173
+ if (!persistence.isAdvancedEnabled) {
174
+ onError?.("Checkpoints require advanced persistence mode");
175
+ return;
176
+ }
177
+
178
+ onOpenCheckpointPanel?.();
179
+ }, [persistence, onOpenCheckpointPanel, onError]);
180
+
181
+ /**
182
+ * Handle keyboard input
183
+ */
184
+ useInput(
185
+ (input, key) => {
186
+ if (!isActive || isOperatingRef.current) {
187
+ return;
188
+ }
189
+
190
+ // Ctrl+S: Save
191
+ if (key.ctrl && input === "s") {
192
+ void triggerSave();
193
+ return;
194
+ }
195
+
196
+ // Ctrl+Shift+C: Create checkpoint
197
+ // Note: In terminal, Ctrl+Shift+C might be captured as just 'C' with ctrl
198
+ if (key.ctrl && key.shift && (input === "c" || input === "C")) {
199
+ void triggerCheckpoint();
200
+ return;
201
+ }
202
+
203
+ // Ctrl+Shift+Z: Open rollback panel
204
+ // Changed from Ctrl+Z to avoid conflict with undo shortcut
205
+ // Only trigger if advanced persistence is enabled
206
+ if (
207
+ key.ctrl &&
208
+ key.shift &&
209
+ (input === "z" || input === "Z") &&
210
+ persistence?.isAdvancedEnabled
211
+ ) {
212
+ openCheckpointPanel();
213
+ return;
214
+ }
215
+ },
216
+ { isActive }
217
+ );
218
+
219
+ return {
220
+ triggerSave,
221
+ triggerCheckpoint,
222
+ openCheckpointPanel,
223
+ isActive,
224
+ };
225
+ }