@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,419 @@
1
+ /**
2
+ * Session Persistence Adapter
3
+ *
4
+ * Provides bidirectional synchronization between TUI MessagesContext
5
+ * and @vellum/core session messages for persistence.
6
+ *
7
+ * @module tui/adapters/session-adapter
8
+ */
9
+
10
+ import type { SessionMessage } from "@vellum/core";
11
+ import { useCallback, useEffect, useRef } from "react";
12
+ import type { Message } from "../context/MessagesContext.js";
13
+ import { useMessages } from "../context/MessagesContext.js";
14
+ import { toSessionMessage, toUIMessages } from "./message-adapter.js";
15
+
16
+ // =============================================================================
17
+ // Types
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Session storage interface for persistence
22
+ *
23
+ * Abstracts the underlying storage mechanism to allow for different
24
+ * implementations (file-based, memory, etc.)
25
+ */
26
+ export interface SessionStorage {
27
+ /**
28
+ * Save messages to the session
29
+ *
30
+ * @param sessionId - Unique session identifier
31
+ * @param messages - Session messages to persist
32
+ */
33
+ save(sessionId: string, messages: readonly SessionMessage[]): Promise<void>;
34
+
35
+ /**
36
+ * Load messages from a session
37
+ *
38
+ * @param sessionId - Unique session identifier
39
+ * @returns Array of session messages, or null if session not found
40
+ */
41
+ load(sessionId: string): Promise<readonly SessionMessage[] | null>;
42
+
43
+ /**
44
+ * Clear all messages from a session
45
+ *
46
+ * @param sessionId - Unique session identifier
47
+ */
48
+ clear(sessionId: string): Promise<void>;
49
+ }
50
+
51
+ /**
52
+ * Options for the useSessionAdapter hook
53
+ */
54
+ export interface UseSessionAdapterOptions {
55
+ /**
56
+ * Session ID for persistence
57
+ */
58
+ sessionId: string;
59
+
60
+ /**
61
+ * Storage implementation for session persistence
62
+ */
63
+ storage: SessionStorage;
64
+
65
+ /**
66
+ * Whether to auto-save on message changes
67
+ * @default true
68
+ */
69
+ autoSave?: boolean;
70
+
71
+ /**
72
+ * Debounce delay for auto-save in milliseconds
73
+ * @default 500
74
+ */
75
+ saveDebounceMs?: number;
76
+
77
+ /**
78
+ * Whether to load session on mount
79
+ * @default true
80
+ */
81
+ autoLoad?: boolean;
82
+ }
83
+
84
+ /**
85
+ * Return value of the useSessionAdapter hook
86
+ */
87
+ export interface UseSessionAdapterReturn {
88
+ /**
89
+ * Manually save current messages to session
90
+ */
91
+ saveSession: () => Promise<void>;
92
+
93
+ /**
94
+ * Load messages from session and update context
95
+ */
96
+ loadSession: () => Promise<void>;
97
+
98
+ /**
99
+ * Clear the session storage
100
+ */
101
+ clearSession: () => Promise<void>;
102
+
103
+ /**
104
+ * Whether a save operation is in progress
105
+ */
106
+ isSaving: boolean;
107
+
108
+ /**
109
+ * Whether a load operation is in progress
110
+ */
111
+ isLoading: boolean;
112
+
113
+ /**
114
+ * Last error that occurred during save/load
115
+ */
116
+ error: Error | null;
117
+ }
118
+
119
+ /**
120
+ * Interface for the Session Adapter (non-hook version)
121
+ */
122
+ export interface SessionAdapter {
123
+ /**
124
+ * Save messages to session storage
125
+ *
126
+ * @param messages - UI messages to persist
127
+ */
128
+ save(messages: readonly Message[]): Promise<void>;
129
+
130
+ /**
131
+ * Load messages from session storage
132
+ *
133
+ * @returns Array of UI messages, or null if session not found
134
+ */
135
+ load(): Promise<readonly Message[] | null>;
136
+
137
+ /**
138
+ * Clear the session storage
139
+ */
140
+ clear(): Promise<void>;
141
+ }
142
+
143
+ // =============================================================================
144
+ // Factory Function
145
+ // =============================================================================
146
+
147
+ /**
148
+ * Create a session adapter for a given session and storage
149
+ *
150
+ * This factory creates a stateless adapter that can be used outside of React
151
+ * components for session persistence operations.
152
+ *
153
+ * @param sessionId - Unique session identifier
154
+ * @param storage - Storage implementation
155
+ * @returns Session adapter interface
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * const storage = createFileSessionStorage('/path/to/sessions');
160
+ * const adapter = createSessionAdapter('session-123', storage);
161
+ *
162
+ * // Save messages
163
+ * await adapter.save(messages);
164
+ *
165
+ * // Load messages
166
+ * const loadedMessages = await adapter.load();
167
+ * ```
168
+ */
169
+ export function createSessionAdapter(sessionId: string, storage: SessionStorage): SessionAdapter {
170
+ return {
171
+ async save(messages: readonly Message[]): Promise<void> {
172
+ const sessionMessages = messages
173
+ .filter((msg): msg is Message => msg.role !== "tool")
174
+ .map((msg) => toSessionMessage(msg));
175
+ await storage.save(sessionId, sessionMessages);
176
+ },
177
+
178
+ async load(): Promise<readonly Message[] | null> {
179
+ const sessionMessages = await storage.load(sessionId);
180
+ if (!sessionMessages) {
181
+ return null;
182
+ }
183
+ return toUIMessages(sessionMessages);
184
+ },
185
+
186
+ async clear(): Promise<void> {
187
+ await storage.clear(sessionId);
188
+ },
189
+ };
190
+ }
191
+
192
+ // =============================================================================
193
+ // Hook Implementation
194
+ // =============================================================================
195
+
196
+ /**
197
+ * Hook that creates a session adapter for persistence
198
+ *
199
+ * Provides automatic synchronization between MessagesContext and
200
+ * session storage, with support for auto-save and auto-load.
201
+ *
202
+ * @param options - Configuration options for the adapter
203
+ * @returns The session adapter interface with save/load/clear methods
204
+ *
205
+ * @example
206
+ * ```tsx
207
+ * function ChatContainer() {
208
+ * const storage = useMemo(() => createFileSessionStorage('/sessions'), []);
209
+ * const { saveSession, loadSession, isSaving, error } = useSessionAdapter({
210
+ * sessionId: 'session-123',
211
+ * storage,
212
+ * autoSave: true,
213
+ * saveDebounceMs: 1000,
214
+ * });
215
+ *
216
+ * // Messages will auto-save on change
217
+ * // Manual save available via saveSession()
218
+ *
219
+ * return (
220
+ * <Box>
221
+ * {isSaving && <Text>Saving...</Text>}
222
+ * {error && <Text color="red">{error.message}</Text>}
223
+ * <MessageList />
224
+ * </Box>
225
+ * );
226
+ * }
227
+ * ```
228
+ */
229
+ export function useSessionAdapter(options: UseSessionAdapterOptions): UseSessionAdapterReturn {
230
+ const { sessionId, storage, autoSave = true, saveDebounceMs = 500, autoLoad = true } = options;
231
+
232
+ // Get messages context
233
+ const { messages, addMessage, clearMessages } = useMessages();
234
+
235
+ // State refs for async operations
236
+ const isSavingRef = useRef(false);
237
+ const isLoadingRef = useRef(false);
238
+ const errorRef = useRef<Error | null>(null);
239
+ const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
240
+
241
+ // Track previous messages for change detection
242
+ const previousMessagesRef = useRef<readonly Message[]>([]);
243
+
244
+ /**
245
+ * Save current messages to session storage
246
+ */
247
+ const saveSession = useCallback(async (): Promise<void> => {
248
+ if (isSavingRef.current) {
249
+ return;
250
+ }
251
+
252
+ isSavingRef.current = true;
253
+ errorRef.current = null;
254
+
255
+ try {
256
+ const adapter = createSessionAdapter(sessionId, storage);
257
+ await adapter.save(messages);
258
+ } catch (err) {
259
+ errorRef.current = err instanceof Error ? err : new Error(String(err));
260
+ } finally {
261
+ isSavingRef.current = false;
262
+ }
263
+ }, [sessionId, storage, messages]);
264
+
265
+ /**
266
+ * Load messages from session storage
267
+ */
268
+ const loadSession = useCallback(async (): Promise<void> => {
269
+ if (isLoadingRef.current) {
270
+ return;
271
+ }
272
+
273
+ isLoadingRef.current = true;
274
+ errorRef.current = null;
275
+
276
+ try {
277
+ const adapter = createSessionAdapter(sessionId, storage);
278
+ const loadedMessages = await adapter.load();
279
+
280
+ if (loadedMessages && loadedMessages.length > 0) {
281
+ // Clear existing messages first
282
+ clearMessages();
283
+
284
+ // Add loaded messages
285
+ for (const msg of loadedMessages) {
286
+ addMessage({
287
+ role: msg.role,
288
+ content: msg.content,
289
+ isStreaming: msg.isStreaming,
290
+ toolCalls: msg.toolCalls,
291
+ });
292
+ }
293
+
294
+ // Update previous messages ref to avoid triggering auto-save
295
+ previousMessagesRef.current = loadedMessages;
296
+ }
297
+ } catch (err) {
298
+ errorRef.current = err instanceof Error ? err : new Error(String(err));
299
+ } finally {
300
+ isLoadingRef.current = false;
301
+ }
302
+ }, [sessionId, storage, clearMessages, addMessage]);
303
+
304
+ /**
305
+ * Clear session storage
306
+ */
307
+ const clearSession = useCallback(async (): Promise<void> => {
308
+ try {
309
+ const adapter = createSessionAdapter(sessionId, storage);
310
+ await adapter.clear();
311
+ } catch (err) {
312
+ errorRef.current = err instanceof Error ? err : new Error(String(err));
313
+ }
314
+ }, [sessionId, storage]);
315
+
316
+ /**
317
+ * Debounced save function
318
+ */
319
+ const debouncedSave = useCallback((): void => {
320
+ // Clear existing timeout
321
+ if (saveTimeoutRef.current) {
322
+ clearTimeout(saveTimeoutRef.current);
323
+ }
324
+
325
+ // Set new timeout
326
+ saveTimeoutRef.current = setTimeout(() => {
327
+ void saveSession();
328
+ saveTimeoutRef.current = null;
329
+ }, saveDebounceMs);
330
+ }, [saveSession, saveDebounceMs]);
331
+
332
+ // Auto-load on mount
333
+ useEffect(() => {
334
+ if (autoLoad) {
335
+ void loadSession();
336
+ }
337
+ }, [autoLoad, loadSession]);
338
+
339
+ // Auto-save on message changes
340
+ useEffect(() => {
341
+ if (!autoSave) {
342
+ return;
343
+ }
344
+
345
+ // Skip if messages haven't changed
346
+ if (messages === previousMessagesRef.current) {
347
+ return;
348
+ }
349
+
350
+ // Skip if this is the initial load (empty to loaded)
351
+ if (previousMessagesRef.current.length === 0 && messages.length > 0 && isLoadingRef.current) {
352
+ previousMessagesRef.current = messages;
353
+ return;
354
+ }
355
+
356
+ // Trigger debounced save
357
+ if (messages.length > 0) {
358
+ debouncedSave();
359
+ }
360
+
361
+ previousMessagesRef.current = messages;
362
+ }, [autoSave, messages, debouncedSave]);
363
+
364
+ // Cleanup timeout on unmount
365
+ useEffect(() => {
366
+ return () => {
367
+ if (saveTimeoutRef.current) {
368
+ clearTimeout(saveTimeoutRef.current);
369
+ }
370
+ };
371
+ }, []);
372
+
373
+ return {
374
+ saveSession,
375
+ loadSession,
376
+ clearSession,
377
+ isSaving: isSavingRef.current,
378
+ isLoading: isLoadingRef.current,
379
+ error: errorRef.current,
380
+ };
381
+ }
382
+
383
+ // =============================================================================
384
+ // Memory Storage Implementation
385
+ // =============================================================================
386
+
387
+ /**
388
+ * In-memory session storage implementation
389
+ *
390
+ * Useful for testing and temporary sessions that don't need
391
+ * to persist across application restarts.
392
+ *
393
+ * @example
394
+ * ```typescript
395
+ * const storage = createMemorySessionStorage();
396
+ * const adapter = createSessionAdapter('session-123', storage);
397
+ *
398
+ * await adapter.save(messages);
399
+ * const loaded = await adapter.load();
400
+ * ```
401
+ */
402
+ export function createMemorySessionStorage(): SessionStorage {
403
+ const sessions = new Map<string, readonly SessionMessage[]>();
404
+
405
+ return {
406
+ async save(sessionId: string, messages: readonly SessionMessage[]): Promise<void> {
407
+ sessions.set(sessionId, [...messages]);
408
+ },
409
+
410
+ async load(sessionId: string): Promise<readonly SessionMessage[] | null> {
411
+ const messages = sessions.get(sessionId);
412
+ return messages ? [...messages] : null;
413
+ },
414
+
415
+ async clear(sessionId: string): Promise<void> {
416
+ sessions.delete(sessionId);
417
+ },
418
+ };
419
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Buffered Stdout for VS Code Terminal
3
+ *
4
+ * Implements Synchronized Output (DEC 2026) to prevent flickering in VS Code's
5
+ * integrated terminal on Windows. Batches multiple writes into a single
6
+ * atomic frame, wrapped in begin/end synchronized update sequences.
7
+ *
8
+ * @module tui/buffered-stdout
9
+ */
10
+
11
+ import fs from "node:fs";
12
+ import { Writable } from "node:stream";
13
+
14
+ // =============================================================================
15
+ // Shared Stdout Reference
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Shared stdout reference for modules that need to write outside Ink.
20
+ * When BufferedStdout is active, this should point to the BufferedStdout instance.
21
+ * Other modules should use getActiveStdout() instead of process.stdout directly.
22
+ */
23
+ let activeStdout: NodeJS.WriteStream = process.stdout;
24
+
25
+ /**
26
+ * Get the active stdout stream.
27
+ * Returns BufferedStdout when active, otherwise process.stdout.
28
+ * Use this instead of process.stdout directly to ensure synchronized output.
29
+ */
30
+ export function getActiveStdout(): NodeJS.WriteStream {
31
+ return activeStdout;
32
+ }
33
+
34
+ /**
35
+ * Set the active stdout stream.
36
+ * Called by App initialization when BufferedStdout is created.
37
+ * @internal
38
+ */
39
+ export function setActiveStdout(stream: NodeJS.WriteStream): void {
40
+ activeStdout = stream;
41
+ }
42
+
43
+ // =============================================================================
44
+ // Constants
45
+ // =============================================================================
46
+
47
+ /** Begin Synchronized Update (DEC 2026) */
48
+ const BSU = "\x1b[?2026h";
49
+
50
+ /** End Synchronized Update (DEC 2026) */
51
+ const ESU = "\x1b[?2026l";
52
+
53
+ /** Hide cursor during frame render */
54
+ const HIDE_CURSOR = "\x1b[?25l";
55
+
56
+ /** Show cursor after frame render */
57
+ const SHOW_CURSOR = "\x1b[?25h";
58
+
59
+ // =============================================================================
60
+ // Environment Detection
61
+ // =============================================================================
62
+
63
+ /**
64
+ * Detects if running inside VS Code integrated terminal.
65
+ */
66
+ function isVsCodeTerminal(): boolean {
67
+ return (
68
+ process.env.TERM_PROGRAM === "vscode" ||
69
+ process.env.VSCODE_INJECTION === "1" ||
70
+ !!process.env.VSCODE_GIT_IPC_HANDLE
71
+ );
72
+ }
73
+
74
+ // =============================================================================
75
+ // Atomic Write Helper
76
+ // =============================================================================
77
+
78
+ /**
79
+ * Writes a string synchronously to stdout (fd=1).
80
+ * This bypasses Node's async buffering for atomic output.
81
+ */
82
+ function atomicWrite(s: string): void {
83
+ fs.writeSync(1, Buffer.from(s, "utf8"));
84
+ }
85
+
86
+ // =============================================================================
87
+ // BufferedStdout Class
88
+ // =============================================================================
89
+
90
+ /**
91
+ * A Writable stream that batches writes and flushes them atomically
92
+ * wrapped in synchronized output sequences.
93
+ *
94
+ * This prevents visual tearing/flickering in terminals that support
95
+ * the DEC 2026 synchronized output feature (VS Code terminal).
96
+ */
97
+ export class BufferedStdout extends Writable {
98
+ private buf = "";
99
+ private scheduled = false;
100
+ private readonly onResize: () => void;
101
+
102
+ constructor() {
103
+ super();
104
+
105
+ // Forward terminal resize events so Ink can re-render on window size changes.
106
+ this.onResize = () => {
107
+ this.emit("resize");
108
+ };
109
+ process.stdout.on("resize", this.onResize);
110
+ }
111
+
112
+ /**
113
+ * Cleanup any listeners. Safe to call multiple times.
114
+ */
115
+ dispose(): void {
116
+ process.stdout.off("resize", this.onResize);
117
+ // Best-effort flush to avoid losing a last frame.
118
+ this.flush();
119
+ }
120
+
121
+ /**
122
+ * Writable _write implementation - buffers chunks and schedules flush.
123
+ */
124
+ _write(
125
+ chunk: Buffer | string,
126
+ _encoding: BufferEncoding,
127
+ callback: (error?: Error | null) => void
128
+ ): void {
129
+ this.buf += chunk.toString("utf8");
130
+
131
+ if (!this.scheduled) {
132
+ this.scheduled = true;
133
+ // Use setImmediate to batch multiple synchronous writes
134
+ setImmediate(() => this.flush());
135
+ }
136
+
137
+ callback();
138
+ }
139
+
140
+ /**
141
+ * Flushes the accumulated buffer as a single atomic frame.
142
+ * Wraps output in synchronized update sequences to prevent flickering.
143
+ */
144
+ flush(): void {
145
+ this.scheduled = false;
146
+
147
+ if (!this.buf) return;
148
+
149
+ const frame = this.buf;
150
+ this.buf = "";
151
+
152
+ // Atomic write with synchronized output wrapping:
153
+ // 1. Hide cursor to prevent cursor flicker
154
+ // 2. Begin synchronized update (terminal holds display)
155
+ // 3. Write the actual frame content
156
+ // 4. End synchronized update (terminal renders atomically)
157
+ // 5. Show cursor again
158
+ atomicWrite(HIDE_CURSOR + BSU + frame + ESU + SHOW_CURSOR);
159
+ }
160
+
161
+ /**
162
+ * Proxy common stdout properties for Ink compatibility.
163
+ */
164
+ get columns(): number {
165
+ return process.stdout.columns ?? 80;
166
+ }
167
+
168
+ get rows(): number {
169
+ return process.stdout.rows ?? 24;
170
+ }
171
+
172
+ get isTTY(): boolean {
173
+ return process.stdout.isTTY ?? false;
174
+ }
175
+
176
+ // Preserve Ink color capability detection.
177
+ // Node's stdout provides these methods; delegate when available.
178
+ hasColors(count?: number): boolean {
179
+ const stdoutWithHasColors = process.stdout as unknown as {
180
+ hasColors?: (count?: number) => boolean;
181
+ };
182
+ return stdoutWithHasColors.hasColors?.(count) ?? false;
183
+ }
184
+
185
+ getColorDepth(env?: NodeJS.ProcessEnv): number {
186
+ const stdoutWithGetColorDepth = process.stdout as unknown as {
187
+ getColorDepth?: (env?: NodeJS.ProcessEnv) => number;
188
+ };
189
+ return stdoutWithGetColorDepth.getColorDepth?.(env) ?? 1;
190
+ }
191
+ }
192
+
193
+ // =============================================================================
194
+ // Factory Function
195
+ // =============================================================================
196
+
197
+ /**
198
+ * Creates a compatible stdout stream for Ink rendering.
199
+ *
200
+ * On Windows + VS Code terminal: Returns a BufferedStdout that implements
201
+ * synchronized output to prevent flickering.
202
+ *
203
+ * On other platforms/terminals: Returns the native process.stdout.
204
+ *
205
+ * @returns A WriteStream compatible with Ink's render() options
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * import { render } from 'ink';
210
+ * import { createCompatStdout } from './buffered-stdout';
211
+ *
212
+ * render(<App />, { stdout: createCompatStdout() });
213
+ * ```
214
+ */
215
+ export function createCompatStdout(): NodeJS.WriteStream {
216
+ // Only use buffered output on Windows + VS Code where flickering is most severe
217
+ if (process.platform === "win32" && isVsCodeTerminal() && (process.stdout.isTTY ?? false)) {
218
+ // Type assertion: BufferedStdout implements write semantics needed by Ink
219
+ // Full WriteStream interface (clearLine, cursorTo, etc.) not required for rendering
220
+ return new BufferedStdout() as unknown as NodeJS.WriteStream;
221
+ }
222
+ return process.stdout;
223
+ }