@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,356 @@
1
+ /**
2
+ * Line Buffer Hook
3
+ *
4
+ * Pre-wraps messages into line arrays for efficient scroll calculations.
5
+ * Uses a ring buffer to limit memory usage with very long conversations.
6
+ *
7
+ * Key features:
8
+ * - Pre-wraps text at specified terminal width
9
+ * - Caches wrap results by message ID + width
10
+ * - Ring buffer discards oldest entries when maxLines exceeded
11
+ * - O(1) line range queries via index lookup
12
+ *
13
+ * @module tui/hooks/useLineBuffer
14
+ */
15
+
16
+ import { useMemo, useRef } from "react";
17
+ import stringWidth from "string-width";
18
+ import type { Message } from "../context/MessagesContext.js";
19
+
20
+ // =============================================================================
21
+ // Types
22
+ // =============================================================================
23
+
24
+ /**
25
+ * A single entry in the line buffer representing one message's wrapped lines.
26
+ */
27
+ export interface LineBufferEntry {
28
+ /** Original message ID */
29
+ readonly messageId: string;
30
+ /** Pre-wrapped lines (each line fits within wrapWidth) */
31
+ readonly lines: readonly string[];
32
+ /** Width used for wrapping (for cache invalidation) */
33
+ readonly wrapWidth: number;
34
+ }
35
+
36
+ /**
37
+ * State returned by useLineBuffer hook.
38
+ */
39
+ export interface LineBufferState {
40
+ /** All entries in the buffer */
41
+ readonly entries: readonly LineBufferEntry[];
42
+ /** Total line count across all entries */
43
+ readonly totalLines: number;
44
+ /**
45
+ * Get lines in range [start, end).
46
+ * Uses 0-based line indexing across all messages.
47
+ */
48
+ getVisibleLines(start: number, end: number): string[];
49
+ }
50
+
51
+ /**
52
+ * Options for useLineBuffer hook.
53
+ */
54
+ export interface UseLineBufferOptions {
55
+ /** Terminal width for wrapping */
56
+ readonly width: number;
57
+ /** Max lines to keep (ring buffer). Default: 10000 */
58
+ readonly maxLines?: number;
59
+ /** Content padding to subtract from width. Default: 4 */
60
+ readonly contentPadding?: number;
61
+ }
62
+
63
+ // =============================================================================
64
+ // Constants
65
+ // =============================================================================
66
+
67
+ /** Default maximum lines to keep in buffer */
68
+ const DEFAULT_MAX_LINES = 10000;
69
+
70
+ /** Default content padding for wrapping */
71
+ const DEFAULT_CONTENT_PADDING = 4;
72
+
73
+ // =============================================================================
74
+ // Pure Functions
75
+ // =============================================================================
76
+
77
+ /**
78
+ * Wrap a single line of text to fit within a given width.
79
+ * Uses string-width for accurate CJK/Emoji/ANSI handling.
80
+ *
81
+ * @param text - Text to wrap (single logical line, no newlines)
82
+ * @param width - Maximum display width per line
83
+ * @returns Array of wrapped lines
84
+ */
85
+ export function wrapLine(text: string, width: number): string[] {
86
+ const safeWidth = Math.max(1, width);
87
+
88
+ // Empty or whitespace-only lines
89
+ if (!text || text.trim().length === 0) {
90
+ return [text || ""];
91
+ }
92
+
93
+ const result: string[] = [];
94
+ let currentLine = "";
95
+ let currentWidth = 0;
96
+
97
+ // Process character by character for accurate width handling
98
+ // This handles CJK (2-width), emoji, and combining characters correctly
99
+ const chars = [...text]; // Spread to handle multi-byte chars
100
+
101
+ for (const char of chars) {
102
+ const charWidth = stringWidth(char);
103
+
104
+ // Would adding this char exceed the width?
105
+ if (currentWidth + charWidth > safeWidth && currentLine.length > 0) {
106
+ result.push(currentLine);
107
+ currentLine = char;
108
+ currentWidth = charWidth;
109
+ } else {
110
+ currentLine += char;
111
+ currentWidth += charWidth;
112
+ }
113
+ }
114
+
115
+ // Don't forget the last line
116
+ if (currentLine.length > 0) {
117
+ result.push(currentLine);
118
+ }
119
+
120
+ // Handle edge case: empty result
121
+ if (result.length === 0) {
122
+ result.push("");
123
+ }
124
+
125
+ return result;
126
+ }
127
+
128
+ /**
129
+ * Wrap multi-line text (with \n) to fit within width.
130
+ *
131
+ * @param text - Text to wrap (may contain newlines)
132
+ * @param width - Maximum display width per line
133
+ * @returns Array of wrapped lines
134
+ */
135
+ export function wrapText(text: string, width: number): string[] {
136
+ if (!text) {
137
+ return [""];
138
+ }
139
+
140
+ const lines = text.split("\n");
141
+ const result: string[] = [];
142
+
143
+ for (const line of lines) {
144
+ const wrapped = wrapLine(line, width);
145
+ result.push(...wrapped);
146
+ }
147
+
148
+ return result;
149
+ }
150
+
151
+ /**
152
+ * Extract displayable content from a message for line buffering.
153
+ *
154
+ * @param message - Message to extract content from
155
+ * @returns Combined text content (content + thinking + tool info)
156
+ */
157
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Message content extraction with multiple optional fields
158
+ function extractMessageContent(message: Message): string {
159
+ const parts: string[] = [];
160
+
161
+ // Message header line
162
+ const role =
163
+ message.role === "user" ? "You" : message.role === "assistant" ? "Vellum" : message.role;
164
+ parts.push(`${role}:`);
165
+
166
+ // Thinking content (if present)
167
+ if (message.thinking && message.thinking.length > 0) {
168
+ parts.push(" Thinking...");
169
+ // Indent thinking content
170
+ const thinkingLines = message.thinking.split("\n");
171
+ for (const line of thinkingLines) {
172
+ parts.push(` ${line}`);
173
+ }
174
+ }
175
+
176
+ // Main content
177
+ if (message.content) {
178
+ const contentLines = message.content.split("\n");
179
+ for (const line of contentLines) {
180
+ parts.push(` ${line}`);
181
+ }
182
+ } else if (!message.isStreaming) {
183
+ parts.push(" (empty)");
184
+ }
185
+
186
+ // Tool calls (simplified representation)
187
+ if (message.toolCalls && message.toolCalls.length > 0) {
188
+ for (const tool of message.toolCalls) {
189
+ const status = tool.status === "completed" ? "+" : tool.status === "error" ? "x" : "-";
190
+ parts.push(` ${status} ${tool.name}`);
191
+ }
192
+ }
193
+
194
+ return parts.join("\n");
195
+ }
196
+
197
+ /**
198
+ * Create a LineBufferEntry for a message.
199
+ *
200
+ * @param message - Message to wrap
201
+ * @param width - Terminal width
202
+ * @param contentPadding - Padding to subtract from width
203
+ * @returns LineBufferEntry with wrapped lines
204
+ */
205
+ function createEntry(message: Message, width: number, contentPadding: number): LineBufferEntry {
206
+ const contentWidth = Math.max(10, width - contentPadding);
207
+ const content = extractMessageContent(message);
208
+ const lines = wrapText(content, contentWidth);
209
+
210
+ return {
211
+ messageId: message.id,
212
+ lines,
213
+ wrapWidth: width,
214
+ };
215
+ }
216
+
217
+ // =============================================================================
218
+ // Cache Key Generation
219
+ // =============================================================================
220
+
221
+ /**
222
+ * Generate a cache key for a message + width combination.
223
+ */
224
+ function getCacheKey(messageId: string, width: number): string {
225
+ return `${messageId}:${width}`;
226
+ }
227
+
228
+ // =============================================================================
229
+ // Hook Implementation
230
+ // =============================================================================
231
+
232
+ /**
233
+ * useLineBuffer - Pre-wraps messages into line arrays for efficient scrolling.
234
+ *
235
+ * This hook maintains a cache of wrapped lines per message, re-wrapping only
236
+ * when width changes or new messages arrive. Uses a ring buffer to limit
237
+ * memory usage.
238
+ *
239
+ * @example
240
+ * ```tsx
241
+ * const lineBuffer = useLineBuffer(messages, {
242
+ * width: terminalWidth,
243
+ * maxLines: 5000,
244
+ * });
245
+ *
246
+ * // Get visible lines for current scroll position
247
+ * const visibleLines = lineBuffer.getVisibleLines(
248
+ * scrollState.offsetFromBottom,
249
+ * scrollState.offsetFromBottom + viewportHeight
250
+ * );
251
+ * ```
252
+ *
253
+ * @param messages - Array of messages to buffer
254
+ * @param options - Buffer configuration options
255
+ * @returns LineBufferState with entries and query methods
256
+ */
257
+ export function useLineBuffer(
258
+ messages: readonly Message[],
259
+ options: UseLineBufferOptions
260
+ ): LineBufferState {
261
+ const { width, maxLines = DEFAULT_MAX_LINES, contentPadding = DEFAULT_CONTENT_PADDING } = options;
262
+
263
+ // Cache for wrapped entries: Map<cacheKey, LineBufferEntry>
264
+ const cacheRef = useRef<Map<string, LineBufferEntry>>(new Map());
265
+
266
+ // Compute entries with memoization
267
+ const entries = useMemo(() => {
268
+ const cache = cacheRef.current;
269
+ const result: LineBufferEntry[] = [];
270
+ let totalLineCount = 0;
271
+
272
+ // Build entries, using cache when available
273
+ for (const message of messages) {
274
+ const cacheKey = getCacheKey(message.id, width);
275
+ let entry = cache.get(cacheKey);
276
+
277
+ // Cache miss or width changed - recompute
278
+ if (!entry || entry.wrapWidth !== width) {
279
+ entry = createEntry(message, width, contentPadding);
280
+ cache.set(cacheKey, entry);
281
+ }
282
+
283
+ result.push(entry);
284
+ totalLineCount += entry.lines.length;
285
+ }
286
+
287
+ // Ring buffer: trim oldest entries if exceeding maxLines
288
+ while (totalLineCount > maxLines && result.length > 1) {
289
+ const removed = result.shift();
290
+ if (removed) {
291
+ totalLineCount -= removed.lines.length;
292
+ // Clean cache for removed entry
293
+ cache.delete(getCacheKey(removed.messageId, width));
294
+ }
295
+ }
296
+
297
+ // Clean stale cache entries (messages no longer in list)
298
+ const activeKeys = new Set(result.map((e) => getCacheKey(e.messageId, width)));
299
+ for (const key of cache.keys()) {
300
+ if (!activeKeys.has(key)) {
301
+ cache.delete(key);
302
+ }
303
+ }
304
+
305
+ return result;
306
+ }, [messages, width, maxLines, contentPadding]);
307
+
308
+ // Compute total lines
309
+ const totalLines = useMemo(() => {
310
+ return entries.reduce((sum, entry) => sum + entry.lines.length, 0);
311
+ }, [entries]);
312
+
313
+ // Build line index for O(1) range queries
314
+ // lineIndex[i] = { entryIndex, lineOffset } for global line i
315
+ const lineIndex = useMemo(() => {
316
+ const index: Array<{ entryIndex: number; lineOffset: number }> = [];
317
+ let entryIdx = 0;
318
+ for (const entry of entries) {
319
+ for (let lineOffset = 0; lineOffset < entry.lines.length; lineOffset++) {
320
+ index.push({ entryIndex: entryIdx, lineOffset });
321
+ }
322
+ entryIdx++;
323
+ }
324
+ return index;
325
+ }, [entries]);
326
+
327
+ // Get visible lines in range [start, end)
328
+ const getVisibleLines = useMemo(() => {
329
+ return (start: number, end: number): string[] => {
330
+ const result: string[] = [];
331
+ const safeStart = Math.max(0, start);
332
+ const safeEnd = Math.min(totalLines, end);
333
+
334
+ for (let i = safeStart; i < safeEnd; i++) {
335
+ const loc = lineIndex[i];
336
+ if (loc) {
337
+ const entry = entries[loc.entryIndex];
338
+ if (entry) {
339
+ const line = entry.lines[loc.lineOffset];
340
+ if (line !== undefined) {
341
+ result.push(line);
342
+ }
343
+ }
344
+ }
345
+ }
346
+
347
+ return result;
348
+ };
349
+ }, [entries, lineIndex, totalLines]);
350
+
351
+ return {
352
+ entries,
353
+ totalLines,
354
+ getVisibleLines,
355
+ };
356
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * useMentionAutocomplete Hook
3
+ *
4
+ * Manages @ mention autocomplete state and logic.
5
+ * Detects @ patterns in input and provides suggestions for types or values.
6
+ *
7
+ * @module tui/hooks/useMentionAutocomplete
8
+ */
9
+
10
+ import {
11
+ MENTION_PARTIAL_REGEX,
12
+ MENTION_VALUE_PARTIAL_REGEX,
13
+ type MentionType,
14
+ mentionRequiresValue,
15
+ } from "@vellum/shared";
16
+ import { useCallback, useMemo } from "react";
17
+ import type { MentionAutocompleteMode } from "../components/Input/MentionAutocomplete.js";
18
+ import { useFileSuggestions } from "./useFileSuggestions.js";
19
+
20
+ // =============================================================================
21
+ // Types
22
+ // =============================================================================
23
+
24
+ /**
25
+ * State of the mention autocomplete system.
26
+ */
27
+ export interface MentionAutocompleteState {
28
+ /** Whether autocomplete should be visible */
29
+ readonly visible: boolean;
30
+ /** Whether autocomplete should capture keyboard input */
31
+ readonly active: boolean;
32
+ /** Current mode: type selection or value completion */
33
+ readonly mode: MentionAutocompleteMode;
34
+ /** The detected mention type (in value mode) */
35
+ readonly mentionType: MentionType | null;
36
+ /** The partial input to filter against */
37
+ readonly filterText: string;
38
+ /** Start position of the mention in the input */
39
+ readonly mentionStart: number;
40
+ }
41
+
42
+ /**
43
+ * Options for useMentionAutocomplete.
44
+ */
45
+ export interface UseMentionAutocompleteOptions {
46
+ /** Current working directory for file suggestions */
47
+ readonly cwd: string;
48
+ }
49
+
50
+ /**
51
+ * Result of the useMentionAutocomplete hook.
52
+ */
53
+ export interface UseMentionAutocompleteResult {
54
+ /** Current autocomplete state */
55
+ readonly state: MentionAutocompleteState;
56
+ /** File suggestions (for file/folder mentions) */
57
+ readonly fileSuggestions: ReturnType<typeof useFileSuggestions>;
58
+ /** Handle selection of a type or value */
59
+ readonly handleSelect: (value: string, mode: MentionAutocompleteMode) => string;
60
+ /** Get completed input value after selection */
61
+ readonly getCompletedValue: (
62
+ currentValue: string,
63
+ selectedValue: string,
64
+ mode: MentionAutocompleteMode
65
+ ) => string;
66
+ }
67
+
68
+ // =============================================================================
69
+ // Helper Functions
70
+ // =============================================================================
71
+
72
+ /**
73
+ * Detect @ mention pattern in input and determine autocomplete state.
74
+ */
75
+ function detectMentionPattern(value: string): MentionAutocompleteState {
76
+ const defaultState: MentionAutocompleteState = {
77
+ visible: false,
78
+ active: false,
79
+ mode: "type",
80
+ mentionType: null,
81
+ filterText: "",
82
+ mentionStart: -1,
83
+ };
84
+
85
+ if (!value.includes("@")) {
86
+ return defaultState;
87
+ }
88
+
89
+ // Check for @type: pattern (value mode)
90
+ const valueMatch = value.match(MENTION_VALUE_PARTIAL_REGEX);
91
+ if (valueMatch) {
92
+ const mentionType = valueMatch[1] as MentionType;
93
+ const partialValue = valueMatch[2] || "";
94
+ const fullMatch = valueMatch[0];
95
+ const mentionStart = value.lastIndexOf(fullMatch);
96
+
97
+ return {
98
+ visible: true,
99
+ active: true,
100
+ mode: "value",
101
+ mentionType,
102
+ filterText: partialValue,
103
+ mentionStart,
104
+ };
105
+ }
106
+
107
+ // Check for @ or @partial pattern (type mode)
108
+ const typeMatch = value.match(MENTION_PARTIAL_REGEX);
109
+ if (typeMatch) {
110
+ const partialType = typeMatch[1] || "";
111
+ const atIndex = value.lastIndexOf("@");
112
+
113
+ // Check if @ is at start or after whitespace
114
+ if (atIndex === 0 || /\s/.test(value[atIndex - 1] || "")) {
115
+ return {
116
+ visible: true,
117
+ active: true,
118
+ mode: "type",
119
+ mentionType: null,
120
+ filterText: partialType,
121
+ mentionStart: atIndex,
122
+ };
123
+ }
124
+ }
125
+
126
+ return defaultState;
127
+ }
128
+
129
+ // =============================================================================
130
+ // Hook Implementation
131
+ // =============================================================================
132
+
133
+ /**
134
+ * Hook to manage @ mention autocomplete.
135
+ *
136
+ * @param inputValue - Current input value
137
+ * @param options - Configuration options
138
+ * @returns Autocomplete state and handlers
139
+ *
140
+ * @example
141
+ * ```tsx
142
+ * const { state, fileSuggestions, handleSelect } = useMentionAutocomplete(
143
+ * inputValue,
144
+ * { cwd: "/project" }
145
+ * );
146
+ *
147
+ * if (state.visible) {
148
+ * return (
149
+ * <MentionAutocomplete
150
+ * mode={state.mode}
151
+ * input={state.filterText}
152
+ * mentionType={state.mentionType}
153
+ * fileSuggestions={fileSuggestions.suggestions}
154
+ * onSelect={(v, m) => setValue(handleSelect(v, m))}
155
+ * />
156
+ * );
157
+ * }
158
+ * ```
159
+ */
160
+ export function useMentionAutocomplete(
161
+ inputValue: string,
162
+ options: UseMentionAutocompleteOptions
163
+ ): UseMentionAutocompleteResult {
164
+ // Detect mention pattern in input
165
+ const state = useMemo(() => detectMentionPattern(inputValue), [inputValue]);
166
+
167
+ // Get file suggestions when in value mode for file/folder types
168
+ const shouldLoadFiles =
169
+ state.visible &&
170
+ state.mode === "value" &&
171
+ (state.mentionType === "file" || state.mentionType === "folder");
172
+
173
+ const fileSuggestions = useFileSuggestions(shouldLoadFiles ? state.filterText : "", {
174
+ cwd: options.cwd,
175
+ includeFiles: state.mentionType === "file",
176
+ includeDirectories: true, // Always include directories for navigation
177
+ });
178
+
179
+ /**
180
+ * Get the completed input value after a selection.
181
+ */
182
+ const getCompletedValue = useCallback(
183
+ (currentValue: string, selectedValue: string, mode: MentionAutocompleteMode): string => {
184
+ if (state.mentionStart === -1) return currentValue;
185
+
186
+ const beforeMention = currentValue.slice(0, state.mentionStart);
187
+
188
+ if (mode === "type") {
189
+ // Type was selected, build the mention prefix
190
+ const needsValue = mentionRequiresValue(selectedValue as MentionType);
191
+ if (needsValue) {
192
+ // Add colon and prepare for value input
193
+ return `${beforeMention}@${selectedValue}:`;
194
+ }
195
+ // Standalone mention, add space
196
+ return `${beforeMention}@${selectedValue} `;
197
+ }
198
+
199
+ // Value was selected
200
+ if (state.mentionType) {
201
+ // Check if selected value is a directory
202
+ const isDir = fileSuggestions.suggestions.find(
203
+ (s) => s.path === selectedValue
204
+ )?.isDirectory;
205
+
206
+ if (isDir) {
207
+ // Directory selected, allow further navigation
208
+ return `${beforeMention}@${state.mentionType}:${selectedValue}/`;
209
+ }
210
+ // File selected, complete the mention
211
+ return `${beforeMention}@${state.mentionType}:${selectedValue} `;
212
+ }
213
+
214
+ return currentValue;
215
+ },
216
+ [state.mentionStart, state.mentionType, fileSuggestions.suggestions]
217
+ );
218
+
219
+ /**
220
+ * Handle selection and return the new input value.
221
+ */
222
+ const handleSelect = useCallback(
223
+ (selectedValue: string, mode: MentionAutocompleteMode): string => {
224
+ return getCompletedValue(inputValue, selectedValue, mode);
225
+ },
226
+ [inputValue, getCompletedValue]
227
+ );
228
+
229
+ return {
230
+ state,
231
+ fileSuggestions,
232
+ handleSelect,
233
+ getCompletedValue,
234
+ };
235
+ }