@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,440 @@
1
+ /**
2
+ * SnapshotCheckpointPanel Component
3
+ *
4
+ * React Ink component for managing file state checkpoints using the Snapshot system.
5
+ * Displays recent snapshots with keyboard navigation for viewing, restoring, and creating.
6
+ *
7
+ * @module tui/components/Checkpoint/SnapshotCheckpointPanel
8
+ */
9
+
10
+ import type { SnapshotInfo } from "@vellum/core";
11
+ import { Box, Text, useInput } from "ink";
12
+ import type React from "react";
13
+ import { useCallback, useMemo, useState } from "react";
14
+ import { useTheme } from "../../theme/index.js";
15
+ import { truncateToDisplayWidth } from "../../utils/index.js";
16
+ import { HotkeyHints } from "../common/HotkeyHints.js";
17
+
18
+ // =============================================================================
19
+ // Types
20
+ // =============================================================================
21
+
22
+ /**
23
+ * Props for the SnapshotCheckpointPanel component.
24
+ */
25
+ export interface SnapshotCheckpointPanelProps {
26
+ /** List of snapshots to display */
27
+ readonly snapshots: readonly SnapshotInfo[];
28
+ /** Whether snapshots are loading */
29
+ readonly isLoading: boolean;
30
+ /** Error message if any */
31
+ readonly error: string | null;
32
+ /** Whether the snapshot system is initialized */
33
+ readonly isInitialized: boolean;
34
+ /** Maximum height in lines */
35
+ readonly maxHeight?: number;
36
+ /** Whether the panel is focused for keyboard input */
37
+ readonly isFocused?: boolean;
38
+ /** Callback when restore is requested */
39
+ readonly onRestore?: (hash: string) => void;
40
+ /** Callback when diff is requested */
41
+ readonly onDiff?: (hash: string) => void;
42
+ /** Callback when new checkpoint is requested */
43
+ readonly onTakeCheckpoint?: () => void;
44
+ /** Callback to refresh the list */
45
+ readonly onRefresh?: () => void;
46
+ /** Callback when a snapshot is selected */
47
+ readonly onSelect?: (snapshot: SnapshotInfo | null) => void;
48
+ }
49
+
50
+ // =============================================================================
51
+ // Constants
52
+ // =============================================================================
53
+
54
+ /** Default maximum height */
55
+ const DEFAULT_MAX_HEIGHT = 12;
56
+
57
+ /** Number of items per page */
58
+ const PAGE_SIZE = 5;
59
+
60
+ // =============================================================================
61
+ // Helper Functions
62
+ // =============================================================================
63
+
64
+ /**
65
+ * Format relative time from a date.
66
+ */
67
+ function formatRelativeTime(date: Date): string {
68
+ const now = Date.now();
69
+ const diff = now - date.getTime();
70
+
71
+ const seconds = Math.floor(diff / 1000);
72
+ const minutes = Math.floor(seconds / 60);
73
+ const hours = Math.floor(minutes / 60);
74
+ const days = Math.floor(hours / 24);
75
+
76
+ if (days > 0) {
77
+ return `${days}d ago`;
78
+ }
79
+ if (hours > 0) {
80
+ return `${hours}h ago`;
81
+ }
82
+ if (minutes > 0) {
83
+ return `${minutes}m ago`;
84
+ }
85
+ return "just now";
86
+ }
87
+
88
+ /**
89
+ * Truncate text to max width.
90
+ */
91
+ function truncate(text: string, maxLength: number): string {
92
+ return truncateToDisplayWidth(text, maxLength);
93
+ }
94
+
95
+ /**
96
+ * Get visible entries based on scroll position.
97
+ */
98
+ function getVisibleEntries<T>(
99
+ entries: readonly T[],
100
+ scrollOffset: number,
101
+ maxVisible: number
102
+ ): readonly T[] {
103
+ return entries.slice(scrollOffset, scrollOffset + maxVisible);
104
+ }
105
+
106
+ // =============================================================================
107
+ // SnapshotItem Component
108
+ // =============================================================================
109
+
110
+ interface SnapshotItemProps {
111
+ readonly snapshot: SnapshotInfo;
112
+ readonly index: number;
113
+ readonly isSelected: boolean;
114
+ readonly width: number;
115
+ readonly primaryColor: string;
116
+ readonly mutedColor: string;
117
+ readonly textColor: string;
118
+ }
119
+
120
+ function SnapshotItem({
121
+ snapshot,
122
+ index,
123
+ isSelected,
124
+ width,
125
+ primaryColor,
126
+ mutedColor,
127
+ textColor,
128
+ }: SnapshotItemProps): React.JSX.Element {
129
+ const indicator = isSelected ? "[>]" : `[${index + 1}]`;
130
+ const hashShort = snapshot.hash.slice(0, 7);
131
+ const timeStr = formatRelativeTime(snapshot.timestamp);
132
+ const message = snapshot.message ?? "(no message)";
133
+ const fileCount = snapshot.files.length;
134
+
135
+ // Calculate available width for message
136
+ const fixedWidth = 5 + 8 + 3 + 10 + 3; // indicator + hash + spacing + time + spacing
137
+ const messageWidth = Math.max(width - fixedWidth - 4, 10);
138
+
139
+ return (
140
+ <Box flexDirection="column" paddingLeft={1}>
141
+ <Box flexDirection="row">
142
+ <Text color={isSelected ? primaryColor : mutedColor} bold={isSelected}>
143
+ {indicator}
144
+ </Text>
145
+ <Text> </Text>
146
+ <Text color={isSelected ? primaryColor : textColor} bold={isSelected}>
147
+ {hashShort}
148
+ </Text>
149
+ <Text color={mutedColor}> - </Text>
150
+ <Text color={mutedColor}>{timeStr}</Text>
151
+ <Text color={mutedColor}> - </Text>
152
+ <Text color={isSelected ? textColor : mutedColor} dimColor={!isSelected}>
153
+ "{truncate(message, messageWidth)}"
154
+ </Text>
155
+ </Box>
156
+ <Box paddingLeft={5}>
157
+ <Text color={mutedColor} dimColor>
158
+ Files: {fileCount} changed
159
+ </Text>
160
+ </Box>
161
+ </Box>
162
+ );
163
+ }
164
+
165
+ // =============================================================================
166
+ // Main Component
167
+ // =============================================================================
168
+
169
+ /**
170
+ * SnapshotCheckpointPanel - Displays file state checkpoints from the Snapshot system.
171
+ *
172
+ * Features:
173
+ * - List recent checkpoints (limit 10)
174
+ * - Show relative time, message, file count
175
+ * - Keyboard navigation: j/k or arrows
176
+ * - r = restore selected
177
+ * - d = show diff
178
+ * - n = take new checkpoint
179
+ *
180
+ * @example
181
+ * ```tsx
182
+ * <SnapshotCheckpointPanel
183
+ * snapshots={snapshots}
184
+ * isLoading={false}
185
+ * error={null}
186
+ * isInitialized={true}
187
+ * isFocused={true}
188
+ * onRestore={(hash) => restore(hash)}
189
+ * onDiff={(hash) => showDiff(hash)}
190
+ * onTakeCheckpoint={() => takeCheckpoint()}
191
+ * />
192
+ * ```
193
+ */
194
+ export function SnapshotCheckpointPanel({
195
+ snapshots,
196
+ isLoading,
197
+ error,
198
+ isInitialized,
199
+ maxHeight = DEFAULT_MAX_HEIGHT,
200
+ isFocused = true,
201
+ onRestore,
202
+ onDiff,
203
+ onTakeCheckpoint,
204
+ onRefresh,
205
+ onSelect,
206
+ }: SnapshotCheckpointPanelProps): React.JSX.Element {
207
+ const { theme } = useTheme();
208
+
209
+ // State
210
+ const [selectedIndex, setSelectedIndex] = useState(0);
211
+ const [scrollOffset, setScrollOffset] = useState(0);
212
+
213
+ // Colors from theme
214
+ const primaryColor = theme.brand.primary;
215
+ const mutedColor = theme.semantic.text.muted;
216
+ const textColor = theme.semantic.text.primary;
217
+ const borderColor = theme.semantic.border.default;
218
+ const errorColor = theme.colors.error;
219
+
220
+ // Calculate visible items (account for header and hints)
221
+ const maxVisible = Math.max(1, Math.floor((maxHeight - 5) / 2)); // Each item takes 2 lines
222
+
223
+ // Get visible snapshots
224
+ const visibleSnapshots = useMemo(
225
+ () => getVisibleEntries(snapshots, scrollOffset, maxVisible),
226
+ [snapshots, scrollOffset, maxVisible]
227
+ );
228
+
229
+ // Scroll indicators
230
+ const canScrollUp = scrollOffset > 0;
231
+ const canScrollDown = scrollOffset + maxVisible < snapshots.length;
232
+
233
+ // Hotkey hints
234
+ const hints = useMemo(
235
+ () => [
236
+ { keys: "j/k", label: "navigate" },
237
+ { keys: "r", label: "restore" },
238
+ { keys: "d", label: "diff" },
239
+ { keys: "n", label: "new" },
240
+ ],
241
+ []
242
+ );
243
+
244
+ /**
245
+ * Navigate to a specific index.
246
+ */
247
+ const navigateToIndex = useCallback(
248
+ (newIndex: number) => {
249
+ const maxIndex = Math.max(0, snapshots.length - 1);
250
+ const clampedIndex = Math.max(0, Math.min(newIndex, maxIndex));
251
+ setSelectedIndex(clampedIndex);
252
+
253
+ // Adjust scroll to keep selection visible
254
+ if (clampedIndex < scrollOffset) {
255
+ setScrollOffset(clampedIndex);
256
+ } else if (clampedIndex >= scrollOffset + maxVisible) {
257
+ setScrollOffset(clampedIndex - maxVisible + 1);
258
+ }
259
+
260
+ // Notify parent
261
+ const selectedSnapshot = snapshots[clampedIndex] ?? null;
262
+ onSelect?.(selectedSnapshot);
263
+ },
264
+ [snapshots, scrollOffset, maxVisible, onSelect]
265
+ );
266
+
267
+ // Handle keyboard input
268
+ useInput(
269
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Input handler must process multiple key bindings for navigation and actions
270
+ (input, key) => {
271
+ if (!isFocused) return;
272
+
273
+ // Navigation down
274
+ if (input === "j" || key.downArrow) {
275
+ navigateToIndex(selectedIndex + 1);
276
+ return;
277
+ }
278
+
279
+ // Navigation up
280
+ if (input === "k" || key.upArrow) {
281
+ navigateToIndex(selectedIndex - 1);
282
+ return;
283
+ }
284
+
285
+ // Page down
286
+ if (key.pageDown || (key.ctrl && input === "d")) {
287
+ navigateToIndex(selectedIndex + PAGE_SIZE);
288
+ return;
289
+ }
290
+
291
+ // Page up
292
+ if (key.pageUp || (key.ctrl && input === "u")) {
293
+ navigateToIndex(selectedIndex - PAGE_SIZE);
294
+ return;
295
+ }
296
+
297
+ // Restore
298
+ if (input === "r" && snapshots.length > 0) {
299
+ const snapshot = snapshots[selectedIndex];
300
+ if (snapshot) {
301
+ onRestore?.(snapshot.hash);
302
+ }
303
+ return;
304
+ }
305
+
306
+ // Diff
307
+ if (input === "d" && snapshots.length > 0) {
308
+ const snapshot = snapshots[selectedIndex];
309
+ if (snapshot) {
310
+ onDiff?.(snapshot.hash);
311
+ }
312
+ return;
313
+ }
314
+
315
+ // New checkpoint
316
+ if (input === "n") {
317
+ onTakeCheckpoint?.();
318
+ return;
319
+ }
320
+
321
+ // Refresh
322
+ if (input === "R") {
323
+ onRefresh?.();
324
+ return;
325
+ }
326
+ },
327
+ { isActive: isFocused }
328
+ );
329
+
330
+ // Loading state
331
+ if (isLoading) {
332
+ return (
333
+ <Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
334
+ <Box>
335
+ <Text color={primaryColor} bold>
336
+ [*] Checkpoints
337
+ </Text>
338
+ </Box>
339
+ <Box paddingY={1}>
340
+ <Text color={mutedColor}>Loading...</Text>
341
+ </Box>
342
+ </Box>
343
+ );
344
+ }
345
+
346
+ // Error state
347
+ if (error) {
348
+ return (
349
+ <Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
350
+ <Box>
351
+ <Text color={primaryColor} bold>
352
+ [*] Checkpoints
353
+ </Text>
354
+ </Box>
355
+ <Box paddingY={1}>
356
+ <Text color={errorColor}>Error: {error}</Text>
357
+ </Box>
358
+ <HotkeyHints hints={[{ keys: "n", label: "initialize" }]} />
359
+ </Box>
360
+ );
361
+ }
362
+
363
+ // Not initialized state
364
+ if (!isInitialized) {
365
+ return (
366
+ <Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
367
+ <Box>
368
+ <Text color={primaryColor} bold>
369
+ [*] Checkpoints
370
+ </Text>
371
+ </Box>
372
+ <Box paddingY={1}>
373
+ <Text color={mutedColor}>Not initialized. Press 'n' to create first checkpoint.</Text>
374
+ </Box>
375
+ <HotkeyHints hints={[{ keys: "n", label: "new checkpoint" }]} />
376
+ </Box>
377
+ );
378
+ }
379
+
380
+ // Empty state
381
+ if (snapshots.length === 0) {
382
+ return (
383
+ <Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
384
+ <Box>
385
+ <Text color={primaryColor} bold>
386
+ [*] Checkpoints (0)
387
+ </Text>
388
+ </Box>
389
+ <Box paddingY={1}>
390
+ <Text color={mutedColor}>No checkpoints yet. Press 'n' to create one.</Text>
391
+ </Box>
392
+ <HotkeyHints hints={[{ keys: "n", label: "new checkpoint" }]} />
393
+ </Box>
394
+ );
395
+ }
396
+
397
+ return (
398
+ <Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
399
+ {/* Header */}
400
+ <Box marginBottom={1}>
401
+ <Text color={primaryColor} bold>
402
+ [*] Checkpoints ({snapshots.length})
403
+ </Text>
404
+ {canScrollUp && <Text color={mutedColor}> [-] more above</Text>}
405
+ {canScrollDown && <Text color={mutedColor}> [+] more below</Text>}
406
+ </Box>
407
+
408
+ {/* Separator */}
409
+ <Box>
410
+ <Text color={borderColor}>{"─".repeat(40)}</Text>
411
+ </Box>
412
+
413
+ {/* Snapshot list */}
414
+ <Box flexDirection="column">
415
+ {visibleSnapshots.map((snapshot, visibleIdx) => {
416
+ const actualIndex = scrollOffset + visibleIdx;
417
+ return (
418
+ <SnapshotItem
419
+ key={snapshot.hash}
420
+ snapshot={snapshot}
421
+ index={actualIndex}
422
+ isSelected={actualIndex === selectedIndex}
423
+ width={60}
424
+ primaryColor={primaryColor}
425
+ mutedColor={mutedColor}
426
+ textColor={textColor}
427
+ />
428
+ );
429
+ })}
430
+ </Box>
431
+
432
+ {/* Hints */}
433
+ <Box marginTop={1}>
434
+ <HotkeyHints hints={hints} />
435
+ </Box>
436
+ </Box>
437
+ );
438
+ }
439
+
440
+ export default SnapshotCheckpointPanel;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Checkpoint Components
3
+ *
4
+ * React Ink components for managing file state checkpoints.
5
+ * Uses the Snapshot system (shadow Git repository) for tracking file states.
6
+ *
7
+ * @module tui/components/Checkpoint
8
+ */
9
+
10
+ export type { CheckpointDiffViewProps } from "./CheckpointDiffView.js";
11
+ export {
12
+ CheckpointDiffView,
13
+ default as CheckpointDiffViewDefault,
14
+ } from "./CheckpointDiffView.js";
15
+ export type { SnapshotCheckpointPanelProps } from "./SnapshotCheckpointPanel.js";
16
+ export {
17
+ default as SnapshotCheckpointPanelDefault,
18
+ SnapshotCheckpointPanel,
19
+ } from "./SnapshotCheckpointPanel.js";
@@ -0,0 +1,226 @@
1
+ /**
2
+ * CostDisplay Component (Phase 35)
3
+ *
4
+ * React Ink component for displaying current session cost information.
5
+ * Shows token usage and cost in USD.
6
+ *
7
+ * @module tui/components/CostDisplay
8
+ */
9
+
10
+ import type { CostBreakdown } from "@vellum/core";
11
+ import { formatCost, formatTokenCount } from "@vellum/core";
12
+ import { getIcons } from "@vellum/shared";
13
+ import { Box, Text } from "ink";
14
+ import { useTUITranslation } from "../i18n/index.js";
15
+ import { useTheme } from "../theme/index.js";
16
+
17
+ // =============================================================================
18
+ // Types
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Props for the CostDisplay component.
23
+ */
24
+ export interface CostDisplayProps {
25
+ /** Total input tokens used */
26
+ readonly inputTokens: number;
27
+ /** Total output tokens generated */
28
+ readonly outputTokens: number;
29
+ /** Total cost in USD */
30
+ readonly totalCost: number;
31
+ /** Optional detailed cost breakdown */
32
+ readonly breakdown?: CostBreakdown;
33
+ /** Whether to show compact view (default: false) */
34
+ readonly compact?: boolean;
35
+ /** Whether to show breakdown details (default: false) */
36
+ readonly showBreakdown?: boolean;
37
+ }
38
+
39
+ // =============================================================================
40
+ // CostDisplay Component
41
+ // =============================================================================
42
+
43
+ /**
44
+ * CostDisplay - Shows current session cost and token usage.
45
+ *
46
+ * Features:
47
+ * - Compact mode: Single line with total tokens and cost
48
+ * - Full mode: Multi-line with detailed breakdown
49
+ * - Color-coded based on cost magnitude
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * <CostDisplay
54
+ * inputTokens={1500}
55
+ * outputTokens={800}
56
+ * totalCost={0.0045}
57
+ * />
58
+ * ```
59
+ */
60
+ export function CostDisplay({
61
+ inputTokens,
62
+ outputTokens,
63
+ totalCost,
64
+ breakdown,
65
+ compact = false,
66
+ showBreakdown = false,
67
+ }: CostDisplayProps): React.JSX.Element {
68
+ const { theme } = useTheme();
69
+ const { t } = useTUITranslation();
70
+ const icons = getIcons();
71
+
72
+ // Determine cost color based on magnitude
73
+ const costColor = getCostColor(totalCost, theme);
74
+
75
+ // Total tokens
76
+ const totalTokens = inputTokens + outputTokens;
77
+
78
+ if (compact) {
79
+ return (
80
+ <Box>
81
+ <Text dimColor>{icons.cost} </Text>
82
+ <Text color={theme.colors.muted}>
83
+ {formatTokenCount(totalTokens)} {t("cost.tokens")}
84
+ </Text>
85
+ <Text dimColor> • </Text>
86
+ <Text color={costColor} bold>
87
+ {formatCost(totalCost)}
88
+ </Text>
89
+ </Box>
90
+ );
91
+ }
92
+
93
+ return (
94
+ <Box flexDirection="column" paddingX={1}>
95
+ <Box marginBottom={1}>
96
+ <Text color={theme.colors.primary} bold>
97
+ {icons.cost} {t("cost.sessionCost")}
98
+ </Text>
99
+ </Box>
100
+
101
+ {/* Token Summary */}
102
+ <Box flexDirection="column" marginLeft={2}>
103
+ <Box>
104
+ <Text color={theme.colors.muted}>{t("cost.input")}</Text>
105
+ <Text>{formatTokenCount(inputTokens)}</Text>
106
+ <Text color={theme.colors.muted}> {t("cost.tokens")}</Text>
107
+ </Box>
108
+ <Box>
109
+ <Text color={theme.colors.muted}>{t("cost.output")}</Text>
110
+ <Text>{formatTokenCount(outputTokens)}</Text>
111
+ <Text color={theme.colors.muted}> {t("cost.tokens")}</Text>
112
+ </Box>
113
+ <Box marginTop={1}>
114
+ <Text color={theme.colors.muted}>{t("cost.total")}</Text>
115
+ <Text bold>{formatTokenCount(totalTokens)}</Text>
116
+ <Text color={theme.colors.muted}> {t("cost.tokens")}</Text>
117
+ </Box>
118
+ </Box>
119
+
120
+ {/* Cost Breakdown */}
121
+ {showBreakdown && breakdown && (
122
+ <Box flexDirection="column" marginLeft={2} marginTop={1}>
123
+ <Text color={theme.colors.muted} dimColor>
124
+ ─────────────────
125
+ </Text>
126
+ {breakdown.input > 0 && (
127
+ <Box>
128
+ <Text color={theme.colors.muted}>{t("cost.input")}</Text>
129
+ <Text>{formatCost(breakdown.input)}</Text>
130
+ </Box>
131
+ )}
132
+ {breakdown.output > 0 && (
133
+ <Box>
134
+ <Text color={theme.colors.muted}>{t("cost.output")}</Text>
135
+ <Text>{formatCost(breakdown.output)}</Text>
136
+ </Box>
137
+ )}
138
+ {breakdown.cacheRead > 0 && (
139
+ <Box>
140
+ <Text color={theme.colors.muted}>{t("cost.cacheRead")}</Text>
141
+ <Text>{formatCost(breakdown.cacheRead)}</Text>
142
+ </Box>
143
+ )}
144
+ {breakdown.cacheWrite > 0 && (
145
+ <Box>
146
+ <Text color={theme.colors.muted}>{t("cost.cacheWrite")}</Text>
147
+ <Text>{formatCost(breakdown.cacheWrite)}</Text>
148
+ </Box>
149
+ )}
150
+ {breakdown.reasoning > 0 && (
151
+ <Box>
152
+ <Text color={theme.colors.muted}>{t("cost.reasoning")}</Text>
153
+ <Text>{formatCost(breakdown.reasoning)}</Text>
154
+ </Box>
155
+ )}
156
+ </Box>
157
+ )}
158
+
159
+ {/* Total Cost */}
160
+ <Box marginLeft={2} marginTop={1}>
161
+ <Text color={theme.colors.muted} dimColor>
162
+ ─────────────────
163
+ </Text>
164
+ </Box>
165
+ <Box marginLeft={2}>
166
+ <Text color={theme.colors.muted}>{t("cost.cost")}</Text>
167
+ <Text color={costColor} bold>
168
+ {formatCost(totalCost)}
169
+ </Text>
170
+ </Box>
171
+ </Box>
172
+ );
173
+ }
174
+
175
+ // =============================================================================
176
+ // Helper Functions
177
+ // =============================================================================
178
+
179
+ /**
180
+ * Get appropriate color for cost display based on magnitude.
181
+ */
182
+ function getCostColor(cost: number, theme: ReturnType<typeof useTheme>["theme"]): string {
183
+ if (cost === 0) {
184
+ return theme.colors.muted;
185
+ }
186
+ if (cost < 0.01) {
187
+ return theme.colors.success; // Green - very low cost
188
+ }
189
+ if (cost < 0.1) {
190
+ return theme.colors.info; // Blue - moderate cost
191
+ }
192
+ if (cost < 1.0) {
193
+ return theme.colors.warning; // Yellow - getting expensive
194
+ }
195
+ return theme.colors.error; // Red - expensive
196
+ }
197
+
198
+ // =============================================================================
199
+ // Compact Cost Badge
200
+ // =============================================================================
201
+
202
+ /**
203
+ * Props for the CostBadge component.
204
+ */
205
+ export interface CostBadgeProps {
206
+ /** Total cost in USD */
207
+ readonly cost: number;
208
+ }
209
+
210
+ /**
211
+ * CostBadge - Minimal cost display for status bars.
212
+ *
213
+ * @example
214
+ * ```tsx
215
+ * <CostBadge cost={0.0045} />
216
+ * // Renders: $0.0045
217
+ * ```
218
+ */
219
+ export function CostBadge({ cost }: CostBadgeProps): React.JSX.Element {
220
+ const { theme } = useTheme();
221
+ const costColor = getCostColor(cost, theme);
222
+
223
+ return <Text color={costColor}>{formatCost(cost)}</Text>;
224
+ }
225
+
226
+ export default CostDisplay;