@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,317 @@
1
+ /**
2
+ * Sandbox Integration
3
+ *
4
+ * Wires the @vellum/sandbox package to shell tool execution,
5
+ * providing secure command execution with platform-specific sandboxing.
6
+ *
7
+ * @module cli/tui/sandbox-integration
8
+ */
9
+
10
+ import {
11
+ detectSandboxBackend,
12
+ type SandboxConfig,
13
+ type SandboxExecutionOptions,
14
+ SandboxExecutor,
15
+ type SandboxResult,
16
+ } from "@vellum/sandbox";
17
+
18
+ import { SANDBOX_DENIED_PATHS, SANDBOX_PERMISSIONS, SANDBOX_RESOURCES } from "./config/index.js";
19
+
20
+ // =============================================================================
21
+ // Types
22
+ // =============================================================================
23
+
24
+ export interface SandboxIntegrationOptions {
25
+ /** Working directory for command execution */
26
+ workingDirectory?: string;
27
+ /** Allow network access in sandbox */
28
+ allowNetwork?: boolean;
29
+ /** Allow file system access */
30
+ allowFileSystem?: boolean;
31
+ /** Maximum execution time in milliseconds */
32
+ timeoutMs?: number;
33
+ /** Maximum output size in bytes */
34
+ maxOutputBytes?: number;
35
+ /** Environment variables */
36
+ environment?: Record<string, string>;
37
+ }
38
+
39
+ export interface SandboxedExecutionResult {
40
+ /** Whether execution succeeded */
41
+ success: boolean;
42
+ /** Exit code (null if terminated) */
43
+ exitCode: number | null;
44
+ /** Standard output */
45
+ stdout: string;
46
+ /** Standard error */
47
+ stderr: string;
48
+ /** Execution duration in milliseconds */
49
+ durationMs: number;
50
+ /** Whether the command was terminated */
51
+ terminated: boolean;
52
+ /** Reason for termination if terminated */
53
+ terminationReason?: string;
54
+ }
55
+
56
+ // =============================================================================
57
+ // Sandbox Integration
58
+ // =============================================================================
59
+
60
+ /**
61
+ * Global sandbox executor instance
62
+ */
63
+ let sandboxExecutor: SandboxExecutor | null = null;
64
+
65
+ /**
66
+ * Initialize the sandbox executor with the given options.
67
+ *
68
+ * @param options - Sandbox configuration options
69
+ * @returns The initialized sandbox executor
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const executor = initializeSandbox({
74
+ * workingDirectory: process.cwd(),
75
+ * allowNetwork: false,
76
+ * allowFileSystem: true,
77
+ * });
78
+ * ```
79
+ */
80
+ export function initializeSandbox(options: SandboxIntegrationOptions = {}): SandboxExecutor {
81
+ const {
82
+ workingDirectory = process.cwd(),
83
+ allowNetwork = SANDBOX_PERMISSIONS.ALLOW_NETWORK,
84
+ allowFileSystem = SANDBOX_PERMISSIONS.ALLOW_FILE_SYSTEM,
85
+ timeoutMs = SANDBOX_RESOURCES.TIMEOUT_MS,
86
+ maxOutputBytes = SANDBOX_RESOURCES.MAX_OUTPUT_BYTES,
87
+ environment = {},
88
+ } = options;
89
+
90
+ const config: SandboxConfig = {
91
+ id: `sandbox-${Date.now()}`,
92
+ strategy: "subprocess",
93
+ workingDir: workingDirectory,
94
+ environment,
95
+ enableAudit: SANDBOX_PERMISSIONS.ENABLE_AUDIT,
96
+ resources: {
97
+ cpuTimeMs: timeoutMs,
98
+ wallTimeMs: timeoutMs + SANDBOX_RESOURCES.WALL_TIME_BUFFER_MS,
99
+ memoryBytes: SANDBOX_RESOURCES.MEMORY_BYTES,
100
+ maxFileDescriptors: SANDBOX_RESOURCES.MAX_FILE_DESCRIPTORS,
101
+ maxProcesses: SANDBOX_RESOURCES.MAX_PROCESSES,
102
+ maxOutputBytes,
103
+ maxFileSizeBytes: SANDBOX_RESOURCES.MAX_FILE_SIZE_BYTES,
104
+ },
105
+ network: {
106
+ allowNetwork,
107
+ allowedHosts: [],
108
+ allowedPorts: [],
109
+ blockDns: !allowNetwork,
110
+ },
111
+ filesystem: {
112
+ rootDir: workingDirectory,
113
+ readOnlyPaths: [],
114
+ readWritePaths: allowFileSystem ? [workingDirectory] : [],
115
+ deniedPaths: [...SANDBOX_DENIED_PATHS],
116
+ useOverlay: SANDBOX_PERMISSIONS.USE_OVERLAY,
117
+ maxDiskUsageBytes: SANDBOX_RESOURCES.MAX_DISK_USAGE_BYTES,
118
+ },
119
+ };
120
+
121
+ const backend = detectSandboxBackend();
122
+ sandboxExecutor = new SandboxExecutor(config, backend);
123
+
124
+ return sandboxExecutor;
125
+ }
126
+
127
+ /**
128
+ * Get the current sandbox executor, initializing if needed.
129
+ *
130
+ * @returns The sandbox executor instance
131
+ */
132
+ export function getSandboxExecutor(): SandboxExecutor {
133
+ if (!sandboxExecutor) {
134
+ sandboxExecutor = initializeSandbox();
135
+ }
136
+ return sandboxExecutor;
137
+ }
138
+
139
+ /**
140
+ * Execute a command in the sandbox.
141
+ *
142
+ * @param command - The command to execute
143
+ * @param args - Command arguments
144
+ * @param options - Execution options
145
+ * @returns The execution result
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const result = await executeSandboxed("ls", ["-la"], {
150
+ * cwd: "/project",
151
+ * timeoutMs: 5000,
152
+ * });
153
+ * if (result.success) {
154
+ * console.log(result.stdout);
155
+ * }
156
+ * ```
157
+ */
158
+ export async function executeSandboxed(
159
+ command: string,
160
+ args: string[] = [],
161
+ options: SandboxExecutionOptions = {}
162
+ ): Promise<SandboxedExecutionResult> {
163
+ const executor = getSandboxExecutor();
164
+
165
+ const result: SandboxResult = await executor.execute(command, args, options);
166
+
167
+ return {
168
+ success: result.exitCode === 0 && !result.terminated,
169
+ exitCode: result.exitCode,
170
+ stdout: result.stdout,
171
+ stderr: result.stderr,
172
+ durationMs: result.durationMs,
173
+ terminated: result.terminated,
174
+ terminationReason: result.terminationReason,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Execute a shell command string in the sandbox.
180
+ *
181
+ * @param shellCommand - The shell command string to execute
182
+ * @param options - Execution options
183
+ * @returns The execution result
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * const result = await executeShellCommand("echo $HOME && pwd", {
188
+ * timeoutMs: 10000,
189
+ * });
190
+ * ```
191
+ */
192
+ export async function executeShellCommand(
193
+ shellCommand: string,
194
+ options: SandboxExecutionOptions = {}
195
+ ): Promise<SandboxedExecutionResult> {
196
+ const shell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
197
+ const shellArgs = process.platform === "win32" ? ["/c", shellCommand] : ["-c", shellCommand];
198
+
199
+ return executeSandboxed(shell, shellArgs, options);
200
+ }
201
+
202
+ /**
203
+ * Cleanup the sandbox executor.
204
+ * Should be called on application shutdown.
205
+ */
206
+ export async function cleanupSandbox(): Promise<void> {
207
+ if (sandboxExecutor) {
208
+ await sandboxExecutor.cleanup();
209
+ sandboxExecutor = null;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Create a sandboxed executor bound to a specific working directory.
215
+ *
216
+ * @param workingDirectory - The working directory
217
+ * @param options - Additional sandbox options
218
+ * @returns An object with bound execute functions
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const sandbox = createBoundSandbox("/project/dir");
223
+ * await sandbox.execute("npm", ["install"]);
224
+ * await sandbox.shell("npm run build && npm test");
225
+ * ```
226
+ */
227
+ export function createBoundSandbox(
228
+ workingDirectory: string,
229
+ options: Omit<SandboxIntegrationOptions, "workingDirectory"> = {}
230
+ ): {
231
+ execute: (
232
+ command: string,
233
+ args?: string[],
234
+ execOptions?: Omit<SandboxExecutionOptions, "cwd">
235
+ ) => Promise<SandboxedExecutionResult>;
236
+ shell: (
237
+ shellCommand: string,
238
+ execOptions?: Omit<SandboxExecutionOptions, "cwd">
239
+ ) => Promise<SandboxedExecutionResult>;
240
+ cleanup: () => Promise<void>;
241
+ } {
242
+ const timeoutMs = options.timeoutMs ?? SANDBOX_RESOURCES.TIMEOUT_MS;
243
+ const allowNetwork = options.allowNetwork ?? SANDBOX_PERMISSIONS.ALLOW_NETWORK;
244
+ const allowFileSystem = options.allowFileSystem ?? SANDBOX_PERMISSIONS.ALLOW_FILE_SYSTEM;
245
+ const maxOutputBytes = options.maxOutputBytes ?? SANDBOX_RESOURCES.MAX_OUTPUT_BYTES;
246
+
247
+ // Create a dedicated executor for this directory
248
+ const config: SandboxConfig = {
249
+ id: `sandbox-bound-${Date.now()}`,
250
+ strategy: "subprocess",
251
+ workingDir: workingDirectory,
252
+ environment: options.environment ?? {},
253
+ enableAudit: SANDBOX_PERMISSIONS.ENABLE_AUDIT,
254
+ resources: {
255
+ cpuTimeMs: timeoutMs,
256
+ wallTimeMs: timeoutMs + SANDBOX_RESOURCES.WALL_TIME_BUFFER_MS,
257
+ memoryBytes: SANDBOX_RESOURCES.MEMORY_BYTES,
258
+ maxFileDescriptors: SANDBOX_RESOURCES.MAX_FILE_DESCRIPTORS,
259
+ maxProcesses: SANDBOX_RESOURCES.MAX_PROCESSES,
260
+ maxOutputBytes,
261
+ maxFileSizeBytes: SANDBOX_RESOURCES.MAX_FILE_SIZE_BYTES,
262
+ },
263
+ network: {
264
+ allowNetwork,
265
+ allowedHosts: [],
266
+ allowedPorts: [],
267
+ blockDns: !allowNetwork,
268
+ },
269
+ filesystem: {
270
+ rootDir: workingDirectory,
271
+ readOnlyPaths: [],
272
+ readWritePaths: allowFileSystem ? [workingDirectory] : [],
273
+ deniedPaths: [],
274
+ useOverlay: SANDBOX_PERMISSIONS.USE_OVERLAY,
275
+ maxDiskUsageBytes: SANDBOX_RESOURCES.MAX_DISK_USAGE_BYTES,
276
+ },
277
+ };
278
+
279
+ const backend = detectSandboxBackend();
280
+ const executor = new SandboxExecutor(config, backend);
281
+
282
+ return {
283
+ execute: async (command, args = [], execOptions = {}) => {
284
+ const result = await executor.execute(command, args, {
285
+ ...execOptions,
286
+ cwd: workingDirectory,
287
+ });
288
+ return {
289
+ success: result.exitCode === 0 && !result.terminated,
290
+ exitCode: result.exitCode,
291
+ stdout: result.stdout,
292
+ stderr: result.stderr,
293
+ durationMs: result.durationMs,
294
+ terminated: result.terminated,
295
+ terminationReason: result.terminationReason,
296
+ };
297
+ },
298
+ shell: async (shellCommand, execOptions = {}) => {
299
+ const shell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
300
+ const shellArgs = process.platform === "win32" ? ["/c", shellCommand] : ["-c", shellCommand];
301
+ const result = await executor.execute(shell, shellArgs, {
302
+ ...execOptions,
303
+ cwd: workingDirectory,
304
+ });
305
+ return {
306
+ success: result.exitCode === 0 && !result.terminated,
307
+ exitCode: result.exitCode,
308
+ stdout: result.stdout,
309
+ stderr: result.stderr,
310
+ durationMs: result.durationMs,
311
+ terminated: result.terminated,
312
+ terminationReason: result.terminationReason,
313
+ };
314
+ },
315
+ cleanup: () => executor.cleanup(),
316
+ };
317
+ }
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Clipboard Service
3
+ *
4
+ * Provides cross-platform clipboard access with error handling
5
+ * for environments that don't support clipboard operations
6
+ * (e.g., WSL, SSH sessions, headless servers).
7
+ *
8
+ * @module tui/services/clipboard
9
+ */
10
+
11
+ import clipboard from "clipboardy";
12
+
13
+ // =============================================================================
14
+ // Types
15
+ // =============================================================================
16
+
17
+ /**
18
+ * Clipboard history entry
19
+ */
20
+ export interface ClipboardHistoryEntry {
21
+ /** The copied text content */
22
+ readonly text: string;
23
+ /** Timestamp when the copy occurred */
24
+ readonly timestamp: number;
25
+ /** Optional label describing the content */
26
+ readonly label?: string;
27
+ }
28
+
29
+ /**
30
+ * Result of a clipboard operation
31
+ */
32
+ export type ClipboardResult<T = void> =
33
+ | { success: true; data: T }
34
+ | { success: false; error: string };
35
+
36
+ // =============================================================================
37
+ // Configuration
38
+ // =============================================================================
39
+
40
+ /** Maximum number of history entries to keep */
41
+ const MAX_HISTORY_SIZE = 10;
42
+
43
+ // =============================================================================
44
+ // Module State
45
+ // =============================================================================
46
+
47
+ /** Clipboard history storage */
48
+ const history: ClipboardHistoryEntry[] = [];
49
+
50
+ /** Cached support status (null = not yet checked) */
51
+ let supportedCache: boolean | null = null;
52
+
53
+ // =============================================================================
54
+ // Support Detection
55
+ // =============================================================================
56
+
57
+ /**
58
+ * Check if clipboard operations are supported in the current environment.
59
+ *
60
+ * Clipboard may not be available in:
61
+ * - WSL without X server or clipboard integration
62
+ * - SSH sessions without forwarding
63
+ * - Headless servers
64
+ * - Docker containers without display
65
+ *
66
+ * @returns true if clipboard operations are likely to work
67
+ */
68
+ export function isSupported(): boolean {
69
+ if (supportedCache !== null) {
70
+ return supportedCache;
71
+ }
72
+
73
+ // Check environment variables that indicate lack of display
74
+ const noDisplay =
75
+ !process.env.DISPLAY &&
76
+ !process.env.WAYLAND_DISPLAY &&
77
+ process.platform !== "win32" &&
78
+ process.platform !== "darwin";
79
+
80
+ // Check for WSL without Windows clipboard access
81
+ const isWsl =
82
+ process.platform === "linux" &&
83
+ (process.env.WSL_DISTRO_NAME !== undefined || process.env.WSL_INTEROP !== undefined);
84
+
85
+ // WSL usually has access to clip.exe/powershell.exe for clipboard
86
+ // so we'll optimistically allow it
87
+ if (isWsl) {
88
+ supportedCache = true;
89
+ return true;
90
+ }
91
+
92
+ // No display on Linux = likely no clipboard
93
+ if (noDisplay) {
94
+ supportedCache = false;
95
+ return false;
96
+ }
97
+
98
+ // Windows and macOS generally support clipboard
99
+ supportedCache = true;
100
+ return true;
101
+ }
102
+
103
+ /**
104
+ * Reset the cached support status.
105
+ * Useful for testing or when environment changes.
106
+ */
107
+ export function resetSupportCache(): void {
108
+ supportedCache = null;
109
+ }
110
+
111
+ // =============================================================================
112
+ // Core Operations
113
+ // =============================================================================
114
+
115
+ /**
116
+ * Copy text to the system clipboard.
117
+ *
118
+ * @param text - The text to copy
119
+ * @param label - Optional label for history (e.g., "code block", "file content")
120
+ * @returns Result indicating success or failure with error message
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const result = await copy("Hello, World!", "greeting");
125
+ * if (result.success) {
126
+ * console.log("Copied successfully");
127
+ * } else {
128
+ * console.error("Copy failed:", result.error);
129
+ * }
130
+ * ```
131
+ */
132
+ export async function copy(text: string, label?: string): Promise<ClipboardResult> {
133
+ if (!text) {
134
+ return { success: false, error: "Nothing to copy" };
135
+ }
136
+
137
+ if (!isSupported()) {
138
+ return {
139
+ success: false,
140
+ error: "Clipboard not supported in this environment",
141
+ };
142
+ }
143
+
144
+ try {
145
+ await clipboard.write(text);
146
+
147
+ // Add to history
148
+ addToHistory(text, label);
149
+
150
+ return { success: true, data: undefined };
151
+ } catch (err) {
152
+ const message = err instanceof Error ? err.message : String(err);
153
+
154
+ // Provide helpful error messages for common issues
155
+ if (message.includes("xsel") || message.includes("xclip")) {
156
+ return {
157
+ success: false,
158
+ error: "Clipboard requires xsel or xclip on Linux. Install with: sudo apt install xclip",
159
+ };
160
+ }
161
+
162
+ return { success: false, error: `Copy failed: ${message}` };
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Synchronous copy operation (best-effort).
168
+ * Falls back gracefully if async fails.
169
+ *
170
+ * @param text - The text to copy
171
+ * @param label - Optional label for history
172
+ * @returns true if copy likely succeeded
173
+ */
174
+ export function copySync(text: string, label?: string): boolean {
175
+ if (!text || !isSupported()) {
176
+ return false;
177
+ }
178
+
179
+ try {
180
+ clipboard.writeSync(text);
181
+ addToHistory(text, label);
182
+ return true;
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Read text from the system clipboard.
190
+ *
191
+ * @returns Result with clipboard content or error
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * const result = await paste();
196
+ * if (result.success) {
197
+ * console.log("Clipboard content:", result.data);
198
+ * }
199
+ * ```
200
+ */
201
+ export async function paste(): Promise<ClipboardResult<string>> {
202
+ if (!isSupported()) {
203
+ return {
204
+ success: false,
205
+ error: "Clipboard not supported in this environment",
206
+ };
207
+ }
208
+
209
+ try {
210
+ const text = await clipboard.read();
211
+ return { success: true, data: text };
212
+ } catch (err) {
213
+ const message = err instanceof Error ? err.message : String(err);
214
+ return { success: false, error: `Paste failed: ${message}` };
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Synchronous paste operation.
220
+ *
221
+ * @returns Clipboard content or empty string on failure
222
+ */
223
+ export function pasteSync(): string {
224
+ if (!isSupported()) {
225
+ return "";
226
+ }
227
+
228
+ try {
229
+ return clipboard.readSync();
230
+ } catch {
231
+ return "";
232
+ }
233
+ }
234
+
235
+ // =============================================================================
236
+ // History Management
237
+ // =============================================================================
238
+
239
+ /**
240
+ * Add an entry to the clipboard history.
241
+ *
242
+ * @param text - The copied text
243
+ * @param label - Optional descriptive label
244
+ */
245
+ function addToHistory(text: string, label?: string): void {
246
+ const entry: ClipboardHistoryEntry = {
247
+ text,
248
+ timestamp: Date.now(),
249
+ label,
250
+ };
251
+
252
+ // Add to front (most recent first)
253
+ history.unshift(entry);
254
+
255
+ // Trim to max size
256
+ if (history.length > MAX_HISTORY_SIZE) {
257
+ history.length = MAX_HISTORY_SIZE;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Get the clipboard history.
263
+ *
264
+ * @param limit - Maximum entries to return (default: all)
265
+ * @returns Array of history entries, most recent first
266
+ */
267
+ export function getHistory(limit?: number): readonly ClipboardHistoryEntry[] {
268
+ if (limit !== undefined && limit > 0) {
269
+ return history.slice(0, limit);
270
+ }
271
+ return [...history];
272
+ }
273
+
274
+ /**
275
+ * Get the most recent clipboard entry.
276
+ *
277
+ * @returns The most recent entry or undefined if history is empty
278
+ */
279
+ export function getLastEntry(): ClipboardHistoryEntry | undefined {
280
+ return history[0];
281
+ }
282
+
283
+ /**
284
+ * Clear the clipboard history.
285
+ */
286
+ export function clearHistory(): void {
287
+ history.length = 0;
288
+ }
289
+
290
+ /**
291
+ * Get the number of entries in history.
292
+ */
293
+ export function getHistorySize(): number {
294
+ return history.length;
295
+ }
296
+
297
+ // =============================================================================
298
+ // Convenience Functions
299
+ // =============================================================================
300
+
301
+ /**
302
+ * Copy text and return a user-friendly status message.
303
+ *
304
+ * @param text - Text to copy
305
+ * @param description - What was copied (for the message)
306
+ * @returns Status message suitable for display
307
+ */
308
+ export async function copyWithMessage(
309
+ text: string,
310
+ description: string
311
+ ): Promise<{ success: boolean; message: string }> {
312
+ const result = await copy(text, description);
313
+
314
+ if (result.success) {
315
+ return {
316
+ success: true,
317
+ message: `📋 Copied ${description} (${text.length} chars)`,
318
+ };
319
+ }
320
+
321
+ return {
322
+ success: false,
323
+ message: `❌ ${result.error}`,
324
+ };
325
+ }
326
+
327
+ // =============================================================================
328
+ // Default Export
329
+ // =============================================================================
330
+
331
+ /**
332
+ * Clipboard service object for convenient access
333
+ */
334
+ export const clipboardService = {
335
+ copy,
336
+ copySync,
337
+ paste,
338
+ pasteSync,
339
+ isSupported,
340
+ getHistory,
341
+ getLastEntry,
342
+ clearHistory,
343
+ getHistorySize,
344
+ copyWithMessage,
345
+ resetSupportCache,
346
+ } as const;
347
+
348
+ export default clipboardService;