@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,246 @@
1
+ /**
2
+ * TokenBreakdown Component (Token Counting Fix)
3
+ *
4
+ * Displays granular token breakdown with cumulative vs per-turn stats.
5
+ * Shows: input | output | cache | thinking tokens with turn/total distinction.
6
+ *
7
+ * @module tui/components/StatusBar/TokenBreakdown
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import { useMemo } from "react";
12
+ import { useTheme } from "../../theme/index.js";
13
+
14
+ // =============================================================================
15
+ // Types
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Detailed token usage statistics
20
+ */
21
+ export interface TokenStats {
22
+ /** Number of input tokens */
23
+ readonly inputTokens: number;
24
+ /** Number of output tokens */
25
+ readonly outputTokens: number;
26
+ /** Number of tokens used for thinking/reasoning (if applicable) */
27
+ readonly thinkingTokens?: number;
28
+ /** Number of tokens read from cache (if applicable) */
29
+ readonly cacheReadTokens?: number;
30
+ /** Number of tokens written to cache (if applicable) */
31
+ readonly cacheWriteTokens?: number;
32
+ }
33
+
34
+ /**
35
+ * Props for the TokenBreakdown component.
36
+ */
37
+ export interface TokenBreakdownProps {
38
+ /** Current turn token usage */
39
+ readonly turn?: TokenStats;
40
+ /** Cumulative session token usage */
41
+ readonly total: TokenStats;
42
+ /** Whether to show compact format (default: false) */
43
+ readonly compact?: boolean;
44
+ /** Whether to show turn stats (default: true if turn provided) */
45
+ readonly showTurn?: boolean;
46
+ }
47
+
48
+ // =============================================================================
49
+ // Helper Functions
50
+ // =============================================================================
51
+
52
+ /**
53
+ * Formats a number with K/M suffix for compact display.
54
+ */
55
+ function formatTokenCount(count: number): string {
56
+ if (count < 1000) {
57
+ return count.toString();
58
+ }
59
+ if (count < 1000000) {
60
+ const k = count / 1000;
61
+ return k >= 10 ? `${Math.round(k)}K` : `${k.toFixed(1)}K`;
62
+ }
63
+ const m = count / 1000000;
64
+ return m >= 10 ? `${Math.round(m)}M` : `${m.toFixed(1)}M`;
65
+ }
66
+
67
+ /**
68
+ * Formats a signed number (with + prefix for positive) for turn display.
69
+ */
70
+ function formatTurnCount(count: number): string {
71
+ const formatted = formatTokenCount(count);
72
+ return count > 0 ? `+${formatted}` : formatted;
73
+ }
74
+
75
+ // =============================================================================
76
+ // Main Component
77
+ // =============================================================================
78
+
79
+ /**
80
+ * TokenBreakdown displays granular token usage with turn vs cumulative distinction.
81
+ *
82
+ * Compact format: `in: X | out: Y | cache: Z | think: W`
83
+ * Full format with turn: `Turn: +X in +Y out | Total: A in B out`
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * // Compact cumulative only
88
+ * <TokenBreakdown
89
+ * total={{ inputTokens: 5000, outputTokens: 2000, cacheReadTokens: 500 }}
90
+ * compact
91
+ * />
92
+ * // Output: in: 5K | out: 2K | cache: 500
93
+ *
94
+ * // With turn breakdown
95
+ * <TokenBreakdown
96
+ * turn={{ inputTokens: 500, outputTokens: 200 }}
97
+ * total={{ inputTokens: 5000, outputTokens: 2000 }}
98
+ * />
99
+ * // Output: Turn: +500 in +200 out | Total: 5K in 2K out
100
+ * ```
101
+ */
102
+ export function TokenBreakdown({
103
+ turn,
104
+ total,
105
+ compact = false,
106
+ showTurn = true,
107
+ }: TokenBreakdownProps): React.JSX.Element {
108
+ const { theme } = useTheme();
109
+
110
+ // Memoized formatted values for total
111
+ const formattedTotal = useMemo(
112
+ () => ({
113
+ input: formatTokenCount(total.inputTokens),
114
+ output: formatTokenCount(total.outputTokens),
115
+ thinking: total.thinkingTokens ? formatTokenCount(total.thinkingTokens) : null,
116
+ cache:
117
+ total.cacheReadTokens || total.cacheWriteTokens
118
+ ? formatTokenCount((total.cacheReadTokens ?? 0) + (total.cacheWriteTokens ?? 0))
119
+ : null,
120
+ }),
121
+ [total]
122
+ );
123
+
124
+ // Memoized formatted values for turn (if provided)
125
+ const formattedTurn = useMemo(() => {
126
+ if (!turn) return null;
127
+ return {
128
+ input: formatTurnCount(turn.inputTokens),
129
+ output: formatTurnCount(turn.outputTokens),
130
+ thinking: turn.thinkingTokens ? formatTurnCount(turn.thinkingTokens) : null,
131
+ cache:
132
+ turn.cacheReadTokens || turn.cacheWriteTokens
133
+ ? formatTurnCount((turn.cacheReadTokens ?? 0) + (turn.cacheWriteTokens ?? 0))
134
+ : null,
135
+ };
136
+ }, [turn]);
137
+
138
+ const hasTurn = showTurn && turn && formattedTurn;
139
+
140
+ // Compact format: `in: X | out: Y | cache: Z | think: W`
141
+ if (compact) {
142
+ const parts: React.ReactNode[] = [];
143
+
144
+ parts.push(
145
+ <Text key="in" color={theme.semantic.text.secondary}>
146
+ in: <Text color={theme.colors.info}>{formattedTotal.input}</Text>
147
+ </Text>
148
+ );
149
+
150
+ parts.push(
151
+ <Text key="out" color={theme.semantic.text.secondary}>
152
+ out: <Text color={theme.colors.success}>{formattedTotal.output}</Text>
153
+ </Text>
154
+ );
155
+
156
+ if (formattedTotal.cache) {
157
+ parts.push(
158
+ <Text key="cache" color={theme.semantic.text.secondary}>
159
+ cache: <Text color={theme.semantic.text.muted}>{formattedTotal.cache}</Text>
160
+ </Text>
161
+ );
162
+ }
163
+
164
+ if (formattedTotal.thinking) {
165
+ parts.push(
166
+ <Text key="think" color={theme.semantic.text.secondary}>
167
+ think: <Text color={theme.colors.warning}>{formattedTotal.thinking}</Text>
168
+ </Text>
169
+ );
170
+ }
171
+
172
+ return (
173
+ <Box>
174
+ {parts.map((part, index) => (
175
+ // biome-ignore lint/suspicious/noArrayIndexKey: Parts array is built locally with stable order, not reordered or filtered
176
+ <Text key={`wrapper-${index}`}>
177
+ {index > 0 && <Text color={theme.semantic.border.muted}> │ </Text>}
178
+ {part}
179
+ </Text>
180
+ ))}
181
+ </Box>
182
+ );
183
+ }
184
+
185
+ // Full format with turn distinction
186
+ if (hasTurn) {
187
+ return (
188
+ <Box>
189
+ {/* Turn stats */}
190
+ <Text color={theme.semantic.text.muted}>Turn: </Text>
191
+ <Text color={theme.colors.info}>{formattedTurn.input}</Text>
192
+ <Text color={theme.semantic.text.muted}> in </Text>
193
+ <Text color={theme.colors.success}>{formattedTurn.output}</Text>
194
+ <Text color={theme.semantic.text.muted}> out</Text>
195
+ {formattedTurn.thinking && (
196
+ <>
197
+ <Text color={theme.semantic.text.muted}> </Text>
198
+ <Text color={theme.colors.warning}>{formattedTurn.thinking}</Text>
199
+ <Text color={theme.semantic.text.muted}> think</Text>
200
+ </>
201
+ )}
202
+
203
+ {/* Separator */}
204
+ <Text color={theme.semantic.border.muted}> │ </Text>
205
+
206
+ {/* Total stats */}
207
+ <Text color={theme.semantic.text.muted}>Total: </Text>
208
+ <Text color={theme.semantic.text.secondary}>{formattedTotal.input}</Text>
209
+ <Text color={theme.semantic.text.muted}> in </Text>
210
+ <Text color={theme.semantic.text.secondary}>{formattedTotal.output}</Text>
211
+ <Text color={theme.semantic.text.muted}> out</Text>
212
+ {formattedTotal.thinking && (
213
+ <>
214
+ <Text color={theme.semantic.text.muted}> </Text>
215
+ <Text color={theme.semantic.text.secondary}>{formattedTotal.thinking}</Text>
216
+ <Text color={theme.semantic.text.muted}> think</Text>
217
+ </>
218
+ )}
219
+ </Box>
220
+ );
221
+ }
222
+
223
+ // Full format without turn (total only)
224
+ return (
225
+ <Box>
226
+ <Text color={theme.semantic.text.secondary}>{formattedTotal.input}</Text>
227
+ <Text color={theme.semantic.text.muted}> in </Text>
228
+ <Text color={theme.semantic.text.secondary}>{formattedTotal.output}</Text>
229
+ <Text color={theme.semantic.text.muted}> out</Text>
230
+ {formattedTotal.cache && (
231
+ <>
232
+ <Text color={theme.semantic.text.muted}> </Text>
233
+ <Text color={theme.semantic.text.muted}>{formattedTotal.cache}</Text>
234
+ <Text color={theme.semantic.text.muted}> cache</Text>
235
+ </>
236
+ )}
237
+ {formattedTotal.thinking && (
238
+ <>
239
+ <Text color={theme.semantic.text.muted}> </Text>
240
+ <Text color={theme.colors.warning}>{formattedTotal.thinking}</Text>
241
+ <Text color={theme.semantic.text.muted}> think</Text>
242
+ </>
243
+ )}
244
+ </Box>
245
+ );
246
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * TokenCounter Component (T036)
3
+ *
4
+ * Displays token usage as a percentage with color-coded warnings.
5
+ * Changes color based on usage thresholds: warning at 80%, error at 95%.
6
+ *
7
+ * @module tui/components/StatusBar/TokenCounter
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import { useMemo } from "react";
12
+ import { useTheme } from "../../theme/index.js";
13
+
14
+ // =============================================================================
15
+ // Types
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Props for the TokenCounter component.
20
+ */
21
+ export interface TokenCounterProps {
22
+ /** Current token count */
23
+ readonly current: number;
24
+ /** Maximum token limit */
25
+ readonly max: number;
26
+ }
27
+
28
+ // =============================================================================
29
+ // Constants
30
+ // =============================================================================
31
+
32
+ /** Warning threshold percentage (80%) */
33
+ const WARNING_THRESHOLD = 80;
34
+
35
+ /** Error threshold percentage (95%) */
36
+ const ERROR_THRESHOLD = 95;
37
+
38
+ /** Token counter icon */
39
+ const TOKEN_ICON = "◊";
40
+
41
+ // =============================================================================
42
+ // Helper Functions
43
+ // =============================================================================
44
+
45
+ /**
46
+ * Formats a number with K/M suffix for compact display.
47
+ */
48
+ function formatTokenCount(count: number): string {
49
+ if (count < 1000) {
50
+ return count.toString();
51
+ }
52
+ if (count < 1000000) {
53
+ const k = count / 1000;
54
+ return k >= 10 ? `${Math.round(k)}K` : `${k.toFixed(1)}K`;
55
+ }
56
+ const m = count / 1000000;
57
+ return m >= 10 ? `${Math.round(m)}M` : `${m.toFixed(1)}M`;
58
+ }
59
+
60
+ /**
61
+ * Calculates the percentage of tokens used.
62
+ */
63
+ function calculatePercentage(current: number, max: number): number {
64
+ if (max <= 0) return 0;
65
+ return Math.min(Math.round((current / max) * 100), 100);
66
+ }
67
+
68
+ // =============================================================================
69
+ // Main Component
70
+ // =============================================================================
71
+
72
+ /**
73
+ * TokenCounter displays token usage with color-coded warnings.
74
+ *
75
+ * Features:
76
+ * - Current/max token display
77
+ * - Percentage indicator
78
+ * - Color-coded thresholds:
79
+ * - Normal (< 80%): secondary text color
80
+ * - Warning (80-95%): warning color
81
+ * - Error (> 95%): error color
82
+ *
83
+ * @example
84
+ * ```tsx
85
+ * // Normal usage
86
+ * <TokenCounter current={5000} max={100000} />
87
+ *
88
+ * // Warning state
89
+ * <TokenCounter current={85000} max={100000} />
90
+ *
91
+ * // Error state
92
+ * <TokenCounter current={98000} max={100000} />
93
+ * ```
94
+ */
95
+ export function TokenCounter({ current, max }: TokenCounterProps): React.JSX.Element {
96
+ const { theme } = useTheme();
97
+
98
+ const percentage = useMemo(() => calculatePercentage(current, max), [current, max]);
99
+
100
+ const formattedCurrent = useMemo(() => formatTokenCount(current), [current]);
101
+ const formattedMax = useMemo(() => formatTokenCount(max), [max]);
102
+
103
+ // Determine color based on percentage thresholds
104
+ const color = useMemo(() => {
105
+ if (percentage >= ERROR_THRESHOLD) {
106
+ return theme.colors.error;
107
+ }
108
+ if (percentage >= WARNING_THRESHOLD) {
109
+ return theme.colors.warning;
110
+ }
111
+ return theme.semantic.text.secondary;
112
+ }, [percentage, theme.colors.error, theme.colors.warning, theme.semantic.text.secondary]);
113
+
114
+ const percentageColor = useMemo(() => {
115
+ if (percentage >= ERROR_THRESHOLD) {
116
+ return theme.colors.error;
117
+ }
118
+ if (percentage >= WARNING_THRESHOLD) {
119
+ return theme.colors.warning;
120
+ }
121
+ return theme.semantic.text.muted;
122
+ }, [percentage, theme.colors.error, theme.colors.warning, theme.semantic.text.muted]);
123
+
124
+ return (
125
+ <Box>
126
+ <Text color={theme.semantic.text.muted}>{TOKEN_ICON} </Text>
127
+ <Text color={color}>{formattedCurrent}</Text>
128
+ <Text color={theme.semantic.text.muted}>/</Text>
129
+ <Text color={theme.semantic.text.secondary}>{formattedMax}</Text>
130
+ <Text color={theme.semantic.text.muted}> (</Text>
131
+ <Text color={percentageColor}>{percentage}%</Text>
132
+ <Text color={theme.semantic.text.muted}>)</Text>
133
+ </Box>
134
+ );
135
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * TrustModeIndicator Component (T037)
3
+ *
4
+ * Displays the current trust mode with an icon and color.
5
+ * Trust modes control how much autonomy the AI has for tool execution.
6
+ *
7
+ * @module tui/components/StatusBar/TrustModeIndicator
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import { useMemo } from "react";
12
+ import { useTheme } from "../../theme/index.js";
13
+
14
+ // =============================================================================
15
+ // Types
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Trust mode types.
20
+ * - ask: AI asks for approval before each action
21
+ * - auto: AI auto-approves safe actions, asks for dangerous ones
22
+ * - full: AI auto-approves all actions
23
+ */
24
+ export type TrustMode = "ask" | "auto" | "full";
25
+
26
+ /**
27
+ * Props for the TrustModeIndicator component.
28
+ */
29
+ export interface TrustModeIndicatorProps {
30
+ /** Current trust mode */
31
+ readonly mode: TrustMode;
32
+ }
33
+
34
+ // =============================================================================
35
+ // Constants
36
+ // =============================================================================
37
+
38
+ /**
39
+ * Trust mode configuration mapping.
40
+ */
41
+ interface TrustModeConfig {
42
+ readonly icon: string;
43
+ readonly label: string;
44
+ readonly description: string;
45
+ }
46
+
47
+ const TRUST_MODE_CONFIG: Record<TrustMode, TrustModeConfig> = {
48
+ ask: {
49
+ icon: "◎",
50
+ label: "Approval: Ask",
51
+ description: "Manual approval required",
52
+ },
53
+ auto: {
54
+ icon: "◉",
55
+ label: "Approval: Auto",
56
+ description: "Auto-approve safe actions",
57
+ },
58
+ full: {
59
+ icon: "●",
60
+ label: "Approval: Full",
61
+ description: "Auto-approve all actions",
62
+ },
63
+ };
64
+
65
+ // =============================================================================
66
+ // Helper Functions
67
+ // =============================================================================
68
+
69
+ /**
70
+ * Gets the color for a trust mode.
71
+ * - ask: warning (requires attention)
72
+ * - auto: info (semi-autonomous)
73
+ * - full: success (fully autonomous) but also indicates risk
74
+ */
75
+ function getTrustModeColor(mode: TrustMode, theme: ReturnType<typeof useTheme>["theme"]): string {
76
+ switch (mode) {
77
+ case "ask":
78
+ return theme.colors.warning;
79
+ case "auto":
80
+ return theme.colors.info;
81
+ case "full":
82
+ return theme.colors.success;
83
+ default:
84
+ return theme.semantic.text.secondary;
85
+ }
86
+ }
87
+
88
+ // =============================================================================
89
+ // Main Component
90
+ // =============================================================================
91
+
92
+ /**
93
+ * TrustModeIndicator displays the current trust mode with visual cues.
94
+ *
95
+ * Features:
96
+ * - Mode-specific icon
97
+ * - Color-coded by trust level
98
+ * - Compact label display
99
+ *
100
+ * Trust Modes:
101
+ * - Ask (◎): Manual approval for all actions
102
+ * - Auto (◉): Auto-approve safe actions, ask for dangerous ones
103
+ * - Full (●): Auto-approve all actions (highest trust)
104
+ *
105
+ * @example
106
+ * ```tsx
107
+ * // Ask mode (most restrictive)
108
+ * <TrustModeIndicator mode="ask" />
109
+ *
110
+ * // Auto mode (balanced)
111
+ * <TrustModeIndicator mode="auto" />
112
+ *
113
+ * // Full mode (most permissive)
114
+ * <TrustModeIndicator mode="full" />
115
+ * ```
116
+ */
117
+ export function TrustModeIndicator({ mode }: TrustModeIndicatorProps): React.JSX.Element {
118
+ const { theme } = useTheme();
119
+
120
+ const config = useMemo(() => TRUST_MODE_CONFIG[mode], [mode]);
121
+ const color = useMemo(() => getTrustModeColor(mode, theme), [mode, theme]);
122
+
123
+ return (
124
+ <Box>
125
+ <Text color={color}>{config.icon}</Text>
126
+ <Text color={theme.semantic.text.muted}> </Text>
127
+ <Text color={color}>{config.label}</Text>
128
+ </Box>
129
+ );
130
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * WorkspaceIndicator Component
3
+ *
4
+ * Displays the current workspace directory name with a folder icon.
5
+ * Uses gradient styling for visual emphasis while maintaining readability.
6
+ *
7
+ * @module tui/components/StatusBar/WorkspaceIndicator
8
+ */
9
+
10
+ import { getIcons } from "@vellum/shared";
11
+ import { Text } from "ink";
12
+ import type React from "react";
13
+ import { useMemo } from "react";
14
+ import { useWorkspace } from "../../hooks/useWorkspace.js";
15
+ import { useTheme } from "../../theme/index.js";
16
+ import { truncateToDisplayWidth } from "../../utils/index.js";
17
+ import { GradientText } from "../common/GradientText.js";
18
+
19
+ // =============================================================================
20
+ // Types
21
+ // =============================================================================
22
+
23
+ /**
24
+ * Props for the WorkspaceIndicator component.
25
+ */
26
+ export interface WorkspaceIndicatorProps {
27
+ /** Maximum width for the workspace name (truncates if exceeded) */
28
+ readonly maxWidth?: number;
29
+ /** Whether to use gradient styling (default: true) */
30
+ readonly useGradient?: boolean;
31
+ }
32
+
33
+ // =============================================================================
34
+ // Component
35
+ // =============================================================================
36
+
37
+ /**
38
+ * WorkspaceIndicator displays the current workspace directory.
39
+ *
40
+ * Uses Nerd Font icon or Unicode/ASCII fallback.
41
+ * Applies gradient styling for visual emphasis (configurable).
42
+ * Truncates long names with ellipsis if maxWidth is specified.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * <WorkspaceIndicator maxWidth={20} />
47
+ * // Output: vellum (with gradient)
48
+ *
49
+ * <WorkspaceIndicator useGradient={false} />
50
+ * // Output: vellum (muted)
51
+ * ```
52
+ */
53
+ export function WorkspaceIndicator({
54
+ maxWidth = 20,
55
+ useGradient = true,
56
+ }: WorkspaceIndicatorProps): React.JSX.Element {
57
+ const { theme } = useTheme();
58
+ const { name } = useWorkspace();
59
+ const icons = getIcons();
60
+
61
+ const displayName = useMemo(() => {
62
+ // Truncate with ellipsis using string-width for CJK/Emoji handling
63
+ return truncateToDisplayWidth(name, maxWidth);
64
+ }, [name, maxWidth]);
65
+
66
+ // Workspace gradient: subtle gold tones
67
+ const workspaceGradient = useMemo(
68
+ () => [theme.brand.primary, theme.brand.secondary, theme.brand.mid] as const,
69
+ [theme.brand]
70
+ );
71
+
72
+ if (useGradient) {
73
+ return (
74
+ <Text>
75
+ <Text color={theme.brand.primary}>{icons.folder} </Text>
76
+ <GradientText text={displayName} colors={workspaceGradient} bold />
77
+ </Text>
78
+ );
79
+ }
80
+
81
+ return (
82
+ <Text color={theme.semantic.text.muted}>
83
+ {icons.folder} {displayName}
84
+ </Text>
85
+ );
86
+ }