@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,257 @@
1
+ /**
2
+ * AgentModeIndicator Component (T057)
3
+ *
4
+ * Displays the current agent mode for multi-agent orchestration.
5
+ * Shows agent name, level depth, and agent-specific icons.
6
+ *
7
+ * @module tui/components/StatusBar/AgentModeIndicator
8
+ */
9
+
10
+ import { getIcons } from "@vellum/shared";
11
+ import { Box, Text } from "ink";
12
+ import { useMemo } from "react";
13
+ import { useTheme } from "../../theme/index.js";
14
+
15
+ // =============================================================================
16
+ // Types
17
+ // =============================================================================
18
+
19
+ /**
20
+ * Agent level depth in the orchestration hierarchy.
21
+ * - 0: Master orchestrator (ouroboros)
22
+ * - 1: Sub-orchestrators (init, spec, implement, archive)
23
+ * - 2: Workers (coder, qa, writer, analyst, etc.)
24
+ */
25
+ export type AgentLevel = 0 | 1 | 2;
26
+
27
+ /**
28
+ * Known agent types in the multi-agent system.
29
+ */
30
+ export type AgentType =
31
+ | "orchestrator"
32
+ | "coder"
33
+ | "qa"
34
+ | "writer"
35
+ | "analyst"
36
+ | "devops"
37
+ | "security"
38
+ | "architect"
39
+ | "researcher"
40
+ | "requirements"
41
+ | "tasks"
42
+ | "validator"
43
+ | "init"
44
+ | "spec"
45
+ | "implement"
46
+ | "archive";
47
+
48
+ /**
49
+ * Props for the AgentModeIndicator component.
50
+ */
51
+ export interface AgentModeIndicatorProps {
52
+ /** Current agent name (e.g., 'orchestrator', 'coder', 'qa') */
53
+ readonly agentName: string;
54
+ /** Agent level depth (0=orchestrator, 1=sub-orchestrator, 2=worker) */
55
+ readonly level?: AgentLevel;
56
+ /** Compact display mode (icon only) */
57
+ readonly compact?: boolean;
58
+ /** Whether delegation is currently active (T059) */
59
+ readonly isDelegating?: boolean;
60
+ /** Agent being delegated to (T059) */
61
+ readonly delegatingTo?: string;
62
+ }
63
+
64
+ // =============================================================================
65
+ // Constants
66
+ // =============================================================================
67
+
68
+ /**
69
+ * Get agent icons mapping.
70
+ * Uses centralized icon system for consistent terminal display.
71
+ */
72
+ function getAgentIcons(): Record<string, string> {
73
+ const icons = getIcons();
74
+ return {
75
+ orchestrator: icons.assistant, // Robot/AI icon
76
+ coder: "⌨", // Keyboard (Unicode)
77
+ qa: "⚗", // Test tube (Unicode)
78
+ writer: "✎", // Pencil (Unicode)
79
+ analyst: "⊙", // Search/target (Unicode)
80
+ devops: icons.tool, // Tool icon
81
+ security: "⊗", // Lock-like (Unicode)
82
+ architect: "⦿", // Diagram-like (Unicode)
83
+ researcher: "⊞", // Book-like (Unicode)
84
+ requirements: icons.plan, // Plan icon
85
+ tasks: icons.note, // Note icon
86
+ validator: icons.check, // Check icon
87
+ init: "▶", // Play (Unicode)
88
+ spec: icons.spec, // Spec icon
89
+ implement: icons.gear, // Gear icon
90
+ archive: "⊟", // Box-like (Unicode)
91
+ default: icons.assistant, // Default robot
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Agent display names mapping.
97
+ */
98
+ const AGENT_NAMES: Record<string, string> = {
99
+ orchestrator: "Orchestrator",
100
+ coder: "Coder",
101
+ qa: "QA",
102
+ writer: "Writer",
103
+ analyst: "Analyst",
104
+ devops: "DevOps",
105
+ security: "Security",
106
+ architect: "Architect",
107
+ researcher: "Researcher",
108
+ requirements: "Requirements",
109
+ tasks: "Tasks",
110
+ validator: "Validator",
111
+ init: "Init",
112
+ spec: "Spec",
113
+ implement: "Implement",
114
+ archive: "Archive",
115
+ };
116
+
117
+ /**
118
+ * Level indicator labels.
119
+ */
120
+ const LEVEL_LABELS: Record<AgentLevel, string> = {
121
+ 0: "L0",
122
+ 1: "L1",
123
+ 2: "L2",
124
+ };
125
+
126
+ // =============================================================================
127
+ // Helper Functions
128
+ // =============================================================================
129
+
130
+ /**
131
+ * Gets the icon for an agent type.
132
+ * Falls back to default robot icon for unknown agents.
133
+ */
134
+ function getAgentIcon(agentName: string): string {
135
+ const agentIcons = getAgentIcons();
136
+ const normalizedName = agentName.toLowerCase();
137
+ return agentIcons[normalizedName] ?? agentIcons.default ?? getIcons().assistant;
138
+ }
139
+
140
+ /**
141
+ * Gets the display name for an agent.
142
+ * Falls back to capitalized agent name for unknown agents.
143
+ */
144
+ function getAgentDisplayName(agentName: string): string {
145
+ const normalizedName = agentName.toLowerCase();
146
+ return AGENT_NAMES[normalizedName] ?? agentName.charAt(0).toUpperCase() + agentName.slice(1);
147
+ }
148
+
149
+ /**
150
+ * Gets the color for an agent level.
151
+ * - L0: Primary (orchestrator level)
152
+ * - L1: Info (sub-orchestrator level)
153
+ * - L2: Secondary (worker level)
154
+ */
155
+ function getLevelColor(level: AgentLevel, theme: ReturnType<typeof useTheme>["theme"]): string {
156
+ switch (level) {
157
+ case 0:
158
+ return theme.colors.primary;
159
+ case 1:
160
+ return theme.colors.info;
161
+ case 2:
162
+ return theme.semantic.text.secondary;
163
+ default:
164
+ return theme.semantic.text.muted;
165
+ }
166
+ }
167
+
168
+ // =============================================================================
169
+ // Main Component
170
+ // =============================================================================
171
+
172
+ /**
173
+ * AgentModeIndicator displays the current agent in the multi-agent system.
174
+ *
175
+ * Features:
176
+ * - Agent-specific icon (emoji)
177
+ * - Agent name display
178
+ * - Level indicator (L0/L1/L2)
179
+ * - Compact mode for space-constrained layouts
180
+ * - Themed styling based on agent level
181
+ *
182
+ * Agent Levels:
183
+ * - L0: Master orchestrator (ouroboros)
184
+ * - L1: Sub-orchestrators (init, spec, implement, archive)
185
+ * - L2: Workers (coder, qa, writer, analyst, devops, security, etc.)
186
+ *
187
+ * @example
188
+ * ```tsx
189
+ * // Master orchestrator
190
+ * <AgentModeIndicator agentName="orchestrator" level={0} />
191
+ *
192
+ * // Coder worker
193
+ * <AgentModeIndicator agentName="coder" level={2} />
194
+ *
195
+ * // Compact mode (icon only)
196
+ * <AgentModeIndicator agentName="qa" level={2} compact />
197
+ * ```
198
+ */
199
+ export function AgentModeIndicator({
200
+ agentName,
201
+ level = 2,
202
+ compact = false,
203
+ isDelegating = false,
204
+ delegatingTo,
205
+ }: AgentModeIndicatorProps): React.JSX.Element {
206
+ const { theme } = useTheme();
207
+
208
+ const icon = useMemo(() => getAgentIcon(agentName), [agentName]);
209
+ const displayName = useMemo(() => getAgentDisplayName(agentName), [agentName]);
210
+ const levelColor = useMemo(() => getLevelColor(level, theme), [level, theme]);
211
+ const levelLabel = LEVEL_LABELS[level];
212
+
213
+ // Show delegation indicator when active (GAP 3 fix - T059)
214
+ if (isDelegating && delegatingTo) {
215
+ const delegateIcon = getAgentIcon(delegatingTo);
216
+ const delegateName = getAgentDisplayName(delegatingTo);
217
+
218
+ if (compact) {
219
+ return (
220
+ <Box>
221
+ <Text color={theme.colors.warning}>⋯</Text>
222
+ <Text color={theme.semantic.text.muted}> </Text>
223
+ <Text>{delegateIcon}</Text>
224
+ </Box>
225
+ );
226
+ }
227
+
228
+ return (
229
+ <Box>
230
+ <Text color={theme.colors.warning}>⋯ Delegating to </Text>
231
+ <Text>{delegateIcon}</Text>
232
+ <Text color={theme.semantic.text.muted}> </Text>
233
+ <Text color={theme.colors.info}>{delegateName}</Text>
234
+ </Box>
235
+ );
236
+ }
237
+
238
+ if (compact) {
239
+ return (
240
+ <Box>
241
+ <Text>{icon}</Text>
242
+ <Text color={theme.semantic.text.muted}> </Text>
243
+ <Text color={levelColor}>{levelLabel}</Text>
244
+ </Box>
245
+ );
246
+ }
247
+
248
+ return (
249
+ <Box>
250
+ <Text>{icon}</Text>
251
+ <Text color={theme.semantic.text.muted}> </Text>
252
+ <Text color={theme.semantic.text.primary}>{displayName}</Text>
253
+ <Text color={theme.semantic.text.muted}> </Text>
254
+ <Text color={levelColor}>[{levelLabel}]</Text>
255
+ </Box>
256
+ );
257
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * ContextProgress Component
3
+ *
4
+ * Visual context window usage indicator with progress bar.
5
+ * Shows token usage with color-coded thresholds:
6
+ * - Green (0-50%): Plenty of context
7
+ * - Yellow (50-80%): Moderate usage
8
+ * - Red (80%+): Critical usage
9
+ *
10
+ * @module tui/components/StatusBar/ContextProgress
11
+ */
12
+
13
+ import { Box, Text } from "ink";
14
+ import { useMemo } from "react";
15
+ import { useTheme } from "../../theme/index.js";
16
+
17
+ // =============================================================================
18
+ // Types
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Props for the ContextProgress component.
23
+ */
24
+ export interface ContextProgressProps {
25
+ /** Current token count */
26
+ readonly current: number;
27
+ /** Maximum token limit */
28
+ readonly max: number;
29
+ /** Whether to show the label "Context:" (default: true) */
30
+ readonly showLabel?: boolean;
31
+ /** Width of progress bar in characters (default: 10) */
32
+ readonly barWidth?: number;
33
+ }
34
+
35
+ // =============================================================================
36
+ // Constants
37
+ // =============================================================================
38
+
39
+ /** Progress bar characters - using half-filled block for visual progress */
40
+ const BAR_FILLED = "▓";
41
+ const BAR_EMPTY = "░";
42
+
43
+ /** Color thresholds for context usage warning */
44
+ const THRESHOLDS = {
45
+ LOW: 50, // 0-50%: green (plenty of context)
46
+ MEDIUM: 80, // 50-80%: yellow (moderate usage)
47
+ // 80%+: red (critical usage)
48
+ } as const;
49
+
50
+ /** Default bar width - compact for status bar usage */
51
+ const DEFAULT_BAR_WIDTH = 10;
52
+
53
+ // =============================================================================
54
+ // Helper Functions
55
+ // =============================================================================
56
+
57
+ /**
58
+ * Formats a number with K/M suffix for compact display.
59
+ */
60
+ function formatTokenCount(count: number): string {
61
+ if (count < 1000) {
62
+ return count.toString();
63
+ }
64
+ if (count < 1000000) {
65
+ const k = count / 1000;
66
+ return k >= 10 ? `${Math.round(k)}K` : `${k.toFixed(1)}K`;
67
+ }
68
+ const m = count / 1000000;
69
+ return m >= 10 ? `${Math.round(m)}M` : `${m.toFixed(1)}M`;
70
+ }
71
+
72
+ /**
73
+ * Calculates the percentage of tokens used.
74
+ */
75
+ function calculatePercentage(current: number, max: number): number {
76
+ if (max <= 0) return 0;
77
+ return Math.min(Math.round((current / max) * 100), 100);
78
+ }
79
+
80
+ /**
81
+ * Get the color based on usage percentage.
82
+ * Uses semantic status colors:
83
+ * - 0-50%: success (green) - plenty of context
84
+ * - 50-80%: warning (yellow) - moderate usage
85
+ * - 80-100%: error (red) - critical usage
86
+ */
87
+ function getProgressColor(percentage: number, theme: ReturnType<typeof useTheme>["theme"]): string {
88
+ if (percentage >= THRESHOLDS.MEDIUM) {
89
+ return theme.colors.error; // Red for 80%+
90
+ }
91
+ if (percentage >= THRESHOLDS.LOW) {
92
+ return theme.colors.warning; // Yellow for 50-80%
93
+ }
94
+ return theme.colors.success; // Green for 0-50%
95
+ }
96
+
97
+ /**
98
+ * Generates the progress bar string.
99
+ */
100
+ function generateProgressBar(percentage: number, width: number): { filled: string; empty: string } {
101
+ const filledCount = Math.round((percentage / 100) * width);
102
+ const emptyCount = width - filledCount;
103
+ return {
104
+ filled: BAR_FILLED.repeat(filledCount),
105
+ empty: BAR_EMPTY.repeat(emptyCount),
106
+ };
107
+ }
108
+
109
+ // =============================================================================
110
+ // Main Component
111
+ // =============================================================================
112
+
113
+ /**
114
+ * ContextProgress displays context window usage with a visual progress bar.
115
+ *
116
+ * Features:
117
+ * - Visual progress bar using Unicode block characters
118
+ * - Color-coded based on usage thresholds
119
+ * - Compact token count display (K/M suffixes)
120
+ * - Optional label display
121
+ *
122
+ * @example
123
+ * ```tsx
124
+ * // Full display with label
125
+ * <ContextProgress current={32000} max={40000} />
126
+ * // Output: Context: ████████░░ 80% (32K/40K)
127
+ *
128
+ * // Compact without label
129
+ * <ContextProgress current={5000} max={100000} showLabel={false} />
130
+ * // Output: █░░░░░░░░░ 5% (5K/100K)
131
+ * ```
132
+ */
133
+ export function ContextProgress({
134
+ current,
135
+ max,
136
+ showLabel = true,
137
+ barWidth = DEFAULT_BAR_WIDTH,
138
+ }: ContextProgressProps): React.JSX.Element {
139
+ const { theme } = useTheme();
140
+
141
+ const percentage = useMemo(() => calculatePercentage(current, max), [current, max]);
142
+
143
+ const progressBar = useMemo(
144
+ () => generateProgressBar(percentage, barWidth),
145
+ [percentage, barWidth]
146
+ );
147
+
148
+ const progressColor = useMemo(() => getProgressColor(percentage, theme), [percentage, theme]);
149
+
150
+ const formattedCurrent = useMemo(() => formatTokenCount(current), [current]);
151
+ const formattedMax = useMemo(() => formatTokenCount(max), [max]);
152
+
153
+ return (
154
+ <Box>
155
+ {showLabel && <Text color={theme.semantic.text.muted}>Context: </Text>}
156
+ <Text color={progressColor}>{progressBar.filled}</Text>
157
+ <Text color={theme.semantic.text.muted}>{progressBar.empty}</Text>
158
+ <Text color={theme.semantic.text.muted}> </Text>
159
+ <Text color={progressColor}>{percentage}%</Text>
160
+ <Text color={theme.semantic.text.muted}> (</Text>
161
+ <Text color={theme.semantic.text.secondary}>{formattedCurrent}</Text>
162
+ <Text color={theme.semantic.text.muted}>/</Text>
163
+ <Text color={theme.semantic.text.secondary}>{formattedMax}</Text>
164
+ <Text color={theme.semantic.text.muted}>)</Text>
165
+ </Box>
166
+ );
167
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * FileChangesIndicator Component
3
+ *
4
+ * Displays file change statistics (+additions -deletions) in the header bar.
5
+ * Uses success color for additions and error color for deletions.
6
+ *
7
+ * @module tui/components/StatusBar/FileChangesIndicator
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import type React from "react";
12
+ import { useTheme } from "../../theme/index.js";
13
+
14
+ // =============================================================================
15
+ // Types
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Props for the FileChangesIndicator component.
20
+ */
21
+ export interface FileChangesIndicatorProps {
22
+ /** Number of lines added */
23
+ readonly additions: number;
24
+ /** Number of lines deleted */
25
+ readonly deletions: number;
26
+ }
27
+
28
+ // =============================================================================
29
+ // Component
30
+ // =============================================================================
31
+
32
+ /**
33
+ * FileChangesIndicator displays cumulative file change statistics.
34
+ *
35
+ * Format: +{additions} -{deletions}
36
+ * Returns null if no changes (additions and deletions both 0).
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * <FileChangesIndicator additions={42} deletions={15} />
41
+ * // Output: +42 -15
42
+ * ```
43
+ */
44
+ export function FileChangesIndicator({
45
+ additions,
46
+ deletions,
47
+ }: FileChangesIndicatorProps): React.JSX.Element | null {
48
+ const { theme } = useTheme();
49
+
50
+ // Don't show if no changes
51
+ if (additions === 0 && deletions === 0) {
52
+ return null;
53
+ }
54
+
55
+ return (
56
+ <Box>
57
+ <Text color={theme.colors.success}>+{additions}</Text>
58
+ <Text> </Text>
59
+ <Text color={theme.colors.error}>-{deletions}</Text>
60
+ </Box>
61
+ );
62
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * GitIndicator Component
3
+ *
4
+ * Displays the current git branch and dirty status.
5
+ * Uses dim styling to avoid competing with main content.
6
+ *
7
+ * @module tui/components/StatusBar/GitIndicator
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 { useGitStatus } from "../../hooks/useGitStatus.js";
15
+ import { useTheme } from "../../theme/index.js";
16
+ import { truncateToDisplayWidth } from "../../utils/index.js";
17
+
18
+ // =============================================================================
19
+ // Types
20
+ // =============================================================================
21
+
22
+ /**
23
+ * Props for the GitIndicator component.
24
+ */
25
+ export interface GitIndicatorProps {
26
+ /** Maximum width for the branch name (truncates if exceeded) */
27
+ readonly maxWidth?: number;
28
+ /** Whether to show the dirty indicator (default: true) */
29
+ readonly showDirty?: boolean;
30
+ /** Whether to show the changed files count (default: false) */
31
+ readonly showChangedCount?: boolean;
32
+ }
33
+
34
+ // =============================================================================
35
+ // Component
36
+ // =============================================================================
37
+
38
+ /**
39
+ * GitIndicator displays the current git branch and status.
40
+ *
41
+ * Uses Nerd Font icon or Unicode/ASCII fallback.
42
+ * Shows dirty indicator when there are uncommitted changes.
43
+ *
44
+ * Returns null if not in a git repository.
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * <GitIndicator maxWidth={15} />
49
+ * // Output: main (clean)
50
+ * // Output: main * (dirty)
51
+ * // Output: feature/long… * (truncated + dirty)
52
+ * ```
53
+ */
54
+ export function GitIndicator({
55
+ maxWidth = 15,
56
+ showDirty = true,
57
+ showChangedCount = false,
58
+ }: GitIndicatorProps): React.JSX.Element | null {
59
+ const { theme } = useTheme();
60
+ const { branch, isDirty, changedFiles, isLoading, isGitRepo } = useGitStatus();
61
+ const icons = getIcons();
62
+
63
+ const displayBranch = useMemo(() => {
64
+ if (!branch) return null;
65
+ // Truncate with ellipsis using string-width for CJK/Emoji handling
66
+ return truncateToDisplayWidth(branch, maxWidth);
67
+ }, [branch, maxWidth]);
68
+
69
+ // Don't render if not in a git repo or still loading
70
+ if (!isGitRepo || isLoading || !displayBranch) {
71
+ return null;
72
+ }
73
+
74
+ // Build the dirty suffix
75
+ const dirtySuffix = showDirty && isDirty ? ` ${icons.dirty}` : "";
76
+ const countSuffix = showChangedCount && changedFiles > 0 ? ` (${changedFiles})` : "";
77
+
78
+ return (
79
+ <Text color={theme.semantic.text.muted}>
80
+ {icons.branch} {displayBranch}
81
+ {isDirty && (
82
+ <Text color={theme.colors.warning}>
83
+ {dirtySuffix}
84
+ {countSuffix}
85
+ </Text>
86
+ )}
87
+ </Text>
88
+ );
89
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * HeaderBar Component
3
+ *
4
+ * Embedded header bar showing workspace and git status indicators.
5
+ * Designed to be placed at the top of the AppHeader without adding extra lines.
6
+ *
7
+ * Features:
8
+ * - Workspace directory name with folder icon
9
+ * - Git branch with dirty indicator
10
+ * - Responsive: adapts to terminal width
11
+ * - Dim styling to not compete with main content
12
+ *
13
+ * @module tui/components/StatusBar/HeaderBar
14
+ */
15
+
16
+ import { Box, Text } from "ink";
17
+ import type React from "react";
18
+ import { useFileChangeStats } from "../../hooks/useFileChangeStats.js";
19
+ import { useTerminalDimensions } from "../../hooks/useTerminalSize.js";
20
+ import { useTheme } from "../../theme/index.js";
21
+ import { FileChangesIndicator } from "./FileChangesIndicator.js";
22
+ import { GitIndicator } from "./GitIndicator.js";
23
+ import { WorkspaceIndicator } from "./WorkspaceIndicator.js";
24
+
25
+ // =============================================================================
26
+ // Constants
27
+ // =============================================================================
28
+
29
+ /** Separator between header bar items */
30
+ const SEPARATOR = " │ ";
31
+
32
+ /** Minimum terminal width to show git indicator */
33
+ const MIN_WIDTH_FOR_GIT = 60;
34
+
35
+ /** Minimum terminal width to show both indicators with full width */
36
+ const COMPACT_THRESHOLD = 80;
37
+
38
+ // =============================================================================
39
+ // Types
40
+ // =============================================================================
41
+
42
+ /**
43
+ * Props for the HeaderBar component.
44
+ */
45
+ export interface HeaderBarProps {
46
+ /** Optional session snapshot count to display */
47
+ readonly snapshotCount?: number;
48
+ /** Whether sandbox boundaries are active */
49
+ readonly sandboxActive?: boolean;
50
+ }
51
+
52
+ // =============================================================================
53
+ // Component
54
+ // =============================================================================
55
+
56
+ /**
57
+ * HeaderBar displays workspace and git status in a compact horizontal bar.
58
+ *
59
+ * Layout:
60
+ * - Wide (≥80 cols): vellum │ main *
61
+ * - Medium (60-79): vellum │ main
62
+ * - Narrow (<60): vellum (no git)
63
+ *
64
+ * Uses dim colors to stay unobtrusive while providing useful context.
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * <HeaderBar />
69
+ * // Output: vellum │ main *
70
+ * ```
71
+ */
72
+ export function HeaderBar({ snapshotCount, sandboxActive }: HeaderBarProps): React.JSX.Element {
73
+ const { theme } = useTheme();
74
+ const { width: columns } = useTerminalDimensions();
75
+ const fileStats = useFileChangeStats();
76
+
77
+ // Responsive width calculations
78
+ const isCompact = columns < COMPACT_THRESHOLD;
79
+ const showGit = columns >= MIN_WIDTH_FOR_GIT;
80
+
81
+ // Calculate max widths based on available space
82
+ const workspaceMaxWidth = isCompact ? 15 : 20;
83
+ const gitMaxWidth = isCompact ? 10 : 15;
84
+
85
+ // Show file changes if there are any
86
+ const hasFileChanges = fileStats.additions > 0 || fileStats.deletions > 0;
87
+
88
+ return (
89
+ <Box flexDirection="row" justifyContent="flex-start">
90
+ {/* Workspace indicator */}
91
+ <WorkspaceIndicator maxWidth={workspaceMaxWidth} />
92
+
93
+ {/* Git indicator (if visible and space allows) */}
94
+ {showGit && (
95
+ <>
96
+ <Text color={theme.semantic.text.muted}>{SEPARATOR}</Text>
97
+ <GitIndicator maxWidth={gitMaxWidth} showDirty showChangedCount={!isCompact} />
98
+ </>
99
+ )}
100
+
101
+ {/* File changes indicator */}
102
+ {hasFileChanges && (
103
+ <>
104
+ <Text color={theme.semantic.text.muted}>{SEPARATOR}</Text>
105
+ <FileChangesIndicator additions={fileStats.additions} deletions={fileStats.deletions} />
106
+ </>
107
+ )}
108
+
109
+ {/* Optional snapshot count */}
110
+ {snapshotCount !== undefined && snapshotCount > 0 && (
111
+ <>
112
+ <Text color={theme.semantic.text.muted}>{SEPARATOR}</Text>
113
+ <Text color={theme.semantic.text.muted}>◉ {snapshotCount}</Text>
114
+ </>
115
+ )}
116
+
117
+ {/* Optional sandbox indicator */}
118
+ {sandboxActive && (
119
+ <>
120
+ <Text color={theme.semantic.text.muted}>{SEPARATOR}</Text>
121
+ <Text color={theme.colors.warning}>⊘ sandbox</Text>
122
+ </>
123
+ )}
124
+ </Box>
125
+ );
126
+ }