nova-terminal-assistant 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.

Potentially problematic release.


This version of nova-terminal-assistant might be problematic. Click here for more details.

Files changed (192) hide show
  1. package/README.md +358 -0
  2. package/bin/nova +38 -0
  3. package/bin/nova.js +12 -0
  4. package/package.json +67 -0
  5. package/src/cli/commands/SmartCompletion.ts +458 -0
  6. package/src/cli/index.ts +5 -0
  7. package/src/cli/startup/IFlowRepl.ts +212 -0
  8. package/src/cli/startup/InkBasedRepl.ts +1056 -0
  9. package/src/cli/startup/InteractiveRepl.ts +2833 -0
  10. package/src/cli/startup/NovaApp.ts +1861 -0
  11. package/src/cli/startup/index.ts +4 -0
  12. package/src/cli/startup/parseArgs.ts +293 -0
  13. package/src/cli/test-modules.ts +27 -0
  14. package/src/cli/ui/IFlowDropdown.ts +425 -0
  15. package/src/cli/ui/ModernReplUI.ts +276 -0
  16. package/src/cli/ui/SimpleSelector2.ts +215 -0
  17. package/src/cli/ui/components/ConfirmDialog.ts +176 -0
  18. package/src/cli/ui/components/ErrorPanel.ts +364 -0
  19. package/src/cli/ui/components/InkAppRunner.tsx +67 -0
  20. package/src/cli/ui/components/InkComponents.tsx +613 -0
  21. package/src/cli/ui/components/NovaInkApp.tsx +312 -0
  22. package/src/cli/ui/components/ProgressBar.ts +177 -0
  23. package/src/cli/ui/components/ProgressIndicator.ts +298 -0
  24. package/src/cli/ui/components/QuickActions.ts +396 -0
  25. package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
  26. package/src/cli/ui/components/StatusBar.ts +194 -0
  27. package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
  28. package/src/cli/ui/components/index.ts +27 -0
  29. package/src/cli/ui/ink-prototype.tsx +347 -0
  30. package/src/cli/utils/CliUI.ts +336 -0
  31. package/src/cli/utils/CompletionHelper.ts +388 -0
  32. package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
  33. package/src/cli/utils/EnhancedCompleter.ts +513 -0
  34. package/src/cli/utils/ErrorEnhancer.ts +429 -0
  35. package/src/cli/utils/OutputFormatter.ts +193 -0
  36. package/src/cli/utils/index.ts +9 -0
  37. package/src/core/agents/AgentOrchestrator.ts +515 -0
  38. package/src/core/agents/index.ts +17 -0
  39. package/src/core/audit/AuditLogger.ts +509 -0
  40. package/src/core/audit/index.ts +11 -0
  41. package/src/core/auth/AuthManager.d.ts.map +1 -0
  42. package/src/core/auth/AuthManager.ts +138 -0
  43. package/src/core/auth/index.d.ts.map +1 -0
  44. package/src/core/auth/index.ts +2 -0
  45. package/src/core/config/ConfigManager.d.ts.map +1 -0
  46. package/src/core/config/ConfigManager.test.ts +183 -0
  47. package/src/core/config/ConfigManager.ts +1219 -0
  48. package/src/core/config/index.d.ts.map +1 -0
  49. package/src/core/config/index.ts +1 -0
  50. package/src/core/context/ContextBuilder.d.ts.map +1 -0
  51. package/src/core/context/ContextBuilder.ts +171 -0
  52. package/src/core/context/ContextCompressor.d.ts.map +1 -0
  53. package/src/core/context/ContextCompressor.ts +642 -0
  54. package/src/core/context/LayeredMemoryManager.ts +657 -0
  55. package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
  56. package/src/core/context/MemoryDiscovery.ts +175 -0
  57. package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
  58. package/src/core/context/defaultSystemPrompt.ts +35 -0
  59. package/src/core/context/index.d.ts.map +1 -0
  60. package/src/core/context/index.ts +22 -0
  61. package/src/core/extensions/SkillGenerator.ts +421 -0
  62. package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
  63. package/src/core/extensions/SkillInstaller.ts +257 -0
  64. package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
  65. package/src/core/extensions/SkillRegistry.ts +361 -0
  66. package/src/core/extensions/SkillValidator.ts +525 -0
  67. package/src/core/extensions/index.ts +15 -0
  68. package/src/core/index.d.ts.map +1 -0
  69. package/src/core/index.ts +42 -0
  70. package/src/core/mcp/McpManager.d.ts.map +1 -0
  71. package/src/core/mcp/McpManager.ts +632 -0
  72. package/src/core/mcp/index.d.ts.map +1 -0
  73. package/src/core/mcp/index.ts +2 -0
  74. package/src/core/model/ModelClient.d.ts.map +1 -0
  75. package/src/core/model/ModelClient.ts +217 -0
  76. package/src/core/model/ModelConnectionTester.ts +363 -0
  77. package/src/core/model/ModelValidator.ts +348 -0
  78. package/src/core/model/index.d.ts.map +1 -0
  79. package/src/core/model/index.ts +6 -0
  80. package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
  81. package/src/core/model/providers/AnthropicProvider.ts +279 -0
  82. package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
  83. package/src/core/model/providers/CodingPlanProvider.ts +210 -0
  84. package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
  85. package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
  86. package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
  87. package/src/core/model/providers/OllamaManager.ts +201 -0
  88. package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
  89. package/src/core/model/providers/OllamaProvider.ts +73 -0
  90. package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  91. package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
  92. package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
  93. package/src/core/model/providers/OpenAIProvider.ts +29 -0
  94. package/src/core/model/providers/index.d.ts.map +1 -0
  95. package/src/core/model/providers/index.ts +12 -0
  96. package/src/core/model/types.d.ts.map +1 -0
  97. package/src/core/model/types.ts +77 -0
  98. package/src/core/security/ApprovalManager.d.ts.map +1 -0
  99. package/src/core/security/ApprovalManager.ts +174 -0
  100. package/src/core/security/FileFilter.d.ts.map +1 -0
  101. package/src/core/security/FileFilter.ts +141 -0
  102. package/src/core/security/HookExecutor.d.ts.map +1 -0
  103. package/src/core/security/HookExecutor.ts +178 -0
  104. package/src/core/security/SandboxExecutor.ts +447 -0
  105. package/src/core/security/index.d.ts.map +1 -0
  106. package/src/core/security/index.ts +8 -0
  107. package/src/core/session/AgentLoop.d.ts.map +1 -0
  108. package/src/core/session/AgentLoop.ts +501 -0
  109. package/src/core/session/SessionManager.d.ts.map +1 -0
  110. package/src/core/session/SessionManager.test.ts +183 -0
  111. package/src/core/session/SessionManager.ts +460 -0
  112. package/src/core/session/index.d.ts.map +1 -0
  113. package/src/core/session/index.ts +3 -0
  114. package/src/core/telemetry/Telemetry.d.ts.map +1 -0
  115. package/src/core/telemetry/Telemetry.ts +90 -0
  116. package/src/core/telemetry/TelemetryService.ts +531 -0
  117. package/src/core/telemetry/index.d.ts.map +1 -0
  118. package/src/core/telemetry/index.ts +12 -0
  119. package/src/core/testing/AutoFixer.ts +385 -0
  120. package/src/core/testing/ErrorAnalyzer.ts +499 -0
  121. package/src/core/testing/TestRunner.ts +265 -0
  122. package/src/core/testing/agent-cli-tests.ts +538 -0
  123. package/src/core/testing/index.ts +11 -0
  124. package/src/core/tools/ToolRegistry.d.ts.map +1 -0
  125. package/src/core/tools/ToolRegistry.test.ts +206 -0
  126. package/src/core/tools/ToolRegistry.ts +260 -0
  127. package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
  128. package/src/core/tools/impl/EditFileTool.ts +97 -0
  129. package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
  130. package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
  131. package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
  132. package/src/core/tools/impl/MemoryTool.ts +102 -0
  133. package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
  134. package/src/core/tools/impl/ReadFileTool.ts +58 -0
  135. package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
  136. package/src/core/tools/impl/SearchContentTool.ts +94 -0
  137. package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
  138. package/src/core/tools/impl/SearchFileTool.ts +61 -0
  139. package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
  140. package/src/core/tools/impl/ShellTool.ts +118 -0
  141. package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
  142. package/src/core/tools/impl/TaskTool.ts +207 -0
  143. package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
  144. package/src/core/tools/impl/TodoTool.ts +122 -0
  145. package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
  146. package/src/core/tools/impl/WebFetchTool.ts +103 -0
  147. package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
  148. package/src/core/tools/impl/WebSearchTool.ts +89 -0
  149. package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
  150. package/src/core/tools/impl/WriteFileTool.ts +49 -0
  151. package/src/core/tools/impl/index.d.ts.map +1 -0
  152. package/src/core/tools/impl/index.ts +16 -0
  153. package/src/core/tools/index.d.ts.map +1 -0
  154. package/src/core/tools/index.ts +7 -0
  155. package/src/core/tools/schemas/execution.d.ts.map +1 -0
  156. package/src/core/tools/schemas/execution.ts +42 -0
  157. package/src/core/tools/schemas/file.d.ts.map +1 -0
  158. package/src/core/tools/schemas/file.ts +119 -0
  159. package/src/core/tools/schemas/index.d.ts.map +1 -0
  160. package/src/core/tools/schemas/index.ts +11 -0
  161. package/src/core/tools/schemas/memory.d.ts.map +1 -0
  162. package/src/core/tools/schemas/memory.ts +52 -0
  163. package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
  164. package/src/core/tools/schemas/orchestration.ts +44 -0
  165. package/src/core/tools/schemas/search.d.ts.map +1 -0
  166. package/src/core/tools/schemas/search.ts +112 -0
  167. package/src/core/tools/schemas/todo.d.ts.map +1 -0
  168. package/src/core/tools/schemas/todo.ts +32 -0
  169. package/src/core/tools/schemas/web.d.ts.map +1 -0
  170. package/src/core/tools/schemas/web.ts +86 -0
  171. package/src/core/types/config.d.ts.map +1 -0
  172. package/src/core/types/config.ts +200 -0
  173. package/src/core/types/errors.d.ts.map +1 -0
  174. package/src/core/types/errors.ts +204 -0
  175. package/src/core/types/index.d.ts.map +1 -0
  176. package/src/core/types/index.ts +8 -0
  177. package/src/core/types/session.d.ts.map +1 -0
  178. package/src/core/types/session.ts +216 -0
  179. package/src/core/types/tools.d.ts.map +1 -0
  180. package/src/core/types/tools.ts +157 -0
  181. package/src/core/utils/CheckpointManager.d.ts.map +1 -0
  182. package/src/core/utils/CheckpointManager.ts +327 -0
  183. package/src/core/utils/Logger.d.ts.map +1 -0
  184. package/src/core/utils/Logger.ts +98 -0
  185. package/src/core/utils/RetryManager.ts +471 -0
  186. package/src/core/utils/TokenCounter.d.ts.map +1 -0
  187. package/src/core/utils/TokenCounter.ts +414 -0
  188. package/src/core/utils/VectorMemoryStore.ts +440 -0
  189. package/src/core/utils/helpers.d.ts.map +1 -0
  190. package/src/core/utils/helpers.ts +89 -0
  191. package/src/core/utils/index.d.ts.map +1 -0
  192. package/src/core/utils/index.ts +19 -0
