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.
- package/README.md +358 -0
- package/bin/nova +38 -0
- package/bin/nova.js +12 -0
- package/package.json +67 -0
- package/src/cli/commands/SmartCompletion.ts +458 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/startup/IFlowRepl.ts +212 -0
- package/src/cli/startup/InkBasedRepl.ts +1056 -0
- package/src/cli/startup/InteractiveRepl.ts +2833 -0
- package/src/cli/startup/NovaApp.ts +1861 -0
- package/src/cli/startup/index.ts +4 -0
- package/src/cli/startup/parseArgs.ts +293 -0
- package/src/cli/test-modules.ts +27 -0
- package/src/cli/ui/IFlowDropdown.ts +425 -0
- package/src/cli/ui/ModernReplUI.ts +276 -0
- package/src/cli/ui/SimpleSelector2.ts +215 -0
- package/src/cli/ui/components/ConfirmDialog.ts +176 -0
- package/src/cli/ui/components/ErrorPanel.ts +364 -0
- package/src/cli/ui/components/InkAppRunner.tsx +67 -0
- package/src/cli/ui/components/InkComponents.tsx +613 -0
- package/src/cli/ui/components/NovaInkApp.tsx +312 -0
- package/src/cli/ui/components/ProgressBar.ts +177 -0
- package/src/cli/ui/components/ProgressIndicator.ts +298 -0
- package/src/cli/ui/components/QuickActions.ts +396 -0
- package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
- package/src/cli/ui/components/StatusBar.ts +194 -0
- package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
- package/src/cli/ui/components/index.ts +27 -0
- package/src/cli/ui/ink-prototype.tsx +347 -0
- package/src/cli/utils/CliUI.ts +336 -0
- package/src/cli/utils/CompletionHelper.ts +388 -0
- package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
- package/src/cli/utils/EnhancedCompleter.ts +513 -0
- package/src/cli/utils/ErrorEnhancer.ts +429 -0
- package/src/cli/utils/OutputFormatter.ts +193 -0
- package/src/cli/utils/index.ts +9 -0
- package/src/core/agents/AgentOrchestrator.ts +515 -0
- package/src/core/agents/index.ts +17 -0
- package/src/core/audit/AuditLogger.ts +509 -0
- package/src/core/audit/index.ts +11 -0
- package/src/core/auth/AuthManager.d.ts.map +1 -0
- package/src/core/auth/AuthManager.ts +138 -0
- package/src/core/auth/index.d.ts.map +1 -0
- package/src/core/auth/index.ts +2 -0
- package/src/core/config/ConfigManager.d.ts.map +1 -0
- package/src/core/config/ConfigManager.test.ts +183 -0
- package/src/core/config/ConfigManager.ts +1219 -0
- package/src/core/config/index.d.ts.map +1 -0
- package/src/core/config/index.ts +1 -0
- package/src/core/context/ContextBuilder.d.ts.map +1 -0
- package/src/core/context/ContextBuilder.ts +171 -0
- package/src/core/context/ContextCompressor.d.ts.map +1 -0
- package/src/core/context/ContextCompressor.ts +642 -0
- package/src/core/context/LayeredMemoryManager.ts +657 -0
- package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
- package/src/core/context/MemoryDiscovery.ts +175 -0
- package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
- package/src/core/context/defaultSystemPrompt.ts +35 -0
- package/src/core/context/index.d.ts.map +1 -0
- package/src/core/context/index.ts +22 -0
- package/src/core/extensions/SkillGenerator.ts +421 -0
- package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
- package/src/core/extensions/SkillInstaller.ts +257 -0
- package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
- package/src/core/extensions/SkillRegistry.ts +361 -0
- package/src/core/extensions/SkillValidator.ts +525 -0
- package/src/core/extensions/index.ts +15 -0
- package/src/core/index.d.ts.map +1 -0
- package/src/core/index.ts +42 -0
- package/src/core/mcp/McpManager.d.ts.map +1 -0
- package/src/core/mcp/McpManager.ts +632 -0
- package/src/core/mcp/index.d.ts.map +1 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/model/ModelClient.d.ts.map +1 -0
- package/src/core/model/ModelClient.ts +217 -0
- package/src/core/model/ModelConnectionTester.ts +363 -0
- package/src/core/model/ModelValidator.ts +348 -0
- package/src/core/model/index.d.ts.map +1 -0
- package/src/core/model/index.ts +6 -0
- package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
- package/src/core/model/providers/AnthropicProvider.ts +279 -0
- package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
- package/src/core/model/providers/CodingPlanProvider.ts +210 -0
- package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
- package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
- package/src/core/model/providers/OllamaManager.ts +201 -0
- package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaProvider.ts +73 -0
- package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
- package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAIProvider.ts +29 -0
- package/src/core/model/providers/index.d.ts.map +1 -0
- package/src/core/model/providers/index.ts +12 -0
- package/src/core/model/types.d.ts.map +1 -0
- package/src/core/model/types.ts +77 -0
- package/src/core/security/ApprovalManager.d.ts.map +1 -0
- package/src/core/security/ApprovalManager.ts +174 -0
- package/src/core/security/FileFilter.d.ts.map +1 -0
- package/src/core/security/FileFilter.ts +141 -0
- package/src/core/security/HookExecutor.d.ts.map +1 -0
- package/src/core/security/HookExecutor.ts +178 -0
- package/src/core/security/SandboxExecutor.ts +447 -0
- package/src/core/security/index.d.ts.map +1 -0
- package/src/core/security/index.ts +8 -0
- package/src/core/session/AgentLoop.d.ts.map +1 -0
- package/src/core/session/AgentLoop.ts +501 -0
- package/src/core/session/SessionManager.d.ts.map +1 -0
- package/src/core/session/SessionManager.test.ts +183 -0
- package/src/core/session/SessionManager.ts +460 -0
- package/src/core/session/index.d.ts.map +1 -0
- package/src/core/session/index.ts +3 -0
- package/src/core/telemetry/Telemetry.d.ts.map +1 -0
- package/src/core/telemetry/Telemetry.ts +90 -0
- package/src/core/telemetry/TelemetryService.ts +531 -0
- package/src/core/telemetry/index.d.ts.map +1 -0
- package/src/core/telemetry/index.ts +12 -0
- package/src/core/testing/AutoFixer.ts +385 -0
- package/src/core/testing/ErrorAnalyzer.ts +499 -0
- package/src/core/testing/TestRunner.ts +265 -0
- package/src/core/testing/agent-cli-tests.ts +538 -0
- package/src/core/testing/index.ts +11 -0
- package/src/core/tools/ToolRegistry.d.ts.map +1 -0
- package/src/core/tools/ToolRegistry.test.ts +206 -0
- package/src/core/tools/ToolRegistry.ts +260 -0
- package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/EditFileTool.ts +97 -0
- package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
- package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/MemoryTool.ts +102 -0
- package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/ReadFileTool.ts +58 -0
- package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchContentTool.ts +94 -0
- package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchFileTool.ts +61 -0
- package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
- package/src/core/tools/impl/ShellTool.ts +118 -0
- package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
- package/src/core/tools/impl/TaskTool.ts +207 -0
- package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
- package/src/core/tools/impl/TodoTool.ts +122 -0
- package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebFetchTool.ts +103 -0
- package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebSearchTool.ts +89 -0
- package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/WriteFileTool.ts +49 -0
- package/src/core/tools/impl/index.d.ts.map +1 -0
- package/src/core/tools/impl/index.ts +16 -0
- package/src/core/tools/index.d.ts.map +1 -0
- package/src/core/tools/index.ts +7 -0
- package/src/core/tools/schemas/execution.d.ts.map +1 -0
- package/src/core/tools/schemas/execution.ts +42 -0
- package/src/core/tools/schemas/file.d.ts.map +1 -0
- package/src/core/tools/schemas/file.ts +119 -0
- package/src/core/tools/schemas/index.d.ts.map +1 -0
- package/src/core/tools/schemas/index.ts +11 -0
- package/src/core/tools/schemas/memory.d.ts.map +1 -0
- package/src/core/tools/schemas/memory.ts +52 -0
- package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
- package/src/core/tools/schemas/orchestration.ts +44 -0
- package/src/core/tools/schemas/search.d.ts.map +1 -0
- package/src/core/tools/schemas/search.ts +112 -0
- package/src/core/tools/schemas/todo.d.ts.map +1 -0
- package/src/core/tools/schemas/todo.ts +32 -0
- package/src/core/tools/schemas/web.d.ts.map +1 -0
- package/src/core/tools/schemas/web.ts +86 -0
- package/src/core/types/config.d.ts.map +1 -0
- package/src/core/types/config.ts +200 -0
- package/src/core/types/errors.d.ts.map +1 -0
- package/src/core/types/errors.ts +204 -0
- package/src/core/types/index.d.ts.map +1 -0
- package/src/core/types/index.ts +8 -0
- package/src/core/types/session.d.ts.map +1 -0
- package/src/core/types/session.ts +216 -0
- package/src/core/types/tools.d.ts.map +1 -0
- package/src/core/types/tools.ts +157 -0
- package/src/core/utils/CheckpointManager.d.ts.map +1 -0
- package/src/core/utils/CheckpointManager.ts +327 -0
- package/src/core/utils/Logger.d.ts.map +1 -0
- package/src/core/utils/Logger.ts +98 -0
- package/src/core/utils/RetryManager.ts +471 -0
- package/src/core/utils/TokenCounter.d.ts.map +1 -0
- package/src/core/utils/TokenCounter.ts +414 -0
- package/src/core/utils/VectorMemoryStore.ts +440 -0
- package/src/core/utils/helpers.d.ts.map +1 -0
- package/src/core/utils/helpers.ts +89 -0
- package/src/core/utils/index.d.ts.map +1 -0
- 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();
|