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,312 @@
1
+ // ============================================================================
2
+ // NovaInkApp - Main Ink-based CLI application
3
+ // ============================================================================
4
+
5
+ import React, { useState, useEffect, useCallback } from 'react';
6
+ import { Box, Text, useApp, useInput } from 'ink';
7
+ import { StatusBar, InputBox, MessageList, ToolCallPanel, Spinner, ThinkingBlock } from './InkComponents.js';
8
+
9
+ // ============================================================================
10
+ // Types
11
+ // ============================================================================
12
+
13
+ interface Message {
14
+ id: string;
15
+ role: 'user' | 'assistant' | 'tool';
16
+ content: string;
17
+ timestamp: Date;
18
+ }
19
+
20
+ interface ToolCall {
21
+ id: string;
22
+ name: string;
23
+ status: 'pending' | 'running' | 'success' | 'error';
24
+ duration?: number;
25
+ }
26
+
27
+ interface AppState {
28
+ model: string;
29
+ mode: 'auto' | 'plan' | 'ask';
30
+ contextUsage: number;
31
+ sessionId: string;
32
+ messages: Message[];
33
+ activeTools: ToolCall[];
34
+ processing: boolean;
35
+ thinkingContent: string;
36
+ showThinking: boolean;
37
+ }
38
+
39
+ interface NovaAppProps {
40
+ initialModel?: string;
41
+ initialMode?: 'auto' | 'plan' | 'ask';
42
+ sessionId?: string;
43
+ onSubmit?: (input: string) => Promise<void>;
44
+ onCommand?: (command: string) => Promise<void>;
45
+ }
46
+
47
+ // ============================================================================
48
+ // Help Screen Component
49
+ // ============================================================================
50
+
51
+ const HelpScreen: React.FC<{ onDismiss: () => void }> = ({ onDismiss }) => {
52
+ useInput((char, key) => {
53
+ if (key.escape || char === 'q') {
54
+ onDismiss();
55
+ }
56
+ });
57
+
58
+ return (
59
+ <Box flexDirection="column" padding={1}>
60
+ <Text bold color="magenta">Nova CLI - Help</Text>
61
+ <Text></Text>
62
+
63
+ <Text bold>Navigation</Text>
64
+ <Text dimColor> /help Show this help</Text>
65
+ <Text dimColor> /quit Exit Nova CLI</Text>
66
+ <Text dimColor> /clear Clear conversation</Text>
67
+ <Text></Text>
68
+
69
+ <Text bold>Model & Mode</Text>
70
+ <Text dimColor> /model Switch model (interactive)</Text>
71
+ <Text dimColor> /mode Cycle mode: AUTO → PLAN → ASK</Text>
72
+ <Text dimColor> /status Show session status</Text>
73
+ <Text></Text>
74
+
75
+ <Text bold>Memory & Context</Text>
76
+ <Text dimColor> /init Generate NOVA.md</Text>
77
+ <Text dimColor> /memory Manage memory</Text>
78
+ <Text dimColor> /compact Toggle compact mode</Text>
79
+ <Text></Text>
80
+
81
+ <Text bold>Tools & Extensions</Text>
82
+ <Text dimColor> /tools List available tools</Text>
83
+ <Text dimColor> /mcp MCP server status</Text>
84
+ <Text dimColor> /skills Available skills</Text>
85
+ <Text dimColor> /ollama Ollama status</Text>
86
+ <Text></Text>
87
+
88
+ <Text bold>Shortcuts</Text>
89
+ <Text dimColor> @file Inject file content</Text>
90
+ <Text dimColor> !command Run shell command</Text>
91
+ <Text dimColor> \ Multi-line input</Text>
92
+ <Text dimColor> Tab Command completion</Text>
93
+ <Text></Text>
94
+
95
+ <Text dimColor>Press Escape or Q to close</Text>
96
+ </Box>
97
+ );
98
+ };
99
+
100
+ // ============================================================================
101
+ // Mode Badge Component
102
+ // ============================================================================
103
+
104
+ const ModeBadge: React.FC<{ mode: 'auto' | 'plan' | 'ask' }> = ({ mode }) => {
105
+ const colors = { auto: 'green', plan: 'yellow', ask: 'cyan' };
106
+ const labels = { auto: 'AUTO', plan: 'PLAN', ask: 'ASK' };
107
+ const descriptions = {
108
+ auto: 'Full autonomous',
109
+ plan: 'Plan first',
110
+ ask: 'Answer only',
111
+ };
112
+
113
+ return (
114
+ <Box>
115
+ <Text bold color={colors[mode]}>[{labels[mode]}]</Text>
116
+ <Text dimColor> {descriptions[mode]}</Text>
117
+ </Box>
118
+ );
119
+ };
120
+
121
+ // ============================================================================
122
+ // Main NovaInkApp Component
123
+ // ============================================================================
124
+
125
+ export const NovaInkApp: React.FC<NovaAppProps> = ({
126
+ initialModel = 'claude-3-sonnet',
127
+ initialMode = 'auto',
128
+ sessionId,
129
+ onSubmit,
130
+ onCommand,
131
+ }) => {
132
+ const { exit } = useApp();
133
+ const [showHelp, setShowHelp] = useState(false);
134
+ const [state, setState] = useState<AppState>({
135
+ model: initialModel,
136
+ mode: initialMode,
137
+ contextUsage: 0,
138
+ sessionId: sessionId || 'new',
139
+ messages: [],
140
+ activeTools: [],
141
+ processing: false,
142
+ thinkingContent: '',
143
+ showThinking: true,
144
+ });
145
+
146
+ // Handle global keyboard shortcuts
147
+ useInput((char, key) => {
148
+ if (showHelp) {
149
+ if (key.escape || char === 'q') {
150
+ setShowHelp(false);
151
+ }
152
+ return;
153
+ }
154
+
155
+ // Ctrl+C to exit
156
+ if (key.ctrl && char === 'c') {
157
+ exit();
158
+ }
159
+ });
160
+
161
+ // Handle input submission
162
+ const handleSubmit = useCallback(async (input: string) => {
163
+ if (!input.trim()) return;
164
+
165
+ // Handle commands
166
+ if (input.startsWith('/')) {
167
+ const command = input.slice(1).toLowerCase();
168
+
169
+ switch (command) {
170
+ case 'help':
171
+ case 'h':
172
+ case '?':
173
+ setShowHelp(true);
174
+ return;
175
+
176
+ case 'quit':
177
+ case 'exit':
178
+ case 'q':
179
+ exit();
180
+ return;
181
+
182
+ case 'clear':
183
+ setState(s => ({ ...s, messages: [], contextUsage: 0 }));
184
+ return;
185
+
186
+ case 'mode':
187
+ setState(s => ({
188
+ ...s,
189
+ mode: s.mode === 'auto' ? 'plan' : s.mode === 'plan' ? 'ask' : 'auto',
190
+ }));
191
+ return;
192
+
193
+ case 'thinking':
194
+ setState(s => ({ ...s, showThinking: !s.showThinking }));
195
+ return;
196
+ }
197
+
198
+ // Forward other commands
199
+ if (onCommand) {
200
+ await onCommand(input);
201
+ }
202
+ return;
203
+ }
204
+
205
+ // Add user message
206
+ const userMessage: Message = {
207
+ id: Date.now().toString(),
208
+ role: 'user',
209
+ content: input,
210
+ timestamp: new Date(),
211
+ };
212
+
213
+ setState(s => ({
214
+ ...s,
215
+ messages: [...s.messages, userMessage],
216
+ processing: true,
217
+ }));
218
+
219
+ // Call submit handler
220
+ if (onSubmit) {
221
+ try {
222
+ await onSubmit(input);
223
+ } finally {
224
+ setState(s => ({ ...s, processing: false }));
225
+ }
226
+ } else {
227
+ // Demo mode - simulate response
228
+ setTimeout(() => {
229
+ const assistantMessage: Message = {
230
+ id: (Date.now() + 1).toString(),
231
+ role: 'assistant',
232
+ content: 'I received your message. In demo mode, this is a simulated response.',
233
+ timestamp: new Date(),
234
+ };
235
+
236
+ setState(s => ({
237
+ ...s,
238
+ messages: [...s.messages, assistantMessage],
239
+ processing: false,
240
+ contextUsage: Math.min(100, s.contextUsage + 5),
241
+ }));
242
+ }, 1000);
243
+ }
244
+ }, [onSubmit, onCommand, exit]);
245
+
246
+ // Render help screen
247
+ if (showHelp) {
248
+ return <HelpScreen onDismiss={() => setShowHelp(false)} />;
249
+ }
250
+
251
+ return (
252
+ <Box flexDirection="column" height="100%">
253
+ {/* Status Bar */}
254
+ <StatusBar
255
+ model={state.model}
256
+ mode={state.mode}
257
+ contextUsage={state.contextUsage}
258
+ sessionId={state.sessionId}
259
+ />
260
+
261
+ {/* Message List */}
262
+ <Box flexGrow={1} flexDirection="column" overflow="hidden" marginY={1}>
263
+ {state.messages.length === 0 ? (
264
+ <Box flexDirection="column" alignItems="center" justifyContent="center" height="100%">
265
+ <Text bold color="magenta">Nova CLI</Text>
266
+ <Text dimColor>AI-powered terminal assistant</Text>
267
+ <Text></Text>
268
+ <Text dimColor>Type a message or /help for commands</Text>
269
+ </Box>
270
+ ) : (
271
+ <MessageList messages={state.messages} />
272
+ )}
273
+
274
+ {/* Thinking Block */}
275
+ {state.thinkingContent && state.showThinking && (
276
+ <ThinkingBlock content={state.thinkingContent} />
277
+ )}
278
+
279
+ {/* Tool Calls */}
280
+ {state.activeTools.length > 0 && (
281
+ <ToolCallPanel tools={state.activeTools} />
282
+ )}
283
+ </Box>
284
+
285
+ {/* Processing Indicator */}
286
+ {state.processing && (
287
+ <Box marginBottom={1}>
288
+ <Spinner message="Thinking..." />
289
+ </Box>
290
+ )}
291
+
292
+ {/* Input Box */}
293
+ <InputBox
294
+ onSubmit={handleSubmit}
295
+ placeholder={state.processing ? 'Processing...' : 'Type a message or /help'}
296
+ disabled={state.processing}
297
+ />
298
+
299
+ {/* Footer */}
300
+ <Box justifyContent="space-between" marginTop={1}>
301
+ <ModeBadge mode={state.mode} />
302
+ <Text dimColor>/help for commands | Ctrl+C to exit</Text>
303
+ </Box>
304
+ </Box>
305
+ );
306
+ };
307
+
308
+ // ============================================================================
309
+ // Export
310
+ // ============================================================================
311
+
312
+ export default NovaInkApp;
@@ -0,0 +1,177 @@
1
+ // ============================================================================
2
+ // ProgressBar - Terminal progress bar component
3
+ // ============================================================================
4
+
5
+ import chalk from 'chalk';
6
+
7
+ export interface ProgressBarOptions {
8
+ /** Total progress value (default: 100) */
9
+ total?: number;
10
+ /** Current progress value (default: 0) */
11
+ current?: number;
12
+ /** Bar width in characters (default: 40) */
13
+ width?: number;
14
+ /** Progress bar color */
15
+ color?: string;
16
+ /** Show percentage (default: true) */
17
+ showPercentage?: boolean;
18
+ /** Show value (default: true) */
19
+ showValue?: boolean;
20
+ /** Custom label */
21
+ label?: string;
22
+ /** Clear line when complete (default: false) */
23
+ clearOnComplete?: boolean;
24
+ }
25
+
26
+ export class ProgressBar {
27
+ private options: Required<ProgressBarOptions>;
28
+ private startTime: number;
29
+ private lastRender: string = '';
30
+
31
+ constructor(options: ProgressBarOptions = {}) {
32
+ this.options = {
33
+ total: options.total ?? 100,
34
+ current: options.current ?? 0,
35
+ width: options.width ?? 40,
36
+ color: options.color ?? '#10B981',
37
+ showPercentage: options.showPercentage ?? true,
38
+ showValue: options.showValue ?? true,
39
+ label: options.label ?? '',
40
+ clearOnComplete: options.clearOnComplete ?? false,
41
+ };
42
+ this.startTime = Date.now();
43
+ }
44
+
45
+ /**
46
+ * Update progress value
47
+ */
48
+ update(current: number): void {
49
+ this.options.current = Math.min(current, this.options.total);
50
+ this.render();
51
+ }
52
+
53
+ /**
54
+ * Increment progress by delta
55
+ */
56
+ increment(delta: number = 1): void {
57
+ this.options.current += delta;
58
+ this.options.current = Math.min(this.options.current, this.options.total);
59
+ this.render();
60
+ }
61
+
62
+ /**
63
+ * Set progress to 100% and optionally clear
64
+ */
65
+ complete(): void {
66
+ this.options.current = this.options.total;
67
+ this.render();
68
+ if (this.options.clearOnComplete) {
69
+ this.clear();
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Clear the progress bar from terminal
75
+ */
76
+ clear(): void {
77
+ const width = process.stdout.columns || 80;
78
+ process.stdout.write('\r' + ' '.repeat(width - 1) + '\r');
79
+ }
80
+
81
+ /**
82
+ * Render the progress bar
83
+ */
84
+ private render(): void {
85
+ const { current, total, width, color, showPercentage, showValue, label } = this.options;
86
+
87
+ const percentage = Math.min(100, Math.max(0, (current / total) * 100));
88
+ const filledWidth = Math.floor((percentage / 100) * width);
89
+ const emptyWidth = width - filledWidth;
90
+
91
+ const colorFn = chalk.hex(color);
92
+ const bar = colorFn('━'.repeat(filledWidth)) + chalk.dim('─'.repeat(emptyWidth));
93
+
94
+ let output = '\r';
95
+
96
+ if (label) {
97
+ output += chalk.cyan(label) + ' ';
98
+ }
99
+
100
+ output += '╭' + bar + '╮';
101
+
102
+ if (showPercentage) {
103
+ output += ' ' + chalk.bold(percentage.toFixed(1) + '%');
104
+ }
105
+
106
+ if (showValue) {
107
+ output += chalk.dim(` (${current}/${total})`);
108
+ }
109
+
110
+ // Add ETA if not complete
111
+ if (percentage > 0 && percentage < 100) {
112
+ const elapsed = (Date.now() - this.startTime) / 1000;
113
+ const rate = current / elapsed;
114
+ const remaining = total - current;
115
+ const eta = remaining / rate;
116
+
117
+ if (eta > 0) {
118
+ const minutes = Math.floor(eta / 60);
119
+ const seconds = Math.floor(eta % 60);
120
+ output += chalk.dim(` ETA: ${minutes}:${seconds.toString().padStart(2, '0')}`);
121
+ }
122
+ }
123
+
124
+ // Prevent flickering by only updating if changed
125
+ if (output !== this.lastRender) {
126
+ process.stdout.write(output);
127
+ this.lastRender = output;
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Create and manage multiple progress bars
134
+ */
135
+ export class MultiProgressBar {
136
+ private bars: ProgressBar[] = [];
137
+ private startLine: number = 0;
138
+
139
+ constructor() {
140
+ this.startLine = this.getCurrentLine();
141
+ }
142
+
143
+ /**
144
+ * Add a new progress bar
145
+ */
146
+ add(options: ProgressBarOptions = {}): ProgressBar {
147
+ const bar = new ProgressBar(options);
148
+ this.bars.push(bar);
149
+ return bar;
150
+ }
151
+
152
+ /**
153
+ * Remove a progress bar
154
+ */
155
+ remove(bar: ProgressBar): void {
156
+ const index = this.bars.indexOf(bar);
157
+ if (index >= 0) {
158
+ this.bars.splice(index, 1);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Get current terminal line
164
+ */
165
+ private getCurrentLine(): number {
166
+ // This is a simplified version - in practice you'd track this manually
167
+ return 0;
168
+ }
169
+ }
170
+
171
+ // Example usage:
172
+ // const bar = new ProgressBar({ label: 'Processing', total: 100 });
173
+ // for (let i = 0; i <= 100; i++) {
174
+ // bar.update(i);
175
+ // await sleep(50);
176
+ // }
177
+ // bar.complete();