@@ -0,0 +1,613 @@
1
+ // ============================================================================
2
+ // InkComponents - Premium terminal UI components for Nova CLI
3
+ // Inspired by Claude Code and iFlow CLI design patterns
4
+ // ============================================================================
5
+
6
+ import React, { useState, useEffect, useRef } from 'react';
7
+ import { Box, Text, useApp, useInput } from 'ink';
8
+ import TextInput from 'ink-text-input';
9
+
10
+ // ============================================================================
11
+ // Design System - Colors & Styles
12
+ // ============================================================================
13
+
14
+ export const Colors = {
15
+ // Brand colors (Purple theme)
16
+ brand: '#7C3AED',
17
+ brandLight: '#A78BFA',
18
+ brandDark: '#5B21B6',
19
+
20
+ // Semantic colors
21
+ success: '#10B981',
22
+ warning: '#F59E0B',
23
+ error: '#EF4444',
24
+ info: '#3B82F6',
25
+
26
+ // Text colors
27
+ primary: '#F9FAFB',
28
+ secondary: '#9CA3AF',
29
+ muted: '#6B7280',
30
+ dim: '#4B5563',
31
+
32
+ // Accent colors
33
+ cyan: '#06B6D4',
34
+ pink: '#EC4899',
35
+ orange: '#F97316',
36
+ lime: '#84CC16',
37
+
38
+ // Background
39
+ bgDark: '#1F2937',
40
+ bgDarker: '#111827',
41
+ };
42
+
43
+ // ============================================================================
44
+ // Spinner Component
45
+ // ============================================================================
46
+
47
+ interface SpinnerProps {
48
+ message?: string;
49
+ color?: string;
50
+ }
51
+
52
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
53
+
54
+ export const Spinner: React.FC<SpinnerProps> = ({
55
+ message = 'Loading...',
56
+ color = Colors.brand
57
+ }) => {
58
+ const [frame, setFrame] = useState(0);
59
+
60
+ useEffect(() => {
61
+ const timer = setInterval(() => {
62
+ setFrame(f => (f + 1) % SPINNER_FRAMES.length);
63
+ }, 80);
64
+ return () => clearInterval(timer);
65
+ }, []);
66
+
67
+ return (
68
+ <Box>
69
+ <Text color={color}>{SPINNER_FRAMES[frame]}</Text>
70
+ <Text> {message}</Text>
71
+ </Box>
72
+ );
73
+ };
74
+
75
+ // ============================================================================
76
+ // Status Bar Component
77
+ // ============================================================================
78
+
79
+ interface StatusBarProps {
80
+ model: string;
81
+ mode: 'auto' | 'plan' | 'ask';
82
+ contextUsage: number;
83
+ sessionId: string;
84
+ mcpConnected?: number;
85
+ mcpTotal?: number;
86
+ }
87
+
88
+ export const StatusBar: React.FC<StatusBarProps> = ({
89
+ model,
90
+ mode,
91
+ contextUsage,
92
+ sessionId,
93
+ mcpConnected = 0,
94
+ mcpTotal = 0,
95
+ }) => {
96
+ const modeColors = { auto: Colors.success, plan: Colors.warning, ask: Colors.info };
97
+ const modeLabels = { auto: 'AUTO', plan: 'PLAN', ask: 'ASK' };
98
+
99
+ // Context usage color
100
+ let contextColor = Colors.success;
101
+ if (contextUsage > 80) contextColor = Colors.error;
102
+ else if (contextUsage > 50) contextColor = Colors.warning;
103
+
104
+ // Model short name
105
+ const modelShort = model.split('/').pop() || model;
106
+
107
+ return (
108
+ <Box
109
+ borderStyle="round"
110
+ borderColor={Colors.brand}
111
+ paddingX={1}
112
+ >
113
+ <Box flexGrow={1}>
114
+ {/* Model */}
115
+ <Text bold color={Colors.brand}>◆ </Text>
116
+ <Text bold>{modelShort}</Text>
117
+ <Text dimColor> │ </Text>
118
+
119
+ {/* Mode */}
120
+ <Text bold color={modeColors[mode]}>● {modeLabels[mode]}</Text>
121
+ <Text dimColor> │ </Text>
122
+
123
+ {/* Context */}
124
+ <Text>ctx: </Text>
125
+ <Text bold color={contextColor}>{contextUsage}%</Text>
126
+ </Box>
127
+
128
+ <Box>
129
+ {/* MCP Status */}
130
+ {mcpTotal > 0 && (
131
+ <>
132
+ <Text dimColor>mcp: </Text>
133
+ <Text color={mcpConnected === mcpTotal ? Colors.success : Colors.warning}>
134
+ {mcpConnected}/{mcpTotal}
135
+ </Text>
136
+ <Text dimColor> │ </Text>
137
+ </>
138
+ )}
139
+
140
+ {/* Session */}
141
+ <Text dimColor>session: </Text>
142
+ <Text>{sessionId.slice(0, 8)}</Text>
143
+ </Box>
144
+ </Box>
145
+ );
146
+ };
147
+
148
+ // ============================================================================
149
+ // Input Box Component
150
+ // ============================================================================
151
+
152
+ interface InputBoxProps {
153
+ onSubmit: (value: string) => void;
154
+ placeholder?: string;
155
+ disabled?: boolean;
156
+ initialValue?: string;
157
+ }
158
+
159
+ export const InputBox: React.FC<InputBoxProps> = ({
160
+ onSubmit,
161
+ placeholder = 'Type a message...',
162
+ disabled = false,
163
+ initialValue = '',
164
+ }) => {
165
+ const [value, setValue] = useState(initialValue);
166
+ const [multiline, setMultiline] = useState(false);
167
+ const [lines, setLines] = useState<string[]>([]);
168
+
169
+ const handleSubmit = (submittedValue: string) => {
170
+ if (disabled) return;
171
+
172
+ if (submittedValue.endsWith('\\')) {
173
+ // Multi-line mode
174
+ setMultiline(true);
175
+ setLines([...lines, submittedValue.slice(0, -1)]);
176
+ setValue('');
177
+ return;
178
+ }
179
+
180
+ if (multiline && lines.length > 0) {
181
+ // End multi-line mode
182
+ const fullInput = [...lines, submittedValue].join('\n');
183
+ onSubmit(fullInput);
184
+ setLines([]);
185
+ setMultiline(false);
186
+ } else {
187
+ onSubmit(submittedValue);
188
+ }
189
+ setValue('');
190
+ };
191
+
192
+ const showPrompt = multiline ? '│ ' : '❯ ';
193
+
194
+ return (
195
+ <Box flexDirection="column" borderStyle="round" borderColor={disabled ? Colors.dim : Colors.brand}>
196
+ {multiline && (
197
+ <Box flexDirection="column" paddingX={1}>
198
+ {lines.map((line, i) => (
199
+ <Text key={i} dimColor>│ {line}</Text>
200
+ ))}
201
+ </Box>
202
+ )}
203
+ <Box>
204
+ <Text bold color={Colors.brand}>{showPrompt}</Text>
205
+ {disabled ? (
206
+ <Text dimColor>{placeholder}</Text>
207
+ ) : (
208
+ <TextInput
209
+ value={value}
210
+ onChange={setValue}
211
+ onSubmit={handleSubmit}
212
+ placeholder={multiline ? '(empty line to finish)' : placeholder}
213
+ showCursor={true}
214
+ />
215
+ )}
216
+ </Box>
217
+ </Box>
218
+ );
219
+ };
220
+
221
+ // ============================================================================
222
+ // Message List Component
223
+ // ============================================================================
224
+
225
+ interface Message {
226
+ id: string;
227
+ role: 'user' | 'assistant' | 'tool';
228
+ content: string;
229
+ timestamp: Date;
230
+ }
231
+
232
+ interface MessageListProps {
233
+ messages: Message[];
234
+ maxVisible?: number;
235
+ }
236
+
237
+ export const MessageList: React.FC<MessageListProps> = ({
238
+ messages,
239
+ maxVisible = 50,
240
+ }) => {
241
+ const visibleMessages = messages.slice(-maxVisible);
242
+
243
+ return (
244
+ <Box flexDirection="column">
245
+ {visibleMessages.map((msg, index) => (
246
+ <MessageItem
247
+ key={msg.id}
248
+ message={msg}
249
+ isLast={index === visibleMessages.length - 1}
250
+ />
251
+ ))}
252
+ </Box>
253
+ );
254
+ };
255
+
256
+ // ============================================================================
257
+ // Message Item Component
258
+ // ============================================================================
259
+
260
+ interface MessageItemProps {
261
+ message: Message;
262
+ isLast?: boolean;
263
+ }
264
+
265
+ const MessageItem: React.FC<MessageItemProps> = ({ message, isLast }) => {
266
+ const roleIcons = {
267
+ user: '👤',
268
+ assistant: '◆',
269
+ tool: '⚙',
270
+ };
271
+
272
+ const roleColors = {
273
+ user: Colors.info,
274
+ assistant: Colors.brand,
275
+ tool: Colors.cyan,
276
+ };
277
+
278
+ // Truncate long content
279
+ const content = message.content.length > 500
280
+ ? message.content.slice(0, 500) + '...'
281
+ : message.content;
282
+
283
+ // Format timestamp
284
+ const time = message.timestamp.toLocaleTimeString('en-US', {
285
+ hour: '2-digit',
286
+ minute: '2-digit',
287
+ hour12: false,
288
+ });
289
+
290
+ return (
291
+ <Box flexDirection="column" marginBottom={1}>
292
+ {/* Header */}
293
+ <Box>
294
+ <Text bold color={roleColors[message.role]}>
295
+ {roleIcons[message.role]} {message.role === 'user' ? 'You' : 'Nova'}
296
+ </Text>
297
+ <Text dimColor> {time}</Text>
298
+ </Box>
299
+
300
+ {/* Content */}
301
+ <Box marginLeft={2}>
302
+ <Text wrap="wrap">{content}</Text>
303
+ </Box>
304
+ </Box>
305
+ );
306
+ };
307
+
308
+ // ============================================================================
309
+ // Tool Call Panel Component
310
+ // ============================================================================
311
+
312
+ interface ToolCall {
313
+ id: string;
314
+ name: string;
315
+ status: 'pending' | 'running' | 'success' | 'error';
316
+ duration?: number;
317
+ input?: string;
318
+ output?: string;
319
+ }
320
+
321
+ interface ToolCallPanelProps {
322
+ tools: ToolCall[];
323
+ }
324
+
325
+ export const ToolCallPanel: React.FC<ToolCallPanelProps> = ({ tools }) => {
326
+ if (tools.length === 0) return null;
327
+
328
+ return (
329
+ <Box flexDirection="column" marginTop={1} borderStyle="round" borderColor={Colors.dim}>
330
+ <Box>
331
+ <Text bold color={Colors.cyan}>⚡ Tool Calls</Text>
332
+ <Text dimColor> ({tools.length})</Text>
333
+ </Box>
334
+ {tools.map(tool => (
335
+ <ToolCallItem key={tool.id} tool={tool} />
336
+ ))}
337
+ </Box>
338
+ );
339
+ };
340
+
341
+ // ============================================================================
342
+ // Tool Call Item Component
343
+ // ============================================================================
344
+
345
+ interface ToolCallItemProps {
346
+ tool: ToolCall;
347
+ }
348
+
349
+ const ToolCallItem: React.FC<ToolCallItemProps> = ({ tool }) => {
350
+ const statusIcons = {
351
+ pending: '○',
352
+ running: '◐',
353
+ success: '●',
354
+ error: '✗',
355
+ };
356
+
357
+ const statusColors = {
358
+ pending: Colors.dim,
359
+ running: Colors.warning,
360
+ success: Colors.success,
361
+ error: Colors.error,
362
+ };
363
+
364
+ return (
365
+ <Box>
366
+ <Text color={statusColors[tool.status]}>{statusIcons[tool.status]}</Text>
367
+ <Text> {tool.name}</Text>
368
+ {tool.duration && (
369
+ <Text dimColor> ({tool.duration}ms)</Text>
370
+ )}
371
+ {tool.status === 'running' && (
372
+ <Spinner message="" color={statusColors[tool.status]} />
373
+ )}
374
+ </Box>
375
+ );
376
+ };
377
+
378
+ // ============================================================================
379
+ // Thinking Block Component
380
+ // ============================================================================
381
+
382
+ interface ThinkingBlockProps {
383
+ content: string;
384
+ expanded?: boolean;
385
+ }
386
+
387
+ export const ThinkingBlock: React.FC<ThinkingBlockProps> = ({
388
+ content,
389
+ expanded = false
390
+ }) => {
391
+ const [isExpanded, setIsExpanded] = useState(expanded);
392
+
393
+ const displayContent = isExpanded
394
+ ? content
395
+ : content.slice(0, 100) + (content.length > 100 ? '...' : '');
396
+
397
+ return (
398
+ <Box
399
+ flexDirection="column"
400
+ borderStyle="round"
401
+ borderColor={Colors.dim}
402
+ marginTop={1}
403
+ >
404
+ <Box>
405
+ <Text dimColor>💭 Thinking</Text>
406
+ <Text dimColor> ({content.length} chars)</Text>
407
+ <Text dimColor>
408
+ {' '}[{isExpanded ? 'collapse' : 'expand'}]
409
+ </Text>
410
+ </Box>
411
+ <Box marginLeft={2}>
412
+ <Text dimColor wrap="wrap">{displayContent}</Text>
413
+ </Box>
414
+ </Box>
415
+ );
416
+ };
417
+
418
+ // ============================================================================
419
+ // Progress Bar Component
420
+ // ============================================================================
421
+
422
+ interface ProgressBarProps {
423
+ percent: number;
424
+ label?: string;
425
+ width?: number;
426
+ showPercent?: boolean;
427
+ color?: string;
428
+ }
429
+
430
+ export const ProgressBar: React.FC<ProgressBarProps> = ({
431
+ percent,
432
+ label,
433
+ width = 30,
434
+ showPercent = true,
435
+ color = Colors.brand,
436
+ }) => {
437
+ const filled = Math.round((percent / 100) * width);
438
+ const empty = width - filled;
439
+
440
+ return (
441
+ <Box>
442
+ {label && <Text>{label} </Text>}
443
+ <Text color={color}>{'█'.repeat(filled)}</Text>
444
+ <Text dimColor>{'░'.repeat(empty)}</Text>
445
+ {showPercent && <Text> {percent}%</Text>}
446
+ </Box>
447
+ );
448
+ };
449
+
450
+ // ============================================================================
451
+ // Confirm Dialog Component
452
+ // ============================================================================
453
+
454
+ interface ConfirmDialogProps {
455
+ message: string;
456
+ onConfirm: () => void;
457
+ onCancel: () => void;
458
+ danger?: boolean;
459
+ }
460
+
461
+ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
462
+ message,
463
+ onConfirm,
464
+ onCancel,
465
+ danger = false,
466
+ }) => {
467
+ useInput((char, key) => {
468
+ if (char === 'y' || char === 'Y') {
469
+ onConfirm();
470
+ } else if (char === 'n' || char === 'N' || key.escape) {
471
+ onCancel();
472
+ }
473
+ });
474
+
475
+ return (
476
+ <Box
477
+ flexDirection="column"
478
+ borderStyle="round"
479
+ borderColor={danger ? Colors.error : Colors.warning}
480
+ padding={1}
481
+ >
482
+ <Text bold color={danger ? Colors.error : Colors.warning}>
483
+ {danger ? '⚠️ Warning' : '❓ Confirm'}
484
+ </Text>
485
+ <Text>{message}</Text>
486
+ <Text dimColor>Press Y to confirm, N or Escape to cancel</Text>
487
+ </Box>
488
+ );
489
+ };
490
+
491
+ // ============================================================================
492
+ // Select List Component (for model selection, etc.)
493
+ // ============================================================================
494
+
495
+ interface SelectItem {
496
+ label: string;
497
+ value: string;
498
+ description?: string;
499
+ }
500
+
501
+ interface SelectListProps {
502
+ items: SelectItem[];
503
+ onSelect: (item: SelectItem) => void;
504
+ onCancel: () => void;
505
+ title?: string;
506
+ }
507
+
508
+ export const SelectList: React.FC<SelectListProps> = ({
509
+ items,
510
+ onSelect,
511
+ onCancel,
512
+ title = 'Select an option',
513
+ }) => {
514
+ const [selectedIndex, setSelectedIndex] = useState(0);
515
+
516
+ useInput((char, key) => {
517
+ if (key.upArrow) {
518
+ setSelectedIndex(i => Math.max(0, i - 1));
519
+ } else if (key.downArrow) {
520
+ setSelectedIndex(i => Math.min(items.length - 1, i + 1));
521
+ } else if (key.return) {
522
+ const selectedItem = items[selectedIndex];
523
+ if (selectedItem) {
524
+ onSelect(selectedItem);
525
+ }
526
+ } else if (key.escape) {
527
+ onCancel();
528
+ }
529
+ });
530
+
531
+ return (
532
+ <Box flexDirection="column" borderStyle="round" borderColor={Colors.brand}>
533
+ <Text bold color={Colors.brand}>{title}</Text>
534
+ <Text dimColor>↑↓ to navigate, Enter to select, Esc to cancel</Text>
535
+ <Text></Text>
536
+
537
+ {items.map((item, index) => (
538
+ <Box key={item.value}>
539
+ <Text color={index === selectedIndex ? Colors.brand : undefined}>
540
+ {index === selectedIndex ? '❯ ' : ' '}
541
+ </Text>
542
+ <Text bold={index === selectedIndex}>{item.label}</Text>
543
+ {item.description && (
544
+ <Text dimColor> - {item.description}</Text>
545
+ )}
546
+ </Box>
547
+ ))}
548
+ </Box>
549
+ );
550
+ };
551
+
552
+ // ============================================================================
553
+ // Toast Notification Component
554
+ // ============================================================================
555
+
556
+ interface ToastProps {
557
+ message: string;
558
+ type: 'success' | 'error' | 'warning' | 'info';
559
+ duration?: number;
560
+ onDismiss: () => void;
561
+ }
562
+
563
+ export const Toast: React.FC<ToastProps> = ({
564
+ message,
565
+ type,
566
+ duration = 3000,
567
+ onDismiss,
568
+ }) => {
569
+ const icons = {
570
+ success: '✓',
571
+ error: '✗',
572
+ warning: '⚠',
573
+ info: 'ℹ',
574
+ };
575
+
576
+ const colors = {
577
+ success: Colors.success,
578
+ error: Colors.error,
579
+ warning: Colors.warning,
580
+ info: Colors.info,
581
+ };
582
+
583
+ useEffect(() => {
584
+ const timer = setTimeout(onDismiss, duration);
585
+ return () => clearTimeout(timer);
586
+ }, [duration, onDismiss]);
587
+
588
+ return (
589
+ <Box borderStyle="round" borderColor={colors[type]}>
590
+ <Text color={colors[type]} bold>{icons[type]}</Text>
591
+ <Text> {message}</Text>
592
+ </Box>
593
+ );
594
+ };
595
+
596
+ // ============================================================================
597
+ // Export All Components
598
+ // ============================================================================
599
+
600
+ export default {
601
+ Spinner,
602
+ StatusBar,
603
+ InputBox,
604
+ MessageList,
605
+ MessageItem,
606
+ ToolCallPanel,
607
+ ToolCallItem,
608
+ ThinkingBlock,
609
+ ProgressBar,
610
+ ConfirmDialog,
611
+ SelectList,
612
+ Toast,
613
+ };