@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,323 @@
1
+ /**
2
+ * CheckpointPanel Component
3
+ *
4
+ * Modal panel for managing session checkpoints.
5
+ * Allows viewing, creating, and rolling back to checkpoints.
6
+ *
7
+ * @module tui/components/session/CheckpointPanel
8
+ */
9
+
10
+ import type { SessionCheckpoint } from "@vellum/core";
11
+ import { Box, Text, useInput } from "ink";
12
+ import type React from "react";
13
+ import { useCallback, useState } from "react";
14
+ import { useTUITranslation } from "../../i18n/index.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 CheckpointPanel component
24
+ */
25
+ export interface CheckpointPanelProps {
26
+ /** List of checkpoints to display */
27
+ readonly checkpoints: readonly SessionCheckpoint[];
28
+ /** Current message count (for calculating messages to lose) */
29
+ readonly currentMessageCount: number;
30
+ /** Whether the panel is open */
31
+ readonly isOpen: boolean;
32
+ /** Callback when a checkpoint is selected for rollback */
33
+ readonly onRollback: (checkpointId: string) => void;
34
+ /** Callback when a new checkpoint is requested */
35
+ readonly onCreateCheckpoint: (description?: string) => void;
36
+ /** Callback when a checkpoint is deleted */
37
+ readonly onDeleteCheckpoint: (checkpointId: string) => void;
38
+ /** Callback to close the panel */
39
+ readonly onClose: () => void;
40
+ }
41
+
42
+ // =============================================================================
43
+ // Helper Functions
44
+ // =============================================================================
45
+
46
+ /**
47
+ * Format a date for display.
48
+ */
49
+ function formatDate(date: Date): string {
50
+ const now = new Date();
51
+ const isToday =
52
+ date.getDate() === now.getDate() &&
53
+ date.getMonth() === now.getMonth() &&
54
+ date.getFullYear() === now.getFullYear();
55
+
56
+ if (isToday) {
57
+ return date.toLocaleTimeString(undefined, {
58
+ hour: "2-digit",
59
+ minute: "2-digit",
60
+ });
61
+ }
62
+
63
+ return date.toLocaleDateString(undefined, {
64
+ month: "short",
65
+ day: "numeric",
66
+ hour: "2-digit",
67
+ minute: "2-digit",
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Truncate text with ellipsis.
73
+ * Uses string-width for accurate CJK/Emoji handling.
74
+ */
75
+ function truncate(text: string, maxLength: number): string {
76
+ return truncateToDisplayWidth(text, maxLength);
77
+ }
78
+
79
+ // =============================================================================
80
+ // CheckpointItem Sub-Component
81
+ // =============================================================================
82
+
83
+ interface CheckpointItemProps {
84
+ readonly checkpoint: SessionCheckpoint;
85
+ readonly isSelected: boolean;
86
+ readonly messagesToLose: number;
87
+ readonly primaryColor: string;
88
+ readonly warningColor: string;
89
+ readonly textColor: string;
90
+ readonly mutedColor: string;
91
+ }
92
+
93
+ function CheckpointItem({
94
+ checkpoint,
95
+ isSelected,
96
+ messagesToLose,
97
+ primaryColor,
98
+ warningColor,
99
+ textColor,
100
+ mutedColor,
101
+ }: CheckpointItemProps): React.JSX.Element {
102
+ const indicator = isSelected ? "▶" : " ";
103
+ const description = checkpoint.description ?? "(no description)";
104
+ const idShort = checkpoint.id.slice(0, 8);
105
+ const dateStr = formatDate(new Date(checkpoint.createdAt));
106
+
107
+ return (
108
+ <Box flexDirection="row" paddingX={1}>
109
+ <Text color={isSelected ? primaryColor : mutedColor}>{indicator} </Text>
110
+ <Box flexDirection="row" justifyContent="space-between" flexGrow={1}>
111
+ <Box flexDirection="row" gap={1}>
112
+ <Text color={isSelected ? primaryColor : mutedColor} dimColor={!isSelected}>
113
+ [{idShort}]
114
+ </Text>
115
+ <Text color={isSelected ? primaryColor : textColor} bold={isSelected}>
116
+ {truncate(description, 30)}
117
+ </Text>
118
+ </Box>
119
+ <Box flexDirection="row" gap={1}>
120
+ <Text color={mutedColor}>{dateStr}</Text>
121
+ <Text color={mutedColor}>|</Text>
122
+ <Text color={mutedColor}>msg #{checkpoint.messageIndex}</Text>
123
+ {messagesToLose > 0 && <Text color={warningColor}>(-{messagesToLose})</Text>}
124
+ </Box>
125
+ </Box>
126
+ </Box>
127
+ );
128
+ }
129
+
130
+ // =============================================================================
131
+ // Main Component
132
+ // =============================================================================
133
+
134
+ /**
135
+ * CheckpointPanel displays and manages session checkpoints.
136
+ *
137
+ * Keybindings:
138
+ * - j/k or ↑/↓: Navigate checkpoints
139
+ * - Enter: Rollback to selected checkpoint
140
+ * - n: Create new checkpoint
141
+ * - d: Delete selected checkpoint
142
+ * - Escape/q: Close panel
143
+ *
144
+ * @example
145
+ * ```tsx
146
+ * <CheckpointPanel
147
+ * checkpoints={checkpoints}
148
+ * currentMessageCount={messages.length}
149
+ * isOpen={showPanel}
150
+ * onRollback={(id) => handleRollback(id)}
151
+ * onCreateCheckpoint={(desc) => handleCreate(desc)}
152
+ * onDeleteCheckpoint={(id) => handleDelete(id)}
153
+ * onClose={() => setShowPanel(false)}
154
+ * />
155
+ * ```
156
+ */
157
+ export function CheckpointPanel({
158
+ checkpoints,
159
+ currentMessageCount,
160
+ isOpen,
161
+ onRollback,
162
+ onCreateCheckpoint,
163
+ onDeleteCheckpoint,
164
+ onClose,
165
+ }: CheckpointPanelProps): React.JSX.Element | null {
166
+ const { theme } = useTheme();
167
+ const { t } = useTUITranslation();
168
+
169
+ // Selection state
170
+ const [selectedIndex, setSelectedIndex] = useState(0);
171
+
172
+ // Get colors from theme
173
+ const primaryColor = theme.brand.primary;
174
+ const warningColor = theme.colors.warning;
175
+ const textColor = theme.semantic.text.primary;
176
+ const mutedColor = theme.semantic.text.muted;
177
+ const borderColor = theme.semantic.border.default;
178
+
179
+ /**
180
+ * Calculate messages that would be lost for a checkpoint.
181
+ */
182
+ const getMessagesToLose = useCallback(
183
+ (checkpoint: SessionCheckpoint): number => {
184
+ return currentMessageCount - checkpoint.messageIndex;
185
+ },
186
+ [currentMessageCount]
187
+ );
188
+
189
+ /**
190
+ * Handle navigation keys (j/k, arrows)
191
+ */
192
+ const handleNavigation = useCallback(
193
+ (input: string, key: { downArrow: boolean; upArrow: boolean }): boolean => {
194
+ if (input === "j" || key.downArrow) {
195
+ setSelectedIndex((prev) => Math.min(prev + 1, checkpoints.length - 1));
196
+ return true;
197
+ }
198
+ if (input === "k" || key.upArrow) {
199
+ setSelectedIndex((prev) => Math.max(prev - 1, 0));
200
+ return true;
201
+ }
202
+ return false;
203
+ },
204
+ [checkpoints.length]
205
+ );
206
+
207
+ /**
208
+ * Handle action keys (Enter, n, d)
209
+ */
210
+ const handleAction = useCallback(
211
+ (input: string, key: { return: boolean }): boolean => {
212
+ if (key.return && checkpoints.length > 0) {
213
+ const checkpoint = checkpoints[selectedIndex];
214
+ if (checkpoint) {
215
+ onRollback(checkpoint.id);
216
+ }
217
+ return true;
218
+ }
219
+ if (input === "n") {
220
+ onCreateCheckpoint();
221
+ return true;
222
+ }
223
+ if (input === "d" && checkpoints.length > 0) {
224
+ const checkpoint = checkpoints[selectedIndex];
225
+ if (checkpoint) {
226
+ onDeleteCheckpoint(checkpoint.id);
227
+ if (selectedIndex >= checkpoints.length - 1) {
228
+ setSelectedIndex(Math.max(0, checkpoints.length - 2));
229
+ }
230
+ }
231
+ return true;
232
+ }
233
+ return false;
234
+ },
235
+ [checkpoints, selectedIndex, onRollback, onCreateCheckpoint, onDeleteCheckpoint]
236
+ );
237
+
238
+ /**
239
+ * Handle keyboard input.
240
+ */
241
+ useInput(
242
+ (input, key) => {
243
+ if (handleNavigation(input, key)) return;
244
+ if (handleAction(input, key)) return;
245
+ if (key.escape || input === "q") {
246
+ onClose();
247
+ }
248
+ },
249
+ { isActive: isOpen }
250
+ );
251
+
252
+ if (!isOpen) {
253
+ return null;
254
+ }
255
+
256
+ const isEmpty = checkpoints.length === 0;
257
+
258
+ return (
259
+ <Box
260
+ flexDirection="column"
261
+ borderStyle="double"
262
+ borderColor={borderColor}
263
+ paddingX={1}
264
+ paddingY={0}
265
+ >
266
+ {/* Header */}
267
+ <Box flexDirection="row" justifyContent="space-between" marginBottom={1}>
268
+ <Text bold color={primaryColor}>
269
+ 📌 {t("persistence.checkpoint.title")}
270
+ </Text>
271
+ <Text dimColor>j/k navigate • Enter rollback • n new • d delete • Esc close</Text>
272
+ </Box>
273
+
274
+ {/* Checkpoint List */}
275
+ {isEmpty ? (
276
+ <Box paddingX={1} paddingY={1}>
277
+ <Text color={mutedColor}>{t("persistence.checkpoint.empty")}</Text>
278
+ </Box>
279
+ ) : (
280
+ <Box flexDirection="column">
281
+ {checkpoints.map((checkpoint, index) => (
282
+ <CheckpointItem
283
+ key={checkpoint.id}
284
+ checkpoint={checkpoint}
285
+ isSelected={index === selectedIndex}
286
+ messagesToLose={getMessagesToLose(checkpoint)}
287
+ primaryColor={primaryColor}
288
+ warningColor={warningColor}
289
+ textColor={textColor}
290
+ mutedColor={mutedColor}
291
+ />
292
+ ))}
293
+ </Box>
294
+ )}
295
+
296
+ {/* Footer - Warning for selected checkpoint */}
297
+ {!isEmpty &&
298
+ (() => {
299
+ const selectedCheckpoint = checkpoints[selectedIndex];
300
+ if (!selectedCheckpoint) return null;
301
+ const messagesToLose = getMessagesToLose(selectedCheckpoint);
302
+ return (
303
+ <Box marginTop={1} paddingX={1}>
304
+ {messagesToLose > 0 ? (
305
+ <Text color={warningColor}>
306
+ ⚠ Rollback will remove {messagesToLose} message
307
+ {messagesToLose === 1 ? "" : "s"}
308
+ </Text>
309
+ ) : (
310
+ <Text color={mutedColor}>Already at this checkpoint</Text>
311
+ )}
312
+ </Box>
313
+ );
314
+ })()}
315
+ </Box>
316
+ );
317
+ }
318
+
319
+ // =============================================================================
320
+ // Exports
321
+ // =============================================================================
322
+
323
+ export default CheckpointPanel;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * RollbackDialog Component
3
+ *
4
+ * Modal confirmation dialog for session rollback operations.
5
+ * Displays a warning about data loss and requires user confirmation.
6
+ *
7
+ * @module tui/components/session/RollbackDialog
8
+ */
9
+
10
+ import type { SessionCheckpoint } from "@vellum/core";
11
+ import { Box, Text, useInput } from "ink";
12
+ import type React from "react";
13
+ import { useTUITranslation } from "../../i18n/index.js";
14
+ import { useTheme } from "../../theme/index.js";
15
+
16
+ // =============================================================================
17
+ // Types
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Props for the RollbackDialog component
22
+ */
23
+ export interface RollbackDialogProps {
24
+ /** Checkpoint to rollback to */
25
+ readonly checkpoint: SessionCheckpoint | null;
26
+ /** Number of messages that will be lost */
27
+ readonly messagesToLose: number;
28
+ /** Whether the dialog is open */
29
+ readonly isOpen: boolean;
30
+ /** Callback when user confirms rollback */
31
+ readonly onConfirm: () => void;
32
+ /** Callback when user cancels */
33
+ readonly onCancel: () => void;
34
+ }
35
+
36
+ // =============================================================================
37
+ // Main Component
38
+ // =============================================================================
39
+
40
+ /**
41
+ * RollbackDialog displays a confirmation prompt for rollback operations.
42
+ *
43
+ * Features:
44
+ * - Shows checkpoint details
45
+ * - Displays warning about message loss
46
+ * - y/n confirmation
47
+ * - Red warning styling
48
+ *
49
+ * Keybindings:
50
+ * - y/Y/Enter: Confirm rollback
51
+ * - n/N/Escape: Cancel
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * <RollbackDialog
56
+ * checkpoint={selectedCheckpoint}
57
+ * messagesToLose={5}
58
+ * isOpen={showDialog}
59
+ * onConfirm={() => {
60
+ * handleRollback();
61
+ * setShowDialog(false);
62
+ * }}
63
+ * onCancel={() => setShowDialog(false)}
64
+ * />
65
+ * ```
66
+ */
67
+ export function RollbackDialog({
68
+ checkpoint,
69
+ messagesToLose,
70
+ isOpen,
71
+ onConfirm,
72
+ onCancel,
73
+ }: RollbackDialogProps): React.JSX.Element | null {
74
+ const { theme } = useTheme();
75
+ const { t } = useTUITranslation();
76
+
77
+ // Get colors from theme
78
+ const errorColor = theme.colors.error;
79
+ const warningColor = theme.colors.warning;
80
+ const successColor = theme.colors.success;
81
+ const textColor = theme.semantic.text.primary;
82
+ const mutedColor = theme.semantic.text.muted;
83
+ const borderColor = errorColor; // Use error color for border to emphasize danger
84
+
85
+ /**
86
+ * Handle keyboard input
87
+ */
88
+ useInput(
89
+ (input, key) => {
90
+ // Confirm: y, Y, or Enter
91
+ if (input === "y" || input === "Y" || key.return) {
92
+ onConfirm();
93
+ return;
94
+ }
95
+
96
+ // Cancel: n, N, or Escape
97
+ if (input === "n" || input === "N" || key.escape) {
98
+ onCancel();
99
+ return;
100
+ }
101
+ },
102
+ { isActive: isOpen }
103
+ );
104
+
105
+ if (!isOpen || !checkpoint) {
106
+ return null;
107
+ }
108
+
109
+ const checkpointDescription = checkpoint.description ?? "(no description)";
110
+ const checkpointIdShort = checkpoint.id.slice(0, 8);
111
+
112
+ return (
113
+ <Box
114
+ flexDirection="column"
115
+ borderStyle="double"
116
+ borderColor={borderColor}
117
+ paddingX={2}
118
+ paddingY={1}
119
+ >
120
+ {/* Title */}
121
+ <Box marginBottom={1}>
122
+ <Text bold color={errorColor}>
123
+ ⚠️ {t("persistence.rollback.confirm")}
124
+ </Text>
125
+ </Box>
126
+
127
+ {/* Checkpoint Info */}
128
+ <Box flexDirection="column" marginBottom={1}>
129
+ <Text color={textColor}>
130
+ Checkpoint: <Text color={mutedColor}>[{checkpointIdShort}]</Text>{" "}
131
+ <Text bold>{checkpointDescription}</Text>
132
+ </Text>
133
+ <Text color={mutedColor}>Message index: {checkpoint.messageIndex}</Text>
134
+ </Box>
135
+
136
+ {/* Warning Message */}
137
+ <Box marginBottom={1} paddingX={1}>
138
+ <Text color={warningColor} bold>
139
+ {t("persistence.rollback.warning", { count: messagesToLose })}
140
+ </Text>
141
+ </Box>
142
+
143
+ {/* Emphasis on irreversibility */}
144
+ <Box marginBottom={1}>
145
+ <Text color={errorColor} dimColor>
146
+ This action cannot be undone.
147
+ </Text>
148
+ </Box>
149
+
150
+ {/* Action Buttons */}
151
+ <Box flexDirection="row" gap={2}>
152
+ <Box>
153
+ <Text color={successColor}>[y]</Text>
154
+ <Text color={textColor}> Confirm</Text>
155
+ </Box>
156
+ <Box>
157
+ <Text color={errorColor}>[n]</Text>
158
+ <Text color={textColor}> Cancel</Text>
159
+ </Box>
160
+ </Box>
161
+ </Box>
162
+ );
163
+ }
164
+
165
+ // =============================================================================
166
+ // Exports
167
+ // =============================================================================
168
+
169
+ export default RollbackDialog;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * SessionItem Component (T056)
3
+ *
4
+ * Displays a single session in the session list with title,
5
+ * last message preview, and timestamp.
6
+ *
7
+ * @module tui/components/session/SessionItem
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import type React from "react";
12
+ import { useTheme } from "../../theme/index.js";
13
+ import { truncateToDisplayWidth } from "../../utils/index.js";
14
+ import type { SessionItemProps } from "./types.js";
15
+
16
+ // =============================================================================
17
+ // Helper Functions
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Format a timestamp for display.
22
+ * Shows time if today, otherwise shows date.
23
+ */
24
+ function formatTimestamp(date: Date): string {
25
+ const now = new Date();
26
+ const isToday =
27
+ date.getDate() === now.getDate() &&
28
+ date.getMonth() === now.getMonth() &&
29
+ date.getFullYear() === now.getFullYear();
30
+
31
+ if (isToday) {
32
+ return date.toLocaleTimeString(undefined, {
33
+ hour: "2-digit",
34
+ minute: "2-digit",
35
+ });
36
+ }
37
+
38
+ return date.toLocaleDateString(undefined, {
39
+ month: "short",
40
+ day: "numeric",
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Truncate text to a maximum display width with ellipsis.
46
+ * Uses string-width for accurate CJK/Emoji handling.
47
+ */
48
+ function truncateText(text: string, maxLength: number): string {
49
+ return truncateToDisplayWidth(text, maxLength);
50
+ }
51
+
52
+ // =============================================================================
53
+ // Main Component
54
+ // =============================================================================
55
+
56
+ /**
57
+ * SessionItem displays a single session entry.
58
+ *
59
+ * Features:
60
+ * - Shows session title with truncation
61
+ * - Displays last message preview (muted)
62
+ * - Shows relative timestamp
63
+ * - Visual indicators for selected/active states
64
+ * - Message count badge
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * <SessionItem
69
+ * session={{
70
+ * id: "sess-1",
71
+ * title: "Debug React app",
72
+ * lastMessage: "I'll help you fix that...",
73
+ * timestamp: new Date(),
74
+ * messageCount: 12
75
+ * }}
76
+ * isSelected={true}
77
+ * isActive={false}
78
+ * onSelect={(id) => handleSelect(id)}
79
+ * />
80
+ * ```
81
+ */
82
+ export function SessionItem({
83
+ session,
84
+ isSelected = false,
85
+ isActive = false,
86
+ }: SessionItemProps): React.JSX.Element {
87
+ const { theme } = useTheme();
88
+
89
+ const textColor = theme.semantic.text.primary;
90
+ const mutedColor = theme.semantic.text.muted;
91
+ const primaryColor = theme.colors.primary;
92
+ const successColor = theme.colors.success;
93
+
94
+ // Determine background indicator
95
+ const indicator = isSelected ? "▶" : isActive ? "●" : " ";
96
+ const indicatorColor = isSelected ? primaryColor : isActive ? successColor : mutedColor;
97
+
98
+ // Truncate title and last message for display
99
+ const displayTitle = truncateText(session.title, 40);
100
+ const displayMessage = session.lastMessage ? truncateText(session.lastMessage, 50) : "";
101
+
102
+ return (
103
+ <Box
104
+ flexDirection="column"
105
+ paddingX={1}
106
+ paddingY={0}
107
+ borderStyle={isSelected ? "single" : undefined}
108
+ borderColor={isSelected ? primaryColor : undefined}
109
+ >
110
+ {/* Title row with indicator and timestamp */}
111
+ <Box flexDirection="row" justifyContent="space-between">
112
+ <Box flexDirection="row" gap={1}>
113
+ <Text color={indicatorColor}>{indicator}</Text>
114
+ <Text color={isSelected ? primaryColor : textColor} bold={isSelected || isActive}>
115
+ {displayTitle}
116
+ </Text>
117
+ </Box>
118
+ <Box flexDirection="row" gap={1}>
119
+ <Text dimColor>({session.messageCount})</Text>
120
+ <Text color={mutedColor}>{formatTimestamp(session.timestamp)}</Text>
121
+ </Box>
122
+ </Box>
123
+
124
+ {/* Last message preview */}
125
+ {displayMessage && (
126
+ <Box marginLeft={2}>
127
+ <Text color={mutedColor} wrap="truncate">
128
+ {displayMessage}
129
+ </Text>
130
+ </Box>
131
+ )}
132
+ </Box>
133
+ );
134
+ }
135
+
136
+ export default SessionItem;