@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,89 @@
1
+ /**
2
+ * ProtectedFileLegend Component
3
+ *
4
+ * Legend indicator for protected files in directory listings.
5
+ * Shows [#] = Protected file explanation when protected files are present.
6
+ *
7
+ * @module tui/components/common/ProtectedFileLegend
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import type React from "react";
12
+ import { memo } from "react";
13
+
14
+ // =============================================================================
15
+ // Constants
16
+ // =============================================================================
17
+
18
+ /** Protected file indicator matching core/permission/protected-files.ts */
19
+ export const PROTECTED_INDICATOR = "[#]" as const;
20
+
21
+ // =============================================================================
22
+ // Types
23
+ // =============================================================================
24
+
25
+ /**
26
+ * Props for ProtectedFileLegend component.
27
+ */
28
+ export interface ProtectedFileLegendProps {
29
+ /** Whether to show the legend (typically when hasProtectedFiles is true) */
30
+ readonly show?: boolean;
31
+ }
32
+
33
+ // =============================================================================
34
+ // Component
35
+ // =============================================================================
36
+
37
+ /**
38
+ * Legend explaining the protected file indicator.
39
+ *
40
+ * Displays a dim, single-line hint at the bottom of file listings
41
+ * when protected files are present.
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * <ProtectedFileLegend show={hasProtectedFiles} />
46
+ * // Renders: [#] = Protected file (always requires approval)
47
+ * ```
48
+ */
49
+ function ProtectedFileLegendImpl({ show = false }: ProtectedFileLegendProps): React.JSX.Element {
50
+ if (!show) {
51
+ return <Box />;
52
+ }
53
+
54
+ return (
55
+ <Box marginTop={1}>
56
+ <Text dimColor>
57
+ <Text bold>{PROTECTED_INDICATOR}</Text>
58
+ <Text> = Protected file (always requires approval)</Text>
59
+ </Text>
60
+ </Box>
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Memoized ProtectedFileLegend component.
66
+ * Only re-renders when show prop changes.
67
+ */
68
+ export const ProtectedFileLegend = memo(ProtectedFileLegendImpl);
69
+
70
+ // =============================================================================
71
+ // Formatting Utilities
72
+ // =============================================================================
73
+
74
+ /**
75
+ * Format a file entry name with protection indicator.
76
+ *
77
+ * @param name - File name
78
+ * @param isProtected - Whether the file is protected
79
+ * @returns Formatted name with [#] prefix if protected
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * formatProtectedFileName('.env', true); // '[#] .env'
84
+ * formatProtectedFileName('app.ts', false); // 'app.ts'
85
+ * ```
86
+ */
87
+ export function formatProtectedFileName(name: string, isProtected?: boolean): string {
88
+ return isProtected ? `${PROTECTED_INDICATOR} ${name}` : name;
89
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * ScrollIndicator Component
3
+ *
4
+ * Minimal ASCII scrollbar for terminal UIs.
5
+ * Displays a vertical track with a proportionally-sized thumb.
6
+ * Supports animated colors via useAnimatedScrollbar hook.
7
+ *
8
+ * @module tui/components/common/ScrollIndicator
9
+ */
10
+
11
+ import { Box, Text } from "ink";
12
+ import type React from "react";
13
+ import { useMemo } from "react";
14
+ import { useTheme } from "../../theme/index.js";
15
+
16
+ // =============================================================================
17
+ // Types
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Props for ScrollIndicator component.
22
+ */
23
+ export interface ScrollIndicatorProps {
24
+ /** Total content height in lines */
25
+ readonly totalHeight: number;
26
+ /** Current offset from bottom */
27
+ readonly offsetFromBottom: number;
28
+ /** Viewport height */
29
+ readonly viewportHeight: number;
30
+ /** Whether to show (default: auto based on content > viewport) */
31
+ readonly show?: boolean;
32
+ /** Animated thumb color (from useAnimatedScrollbar) */
33
+ readonly thumbColor?: string;
34
+ /** Animated track color (from useAnimatedScrollbar) */
35
+ readonly trackColor?: string;
36
+ }
37
+
38
+ // =============================================================================
39
+ // Constants
40
+ // =============================================================================
41
+
42
+ /** Scrollbar characters */
43
+ const CHARS = {
44
+ thumb: "█",
45
+ track: "│",
46
+ } as const;
47
+
48
+ /** Minimum thumb size in lines */
49
+ const MIN_THUMB_SIZE = 1;
50
+
51
+ // =============================================================================
52
+ // Component
53
+ // =============================================================================
54
+
55
+ /**
56
+ * Vertical scrollbar indicator for terminal UIs.
57
+ *
58
+ * Renders a minimal ASCII scrollbar. Thumb size is proportional
59
+ * to viewport/content ratio. Returns null when not scrollable.
60
+ *
61
+ * Supports animated colors via optional thumbColor/trackColor props
62
+ * from useAnimatedScrollbar hook.
63
+ *
64
+ * @example
65
+ * ```tsx
66
+ * // Basic usage
67
+ * <Box flexDirection="row">
68
+ * <Box flexGrow={1}>{content}</Box>
69
+ * <ScrollIndicator
70
+ * totalHeight={100}
71
+ * offsetFromBottom={25}
72
+ * viewportHeight={20}
73
+ * />
74
+ * </Box>
75
+ *
76
+ * // With animated colors
77
+ * const { scrollbarColor, trackColor } = useAnimatedScrollbar(isFocused, scrollBy);
78
+ * <ScrollIndicator
79
+ * totalHeight={100}
80
+ * offsetFromBottom={25}
81
+ * viewportHeight={20}
82
+ * thumbColor={scrollbarColor}
83
+ * trackColor={trackColor}
84
+ * />
85
+ * ```
86
+ */
87
+ export function ScrollIndicator({
88
+ totalHeight,
89
+ offsetFromBottom,
90
+ viewportHeight,
91
+ show,
92
+ thumbColor: animatedThumbColor,
93
+ trackColor: animatedTrackColor,
94
+ }: ScrollIndicatorProps): React.ReactElement | null {
95
+ const { theme } = useTheme();
96
+
97
+ // Determine if scrollable
98
+ const isScrollable = totalHeight > viewportHeight;
99
+
100
+ // Calculate visibility
101
+ const shouldShow = show ?? isScrollable;
102
+
103
+ // Calculate scrollbar metrics
104
+ const metrics = useMemo(() => {
105
+ if (!isScrollable || viewportHeight <= 0 || totalHeight <= 0) {
106
+ return null;
107
+ }
108
+
109
+ // Thumb size proportional to viewport/content ratio
110
+ const ratio = viewportHeight / totalHeight;
111
+ const thumbSize = Math.max(MIN_THUMB_SIZE, Math.round(viewportHeight * ratio));
112
+
113
+ // Track height (use actual viewport height)
114
+ const trackHeight = viewportHeight;
115
+
116
+ // Calculate thumb position
117
+ // offsetFromBottom=0 means at bottom, thumb at bottom
118
+ // offsetFromBottom=max means at top, thumb at top
119
+ const maxOffset = totalHeight - viewportHeight;
120
+ const scrollProgress = maxOffset > 0 ? 1 - offsetFromBottom / maxOffset : 1;
121
+
122
+ // Thumb position: 0 = top, (trackHeight - thumbSize) = bottom
123
+ const maxThumbPos = trackHeight - thumbSize;
124
+ const thumbPosition = Math.round(scrollProgress * maxThumbPos);
125
+
126
+ return {
127
+ thumbSize,
128
+ thumbPosition,
129
+ trackHeight,
130
+ };
131
+ }, [totalHeight, offsetFromBottom, viewportHeight, isScrollable]);
132
+
133
+ // Don't render if not showing or no metrics
134
+ if (!shouldShow || !metrics) {
135
+ return null;
136
+ }
137
+
138
+ const { thumbSize, thumbPosition, trackHeight } = metrics;
139
+
140
+ // Use animated colors if provided, otherwise fall back to theme
141
+ const trackColor = animatedTrackColor ?? theme.semantic.border.muted;
142
+ const thumbColor = animatedThumbColor ?? theme.semantic.text.muted;
143
+
144
+ // Build scrollbar lines
145
+ const lines: React.ReactNode[] = [];
146
+ for (let i = 0; i < trackHeight; i++) {
147
+ const isThumb = i >= thumbPosition && i < thumbPosition + thumbSize;
148
+ lines.push(
149
+ <Text key={i} color={isThumb ? thumbColor : trackColor}>
150
+ {isThumb ? CHARS.thumb : CHARS.track}
151
+ </Text>
152
+ );
153
+ }
154
+
155
+ return (
156
+ <Box flexDirection="column" width={1}>
157
+ {lines}
158
+ </Box>
159
+ );
160
+ }
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Spinner Component (Chain 21)
3
+ *
4
+ * Reusable animated spinner for loading states.
5
+ * Extracted from ThinkingBlock for general use.
6
+ *
7
+ * Uses global AnimationContext for centralized timing to prevent
8
+ * flickering from multiple independent timers.
9
+ *
10
+ * Supports both custom animation frames and ink-spinner types.
11
+ * Includes scene-based animations for contextual loading states.
12
+ *
13
+ * @module tui/components/common/Spinner
14
+ */
15
+
16
+ import { Text } from "ink";
17
+ import InkSpinner from "ink-spinner";
18
+ import type React from "react";
19
+ import { useAnimationFrame } from "../../context/AnimationContext.js";
20
+ import { useTheme } from "../../theme/index.js";
21
+
22
+ // =============================================================================
23
+ // Types
24
+ // =============================================================================
25
+
26
+ /**
27
+ * Scene-based spinner types for contextual loading states.
28
+ * Each scene has a unique animation that conveys the operation type.
29
+ */
30
+ export type SpinnerScene =
31
+ | "default" // Default braille spinner
32
+ | "thinking" // AI thinking: 🤔 💭 💡 ✨
33
+ | "writing" // File writing: ✎. ✎.. ✎...
34
+ | "searching" // Search operation: 🔍 🔎
35
+ | "loading" // Generic loading: ◐ ◓ ◑ ◒
36
+ | "streaming"; // Stream output: ▰▰▰▱▱▱
37
+
38
+ /**
39
+ * Available ink-spinner type names.
40
+ * These are the built-in spinner types from cli-spinners.
41
+ */
42
+ export type SpinnerType =
43
+ | "dots"
44
+ | "dots2"
45
+ | "dots3"
46
+ | "dots4"
47
+ | "dots5"
48
+ | "dots6"
49
+ | "dots7"
50
+ | "dots8"
51
+ | "dots9"
52
+ | "dots10"
53
+ | "dots11"
54
+ | "dots12"
55
+ | "line"
56
+ | "line2"
57
+ | "pipe"
58
+ | "simpleDots"
59
+ | "simpleDotsScrolling"
60
+ | "star"
61
+ | "star2"
62
+ | "flip"
63
+ | "hamburger"
64
+ | "growVertical"
65
+ | "growHorizontal"
66
+ | "balloon"
67
+ | "balloon2"
68
+ | "noise"
69
+ | "bounce"
70
+ | "boxBounce"
71
+ | "boxBounce2"
72
+ | "triangle"
73
+ | "arc"
74
+ | "circle"
75
+ | "squareCorners"
76
+ | "circleQuarters"
77
+ | "circleHalves"
78
+ | "squish"
79
+ | "toggle"
80
+ | "toggle2"
81
+ | "toggle3"
82
+ | "toggle4"
83
+ | "toggle5"
84
+ | "toggle6"
85
+ | "toggle7"
86
+ | "toggle8"
87
+ | "toggle9"
88
+ | "toggle10"
89
+ | "toggle11"
90
+ | "toggle12"
91
+ | "toggle13"
92
+ | "arrow"
93
+ | "arrow2"
94
+ | "arrow3"
95
+ | "bouncingBar"
96
+ | "bouncingBall"
97
+ | "smiley"
98
+ | "monkey"
99
+ | "hearts"
100
+ | "clock"
101
+ | "earth"
102
+ | "moon"
103
+ | "runner"
104
+ | "pong"
105
+ | "shark"
106
+ | "dqpb"
107
+ | "weather"
108
+ | "christmas"
109
+ | "grenade"
110
+ | "point"
111
+ | "layer"
112
+ | "betaWave";
113
+
114
+ /**
115
+ * Props for the Spinner component.
116
+ */
117
+ export interface SpinnerProps {
118
+ /** Color of the spinner (default: from theme or "cyan") */
119
+ readonly color?: string;
120
+ /**
121
+ * Scene-based spinner animation (default: 'default').
122
+ * Provides contextual animations for different operation types.
123
+ * Takes precedence over `frames` when specified (unless 'default').
124
+ */
125
+ readonly scene?: SpinnerScene;
126
+ /**
127
+ * Label to display after the spinner.
128
+ * Useful for providing context about the current operation.
129
+ */
130
+ readonly label?: string;
131
+ /**
132
+ * Animation frames (default: braille spinner)
133
+ * Ignored when `type` or non-default `scene` is specified.
134
+ */
135
+ readonly frames?: readonly string[];
136
+ /**
137
+ * Animation interval in milliseconds
138
+ * @deprecated No longer used - interval is controlled globally by AnimationContext
139
+ */
140
+ readonly interval?: number;
141
+ /**
142
+ * Use ink-spinner with specified type.
143
+ * When set, this takes precedence over `frames` and `scene`.
144
+ * @example "dots", "line", "arc", "bounce"
145
+ */
146
+ readonly type?: SpinnerType;
147
+ /**
148
+ * Whether to use ink-spinner (default: false for backward compatibility).
149
+ * Set to true to use ink-spinner even without specifying a type.
150
+ */
151
+ readonly useInkSpinner?: boolean;
152
+ }
153
+
154
+ /**
155
+ * Props for the LoadingIndicator component.
156
+ */
157
+ export interface LoadingIndicatorProps {
158
+ /** Message to display alongside the spinner */
159
+ readonly message?: string;
160
+ /** Whether to show the spinner (default: true) */
161
+ readonly showSpinner?: boolean;
162
+ /** Color for the spinner */
163
+ readonly spinnerColor?: string;
164
+ /** Spinner animation frames */
165
+ readonly frames?: readonly string[];
166
+ /** Whether the indicator is dimmed */
167
+ readonly dimmed?: boolean;
168
+ }
169
+
170
+ // =============================================================================
171
+ // Constants
172
+ // =============================================================================
173
+
174
+ /** Default spinner animation frames (braille pattern) */
175
+ export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
176
+
177
+ /**
178
+ * Scene-based animation frames for contextual loading states.
179
+ * Each scene provides a unique visual representation of the operation.
180
+ */
181
+ export const SCENE_FRAMES = {
182
+ /** Default braille spinner */
183
+ default: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
184
+ /** AI thinking animation */
185
+ thinking: ["🤔", "💭", "💡", "✨"],
186
+ /** File writing animation */
187
+ writing: ["✎", "✎.", "✎..", "✎..."],
188
+ /** Search operation animation */
189
+ searching: ["🔍", "🔎"],
190
+ /** Generic loading animation */
191
+ loading: ["◐", "◓", "◑", "◒"],
192
+ /** Streaming data animation */
193
+ streaming: ["▰▱▱▱", "▰▰▱▱", "▰▰▰▱", "▰▰▰▰", "▱▰▰▰", "▱▱▰▰", "▱▱▱▰"],
194
+ } as const;
195
+
196
+ /** Alternative spinner styles */
197
+ export const SPINNER_STYLES = {
198
+ /** Braille dots (default) */
199
+ braille: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
200
+ /** Classic dots */
201
+ dots: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
202
+ /** Simple line */
203
+ line: ["-", "\\", "|", "/"],
204
+ /** Growing dots */
205
+ growDots: [".", "..", "...", "...."],
206
+ /** Bouncing ball */
207
+ bounce: ["⠁", "⠂", "⠄", "⠂"],
208
+ /** Arc spinner */
209
+ arc: ["◜", "◠", "◝", "◞", "◡", "◟"],
210
+ /** Box spinner */
211
+ box: ["▖", "▘", "▝", "▗"],
212
+ } as const;
213
+
214
+ /** Default animation interval in milliseconds
215
+ * @deprecated Interval is now controlled globally by AnimationContext (120ms default, 150ms for VS Code)
216
+ */
217
+ const DEFAULT_INTERVAL_MS = 120;
218
+
219
+ // =============================================================================
220
+ // Spinner Component
221
+ // =============================================================================
222
+
223
+ /**
224
+ * Spinner - Animated loading indicator.
225
+ *
226
+ * Features:
227
+ * - Configurable animation frames
228
+ * - Scene-based animations for contextual loading states
229
+ * - Adjustable speed
230
+ * - Custom colors
231
+ * - Multiple built-in styles
232
+ * - Support for ink-spinner types
233
+ * - Optional label display
234
+ *
235
+ * @example
236
+ * ```tsx
237
+ * // Basic usage (custom frames)
238
+ * <Spinner />
239
+ *
240
+ * // Scene-based animation
241
+ * <Spinner scene="thinking" label="Analyzing..." />
242
+ *
243
+ * // Writing scene
244
+ * <Spinner scene="writing" label="Saving file..." />
245
+ *
246
+ * // Custom color
247
+ * <Spinner color="yellow" />
248
+ *
249
+ * // Different style
250
+ * <Spinner frames={SPINNER_STYLES.dots} />
251
+ *
252
+ * // Using ink-spinner type
253
+ * <Spinner type="dots" />
254
+ *
255
+ * // Using ink-spinner with explicit flag
256
+ * <Spinner useInkSpinner type="arc" color="green" />
257
+ * ```
258
+ */
259
+ export function Spinner({
260
+ color,
261
+ scene = "default",
262
+ label,
263
+ frames = SPINNER_FRAMES,
264
+ // interval prop kept for backward compatibility but no longer used
265
+ interval: _interval = DEFAULT_INTERVAL_MS,
266
+ type,
267
+ useInkSpinner = false,
268
+ }: SpinnerProps): React.JSX.Element {
269
+ const { theme } = useTheme();
270
+
271
+ // Determine which frames to use based on scene
272
+ const effectiveFrames = scene !== "default" ? SCENE_FRAMES[scene] : frames;
273
+
274
+ // Use global animation context for custom frames
275
+ const frameIndex = useAnimationFrame(effectiveFrames);
276
+
277
+ const spinnerColor = color ?? theme.semantic.text.primary;
278
+
279
+ // Use ink-spinner if type is specified or useInkSpinner is true
280
+ if (type || useInkSpinner) {
281
+ return (
282
+ <Text color={spinnerColor}>
283
+ <InkSpinner type={type ?? "dots"} />
284
+ {label && <Text> {label}</Text>}
285
+ </Text>
286
+ );
287
+ }
288
+
289
+ // Default: use custom frames with AnimationContext
290
+ return (
291
+ <Text color={spinnerColor}>
292
+ {effectiveFrames[frameIndex]}
293
+ {label && <Text> {label}</Text>}
294
+ </Text>
295
+ );
296
+ }
297
+
298
+ // =============================================================================
299
+ // LoadingIndicator Component
300
+ // =============================================================================
301
+
302
+ /**
303
+ * LoadingIndicator - Spinner with optional message.
304
+ *
305
+ * Combines a spinner with a loading message for common loading states.
306
+ *
307
+ * @example
308
+ * ```tsx
309
+ * // Basic loading
310
+ * <LoadingIndicator message="Loading..." />
311
+ *
312
+ * // Without spinner (just text)
313
+ * <LoadingIndicator message="Please wait" showSpinner={false} />
314
+ *
315
+ * // Dimmed style
316
+ * <LoadingIndicator message="Processing..." dimmed />
317
+ * ```
318
+ */
319
+ export function LoadingIndicator({
320
+ message = "Loading...",
321
+ showSpinner = true,
322
+ spinnerColor,
323
+ frames,
324
+ dimmed = false,
325
+ }: LoadingIndicatorProps): React.JSX.Element {
326
+ return (
327
+ <Text dimColor={dimmed}>
328
+ {showSpinner && (
329
+ <>
330
+ <Spinner color={spinnerColor} frames={frames} />{" "}
331
+ </>
332
+ )}
333
+ {message}
334
+ </Text>
335
+ );
336
+ }
337
+
338
+ // =============================================================================
339
+ // Exports
340
+ // =============================================================================
341
+
342
+ export default Spinner;