@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,157 @@
1
+ /**
2
+ * ModelIndicator Component (T035)
3
+ *
4
+ * Displays the current AI provider icon and model name in the status bar.
5
+ * Uses provider-specific icons for visual identification.
6
+ *
7
+ * @module tui/components/StatusBar/ModelIndicator
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import { useMemo } from "react";
12
+ import { useTheme } from "../../theme/index.js";
13
+
14
+ // =============================================================================
15
+ // Types
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Props for the ModelIndicator component.
20
+ */
21
+ export interface ModelIndicatorProps {
22
+ /** AI provider name (e.g., 'anthropic', 'openai', 'google') */
23
+ readonly provider?: string;
24
+ /** Model name (e.g., 'claude-3-opus', 'gpt-4') */
25
+ readonly model: string;
26
+ /** Compact mode: show only model name without provider (default: false) */
27
+ readonly compact?: boolean;
28
+ }
29
+
30
+ // =============================================================================
31
+ // Constants
32
+ // =============================================================================
33
+
34
+ /**
35
+ * Provider icons mapping.
36
+ * Uses Unicode symbols for terminal compatibility.
37
+ */
38
+ const PROVIDER_ICONS: Record<string, string> = {
39
+ anthropic: "◈", // Diamond with dot
40
+ openai: "◉", // Circle with fill
41
+ google: "◎", // Circle with ring
42
+ azure: "◇", // Diamond outline
43
+ bedrock: "▣", // Square with fill
44
+ mistral: "◆", // Filled diamond
45
+ ollama: "○", // Circle outline
46
+ default: "●", // Filled circle
47
+ };
48
+
49
+ /**
50
+ * Provider display names mapping.
51
+ */
52
+ const PROVIDER_NAMES: Record<string, string> = {
53
+ anthropic: "Anthropic",
54
+ openai: "OpenAI",
55
+ google: "Google",
56
+ azure: "Azure",
57
+ bedrock: "Bedrock",
58
+ mistral: "Mistral",
59
+ ollama: "Ollama",
60
+ };
61
+
62
+ // =============================================================================
63
+ // Helper Functions
64
+ // =============================================================================
65
+
66
+ /**
67
+ * Gets the icon for a provider.
68
+ * Falls back to default icon for unknown providers.
69
+ */
70
+ function getProviderIcon(provider: string): string {
71
+ const normalizedProvider = provider.toLowerCase();
72
+ return PROVIDER_ICONS[normalizedProvider] ?? "●";
73
+ }
74
+
75
+ /**
76
+ * Gets the display name for a provider.
77
+ * Falls back to capitalized provider name for unknown providers.
78
+ */
79
+ function getProviderName(provider: string): string {
80
+ const normalizedProvider = provider.toLowerCase();
81
+ return PROVIDER_NAMES[normalizedProvider] ?? provider.charAt(0).toUpperCase() + provider.slice(1);
82
+ }
83
+
84
+ /**
85
+ * Truncates model name if too long.
86
+ * Preserves the version suffix if present.
87
+ */
88
+ function formatModelName(model: string, maxLength = 25): string {
89
+ if (model.length <= maxLength) {
90
+ return model;
91
+ }
92
+
93
+ // Try to preserve version suffix
94
+ const parts = model.split("-");
95
+ if (parts.length > 1) {
96
+ const suffix = parts[parts.length - 1] ?? "";
97
+ const truncated = model.slice(0, maxLength - suffix.length - 4);
98
+ return `${truncated}...${suffix}`;
99
+ }
100
+
101
+ return `${model.slice(0, maxLength - 3)}...`;
102
+ }
103
+
104
+ // =============================================================================
105
+ // Main Component
106
+ // =============================================================================
107
+
108
+ /**
109
+ * ModelIndicator displays the current AI provider and model.
110
+ *
111
+ * Features:
112
+ * - Provider-specific icon
113
+ * - Provider name (optional)
114
+ * - Model name (truncated if too long)
115
+ * - Compact mode for unified footer layout
116
+ * - Themed styling
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * // Full display with provider
121
+ * <ModelIndicator provider="anthropic" model="claude-3-opus" />
122
+ *
123
+ * // Compact mode (model only)
124
+ * <ModelIndicator model="claude-sonnet-4" compact />
125
+ * ```
126
+ */
127
+ export function ModelIndicator({
128
+ provider,
129
+ model,
130
+ compact = false,
131
+ }: ModelIndicatorProps): React.JSX.Element {
132
+ const { theme } = useTheme();
133
+
134
+ const icon = useMemo(() => (provider ? getProviderIcon(provider) : "●"), [provider]);
135
+ const providerName = useMemo(() => (provider ? getProviderName(provider) : ""), [provider]);
136
+ const displayModel = useMemo(() => formatModelName(model, compact ? 20 : 25), [model, compact]);
137
+
138
+ // Compact mode: model name only
139
+ if (compact) {
140
+ return (
141
+ <Box>
142
+ <Text color={theme.semantic.text.primary}>{displayModel}</Text>
143
+ </Box>
144
+ );
145
+ }
146
+
147
+ // Full mode: icon + provider + model
148
+ return (
149
+ <Box>
150
+ <Text color={theme.colors.primary}>{icon}</Text>
151
+ <Text color={theme.semantic.text.muted}> </Text>
152
+ <Text color={theme.semantic.text.secondary}>{providerName}</Text>
153
+ <Text color={theme.semantic.text.muted}>/</Text>
154
+ <Text color={theme.semantic.text.primary}>{displayModel}</Text>
155
+ </Box>
156
+ );
157
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * PersistenceStatusIndicator Component
3
+ *
4
+ * Status indicator for session persistence state.
5
+ * Displays save status, unsaved message count, and timing info.
6
+ *
7
+ * @module tui/components/StatusBar/PersistenceStatusIndicator
8
+ */
9
+
10
+ import { getIcons } from "@vellum/shared";
11
+ import { Box, Text } from "ink";
12
+ import type React from "react";
13
+ import { useMemo } from "react";
14
+ import type { PersistenceStatus } from "../../hooks/usePersistence.js";
15
+ import { useTUITranslation } from "../../i18n/index.js";
16
+ import { useTheme } from "../../theme/index.js";
17
+
18
+ // =============================================================================
19
+ // Types
20
+ // =============================================================================
21
+
22
+ /**
23
+ * Props for the PersistenceStatusIndicator component
24
+ */
25
+ export interface PersistenceStatusIndicatorProps {
26
+ /** Current persistence status */
27
+ readonly status: PersistenceStatus;
28
+ /** Number of unsaved messages */
29
+ readonly unsavedCount?: number;
30
+ /** Timestamp of last successful save */
31
+ readonly lastSavedAt?: Date | null;
32
+ /** Whether to use compact display mode */
33
+ readonly compact?: boolean;
34
+ /** Whether the indicator is visible */
35
+ readonly visible?: boolean;
36
+ }
37
+
38
+ // =============================================================================
39
+ // Constants
40
+ // =============================================================================
41
+
42
+ /**
43
+ * Get icon for a persistence status using the theme icon system.
44
+ */
45
+ function getStatusIcon(status: PersistenceStatus): string {
46
+ const icons = getIcons();
47
+ switch (status) {
48
+ case "idle":
49
+ return icons.check; // All good, nothing to save
50
+ case "saving":
51
+ return icons.running; // In progress
52
+ case "saved":
53
+ return icons.success; // Just saved successfully
54
+ case "error":
55
+ return icons.error; // Save failed
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Icon suffix for unsaved state
61
+ */
62
+ const UNSAVED_SUFFIX = "•";
63
+
64
+ // =============================================================================
65
+ // Helper Functions
66
+ // =============================================================================
67
+
68
+ /**
69
+ * Format relative time from a date.
70
+ *
71
+ * @param date - Date to format
72
+ * @returns Human-readable relative time string
73
+ */
74
+ function formatRelativeTime(date: Date): string {
75
+ const now = new Date();
76
+ const diffMs = now.getTime() - date.getTime();
77
+ const diffSecs = Math.floor(diffMs / 1000);
78
+ const diffMins = Math.floor(diffSecs / 60);
79
+ const diffHours = Math.floor(diffMins / 60);
80
+
81
+ if (diffSecs < 10) {
82
+ return "just now";
83
+ }
84
+ if (diffSecs < 60) {
85
+ return `${diffSecs}s ago`;
86
+ }
87
+ if (diffMins < 60) {
88
+ return `${diffMins}min ago`;
89
+ }
90
+ if (diffHours < 24) {
91
+ return `${diffHours}h ago`;
92
+ }
93
+ return date.toLocaleDateString();
94
+ }
95
+
96
+ // =============================================================================
97
+ // Main Component
98
+ // =============================================================================
99
+
100
+ /**
101
+ * PersistenceStatusIndicator displays the current session save state.
102
+ *
103
+ * Status Icons (using theme icons):
104
+ * - ✔/✦ (green) - Saved, no unsaved changes
105
+ * - ✔• (yellow) - Unsaved changes present
106
+ * - ⟳ (blue) - Save in progress
107
+ * - ✘ (red) - Save failed
108
+ *
109
+ * Display Modes:
110
+ * - Normal: "✔ saved 2min ago" or "✔• unsaved: 3"
111
+ * - Compact: "✔" or "✔•3"
112
+ *
113
+ * @example
114
+ * ```tsx
115
+ * <PersistenceStatusIndicator
116
+ * status="idle"
117
+ * unsavedCount={3}
118
+ * lastSavedAt={new Date()}
119
+ * />
120
+ * ```
121
+ */
122
+ export function PersistenceStatusIndicator({
123
+ status,
124
+ unsavedCount = 0,
125
+ lastSavedAt = null,
126
+ compact = false,
127
+ visible = true,
128
+ }: PersistenceStatusIndicatorProps): React.JSX.Element | null {
129
+ const { theme } = useTheme();
130
+ const { t } = useTUITranslation();
131
+
132
+ // Determine effective status based on unsaved count
133
+ const effectiveStatus = useMemo((): PersistenceStatus => {
134
+ if (status === "saving" || status === "error") {
135
+ return status;
136
+ }
137
+ if (unsavedCount > 0) {
138
+ return "idle";
139
+ }
140
+ return "saved";
141
+ }, [status, unsavedCount]);
142
+
143
+ // Get icon based on status
144
+ const icon = useMemo((): string => {
145
+ const baseIcon = getStatusIcon(effectiveStatus);
146
+ if (effectiveStatus === "idle" && unsavedCount > 0) {
147
+ return `${getStatusIcon("idle")}${UNSAVED_SUFFIX}`;
148
+ }
149
+ return baseIcon;
150
+ }, [effectiveStatus, unsavedCount]);
151
+
152
+ // Determine color based on status
153
+ const color = useMemo((): string => {
154
+ switch (effectiveStatus) {
155
+ case "saved":
156
+ return theme.colors.success;
157
+ case "saving":
158
+ return theme.colors.info;
159
+ case "error":
160
+ return theme.colors.error;
161
+ case "idle":
162
+ return unsavedCount > 0 ? theme.colors.warning : theme.semantic.text.muted;
163
+ }
164
+ }, [effectiveStatus, unsavedCount, theme]);
165
+
166
+ // Build display text
167
+ const displayText = useMemo((): string => {
168
+ if (compact) {
169
+ // Compact mode: just icon and count if unsaved
170
+ if (unsavedCount > 0 && effectiveStatus !== "saving") {
171
+ return `${icon}${unsavedCount}`;
172
+ }
173
+ return icon;
174
+ }
175
+
176
+ // Full mode
177
+ switch (effectiveStatus) {
178
+ case "saving":
179
+ return `${icon} ${t("persistence.saving")}`;
180
+ case "saved":
181
+ if (lastSavedAt) {
182
+ return `${icon} ${t("persistence.saved")} ${formatRelativeTime(lastSavedAt)}`;
183
+ }
184
+ return `${icon} ${t("persistence.saved")}`;
185
+ case "error":
186
+ return `${icon} ${t("persistence.saveError")}`;
187
+ case "idle":
188
+ if (unsavedCount > 0) {
189
+ return `${icon} ${t("persistence.unsaved")}: ${unsavedCount}`;
190
+ }
191
+ return `${icon} ${t("persistence.saved")}`;
192
+ }
193
+ }, [compact, effectiveStatus, unsavedCount, lastSavedAt, icon, t]);
194
+
195
+ if (!visible) {
196
+ return null;
197
+ }
198
+
199
+ return (
200
+ <Box>
201
+ <Text color={color}>{displayText}</Text>
202
+ </Box>
203
+ );
204
+ }
205
+
206
+ // =============================================================================
207
+ // Exports
208
+ // =============================================================================
209
+
210
+ export default PersistenceStatusIndicator;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Resilience Indicator Component
3
+ *
4
+ * Displays rate limiting and retry status in the TUI.
5
+ *
6
+ * @module tui/components/StatusBar/ResilienceIndicator
7
+ */
8
+
9
+ import { Box, Text } from "ink";
10
+ import type React from "react";
11
+
12
+ import { type ResilienceStatus, useResilienceOptional } from "../../context/ResilienceContext.js";
13
+ import { useTheme } from "../../theme/index.js";
14
+
15
+ // =============================================================================
16
+ // Types
17
+ // =============================================================================
18
+
19
+ /**
20
+ * Props for ResilienceIndicator
21
+ */
22
+ export interface ResilienceIndicatorProps {
23
+ /** Override status (for testing/storybook) */
24
+ readonly status?: ResilienceStatus;
25
+ /** Show compact version */
26
+ readonly compact?: boolean;
27
+ }
28
+
29
+ // =============================================================================
30
+ // Component
31
+ // =============================================================================
32
+
33
+ /**
34
+ * Resilience indicator showing rate limit and retry status.
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * // In StatusBar
39
+ * <ResilienceIndicator />
40
+ *
41
+ * // Compact version
42
+ * <ResilienceIndicator compact />
43
+ * ```
44
+ */
45
+ export function ResilienceIndicator({
46
+ status: overrideStatus,
47
+ compact = false,
48
+ }: ResilienceIndicatorProps): React.JSX.Element | null {
49
+ const { theme } = useTheme();
50
+ const context = useResilienceOptional();
51
+
52
+ // Use override status or context status
53
+ const status = overrideStatus ?? context?.status;
54
+
55
+ // Don't render if no status or idle
56
+ if (!status || status.type === "idle") {
57
+ return null;
58
+ }
59
+
60
+ // Select icon and color based on status type
61
+ const icon = status.type === "rate-limit" ? "⏳" : "🔄";
62
+ const color = status.type === "rate-limit" ? theme.colors.warning : theme.colors.info;
63
+
64
+ if (compact) {
65
+ // Compact: just icon and wait time
66
+ const waitText = status.waitSeconds ? `${status.waitSeconds}s` : "";
67
+ return (
68
+ <Box>
69
+ <Text color={color}>
70
+ {icon} {waitText}
71
+ </Text>
72
+ </Box>
73
+ );
74
+ }
75
+
76
+ // Full: icon and message
77
+ return (
78
+ <Box>
79
+ <Text color={color}>
80
+ {icon} {status.message}
81
+ </Text>
82
+ </Box>
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Resilience status bar segment for integration into StatusBar.
88
+ *
89
+ * Returns null if no active resilience event, making it safe to
90
+ * include in layouts unconditionally.
91
+ */
92
+ export function ResilienceStatusSegment(): React.JSX.Element | null {
93
+ const context = useResilienceOptional();
94
+
95
+ // Don't render anything if context not available or feedback disabled
96
+ if (!context || !context.feedbackEnabled) {
97
+ return null;
98
+ }
99
+
100
+ // Don't render if idle
101
+ if (context.status.type === "idle") {
102
+ return null;
103
+ }
104
+
105
+ return <ResilienceIndicator />;
106
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * SandboxIndicator Component
3
+ *
4
+ * Displays the current sandbox policy with an icon and label.
5
+ * Sandbox policies control file system access boundaries.
6
+ *
7
+ * Trust Mode (approval) and Sandbox (file boundaries) are SEPARATE concepts:
8
+ * - Trust Mode: When to ask user (ask/auto/full-auto)
9
+ * - Sandbox: Where agent can access (workspace/cwd/system)
10
+ *
11
+ * @module tui/components/StatusBar/SandboxIndicator
12
+ */
13
+
14
+ import type { SandboxPolicy } from "@vellum/core";
15
+ import { Box, Text } from "ink";
16
+ import { useMemo } from "react";
17
+ import { useTheme } from "../../theme/index.js";
18
+
19
+ // =============================================================================
20
+ // Types
21
+ // =============================================================================
22
+
23
+ /**
24
+ * Props for the SandboxIndicator component.
25
+ */
26
+ export interface SandboxIndicatorProps {
27
+ /** Current sandbox policy */
28
+ readonly policy: SandboxPolicy;
29
+ /** Whether to show compact label (default: true) */
30
+ readonly compact?: boolean;
31
+ }
32
+
33
+ // =============================================================================
34
+ // Constants
35
+ // =============================================================================
36
+
37
+ /**
38
+ * Sandbox policy display configuration.
39
+ *
40
+ * Maps technical policy names to user-friendly display:
41
+ * - workspace-read: Read-only within workspace
42
+ * - workspace-write: Read/write within workspace
43
+ * - cwd-read: Read-only in current directory
44
+ * - cwd-write: Read/write in current directory
45
+ * - full-access: Full system-wide access (dangerous)
46
+ */
47
+ interface SandboxDisplayConfig {
48
+ readonly icon: string;
49
+ readonly label: string;
50
+ readonly compactLabel: string;
51
+ readonly description: string;
52
+ readonly severity: "safe" | "caution" | "danger";
53
+ }
54
+
55
+ const SANDBOX_DISPLAY: Record<SandboxPolicy, SandboxDisplayConfig> = {
56
+ "workspace-read": {
57
+ icon: "[r]",
58
+ label: "workspace (read)",
59
+ compactLabel: "ws",
60
+ description: "Read-only access within workspace",
61
+ severity: "safe",
62
+ },
63
+ "workspace-write": {
64
+ icon: "[w]",
65
+ label: "workspace",
66
+ compactLabel: "ws",
67
+ description: "Read/write access within workspace",
68
+ severity: "caution",
69
+ },
70
+ "cwd-read": {
71
+ icon: "[r]",
72
+ label: "cwd (read)",
73
+ compactLabel: "cwd",
74
+ description: "Read-only access in current directory",
75
+ severity: "safe",
76
+ },
77
+ "cwd-write": {
78
+ icon: "[w]",
79
+ label: "cwd",
80
+ compactLabel: "cwd",
81
+ description: "Read/write access in current directory",
82
+ severity: "caution",
83
+ },
84
+ "full-access": {
85
+ icon: "[*]",
86
+ label: "system",
87
+ compactLabel: "sys",
88
+ description: "Full system-wide access",
89
+ severity: "danger",
90
+ },
91
+ };
92
+
93
+ // =============================================================================
94
+ // Helper Functions
95
+ // =============================================================================
96
+
97
+ /**
98
+ * Gets the semantic color for a sandbox policy severity.
99
+ * - safe: success (green) - limited access
100
+ * - caution: warning (yellow) - workspace/cwd write access
101
+ * - danger: error (red) - full system access
102
+ */
103
+ function getSandboxColor(
104
+ severity: SandboxDisplayConfig["severity"],
105
+ theme: ReturnType<typeof useTheme>["theme"]
106
+ ): string {
107
+ switch (severity) {
108
+ case "safe":
109
+ return theme.colors.success;
110
+ case "caution":
111
+ return theme.colors.warning;
112
+ case "danger":
113
+ return theme.colors.error;
114
+ default:
115
+ return theme.semantic.text.secondary;
116
+ }
117
+ }
118
+
119
+ // =============================================================================
120
+ // Main Component
121
+ // =============================================================================
122
+
123
+ /**
124
+ * SandboxIndicator displays the current sandbox policy with visual cues.
125
+ *
126
+ * Features:
127
+ * - Policy-specific icon
128
+ * - Color-coded by access level severity
129
+ * - Compact or full label display
130
+ *
131
+ * Sandbox Policies:
132
+ * - workspace-read: [r] Read-only within workspace (safe)
133
+ * - workspace-write: [w] Read/write within workspace (caution)
134
+ * - cwd-read: [r] Read-only in current directory (safe)
135
+ * - cwd-write: [w] Read/write in current directory (caution)
136
+ * - full-access: [*] Full system access (danger)
137
+ *
138
+ * @example
139
+ * ```tsx
140
+ * // Compact mode (default)
141
+ * <SandboxIndicator policy="workspace-write" />
142
+ * // Output: [w] ws
143
+ *
144
+ * // Full mode
145
+ * <SandboxIndicator policy="workspace-write" compact={false} />
146
+ * // Output: [w] workspace
147
+ * ```
148
+ */
149
+ export function SandboxIndicator({
150
+ policy,
151
+ compact = true,
152
+ }: SandboxIndicatorProps): React.JSX.Element {
153
+ const { theme } = useTheme();
154
+
155
+ const config = useMemo(() => SANDBOX_DISPLAY[policy], [policy]);
156
+ const color = useMemo(() => getSandboxColor(config.severity, theme), [config.severity, theme]);
157
+
158
+ const displayLabel = compact ? config.compactLabel : config.label;
159
+
160
+ return (
161
+ <Box flexDirection="row">
162
+ <Text color={color}>
163
+ {config.icon} {displayLabel}
164
+ </Text>
165
+ </Box>
166
+ );
167
+ }