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,298 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ProgressIndicator - Modern progress indicator for Nova CLI
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { EventEmitter } from 'node:events';
|
|
7
|
+
|
|
8
|
+
export interface ProgressOptions {
|
|
9
|
+
type?: 'spinner' | 'bar' | 'dots';
|
|
10
|
+
message?: string;
|
|
11
|
+
showPercentage?: boolean;
|
|
12
|
+
color?: 'blue' | 'green' | 'yellow' | 'red' | 'cyan';
|
|
13
|
+
width?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ProgressIndicator extends EventEmitter {
|
|
17
|
+
private isActive = false;
|
|
18
|
+
private currentMessage = '';
|
|
19
|
+
private currentType: 'spinner' | 'bar' | 'dots' = 'spinner';
|
|
20
|
+
private currentColor: 'blue' | 'green' | 'yellow' | 'red' | 'cyan' = 'blue';
|
|
21
|
+
private progress = 0;
|
|
22
|
+
private startTime = 0;
|
|
23
|
+
private timer: NodeJS.Timeout | null = null;
|
|
24
|
+
|
|
25
|
+
// Spinner frames
|
|
26
|
+
private spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
27
|
+
private currentFrame = 0;
|
|
28
|
+
|
|
29
|
+
// Bar characters
|
|
30
|
+
private barWidth = 20;
|
|
31
|
+
private filledChar = '█';
|
|
32
|
+
private emptyChar = '░';
|
|
33
|
+
|
|
34
|
+
constructor(private options: ProgressOptions = {}) {
|
|
35
|
+
super();
|
|
36
|
+
this.options = {
|
|
37
|
+
type: 'spinner',
|
|
38
|
+
showPercentage: true,
|
|
39
|
+
color: 'blue',
|
|
40
|
+
width: 40,
|
|
41
|
+
...options
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
start(message: string = 'Processing...'): void {
|
|
46
|
+
if (this.isActive) {
|
|
47
|
+
this.stop();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.isActive = true;
|
|
51
|
+
this.currentMessage = message;
|
|
52
|
+
this.startTime = Date.now();
|
|
53
|
+
|
|
54
|
+
this.render();
|
|
55
|
+
|
|
56
|
+
switch (this.options.type) {
|
|
57
|
+
case 'spinner':
|
|
58
|
+
this.startSpinner();
|
|
59
|
+
break;
|
|
60
|
+
case 'bar':
|
|
61
|
+
this.startBar();
|
|
62
|
+
break;
|
|
63
|
+
case 'dots':
|
|
64
|
+
this.startDots();
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
update(progress: number, message?: string): void {
|
|
70
|
+
if (!this.isActive) return;
|
|
71
|
+
|
|
72
|
+
this.progress = Math.max(0, Math.min(100, progress));
|
|
73
|
+
|
|
74
|
+
if (message) {
|
|
75
|
+
this.currentMessage = message;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.render();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
complete(message: string = 'Done!'): void {
|
|
82
|
+
this.update(100, message);
|
|
83
|
+
|
|
84
|
+
if (this.timer) {
|
|
85
|
+
clearTimeout(this.timer);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Clear the line and show completion
|
|
89
|
+
process.stdout.write('\r\x1b[K');
|
|
90
|
+
console.log(chalk.green(`✓ ${message}`));
|
|
91
|
+
|
|
92
|
+
this.isActive = false;
|
|
93
|
+
this.emit('complete', { progress: 100, duration: Date.now() - this.startTime });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fail(error: Error | string, message: string = 'Failed'): void {
|
|
97
|
+
const errorMessage = typeof error === 'string' ? error : error.message;
|
|
98
|
+
|
|
99
|
+
this.update(0, `${message}: ${errorMessage}`);
|
|
100
|
+
|
|
101
|
+
if (this.timer) {
|
|
102
|
+
clearTimeout(this.timer);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Show error in red
|
|
106
|
+
process.stdout.write('\r\x1b[K');
|
|
107
|
+
console.log(chalk.red(`✗ ${message}: ${errorMessage}`));
|
|
108
|
+
|
|
109
|
+
this.isActive = false;
|
|
110
|
+
this.emit('fail', { error: errorMessage, duration: Date.now() - this.startTime });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
stop(): void {
|
|
114
|
+
if (!this.isActive || !this.timer) return;
|
|
115
|
+
|
|
116
|
+
this.isActive = false;
|
|
117
|
+
this.clear();
|
|
118
|
+
|
|
119
|
+
if (this.timer) {
|
|
120
|
+
clearTimeout(this.timer);
|
|
121
|
+
this.timer = null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private startSpinner(): void {
|
|
126
|
+
this.timer = setInterval(() => {
|
|
127
|
+
if (!this.isActive) {
|
|
128
|
+
if (this.timer) {
|
|
129
|
+
clearInterval(this.timer);
|
|
130
|
+
this.timer = null;
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.currentFrame = (this.currentFrame + 1) % this.spinnerFrames.length;
|
|
136
|
+
this.render();
|
|
137
|
+
}, 80);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private startBar(): void {
|
|
141
|
+
this.timer = setInterval(() => {
|
|
142
|
+
if (!this.isActive) {
|
|
143
|
+
if (this.timer) {
|
|
144
|
+
clearInterval(this.timer);
|
|
145
|
+
this.timer = null;
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.render();
|
|
151
|
+
}, 100);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private startDots(): void {
|
|
155
|
+
let dotCount = 0;
|
|
156
|
+
this.timer = setInterval(() => {
|
|
157
|
+
if (!this.isActive) {
|
|
158
|
+
if (this.timer) {
|
|
159
|
+
clearInterval(this.timer);
|
|
160
|
+
this.timer = null;
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
dotCount = (dotCount + 1) % 4;
|
|
166
|
+
this.render();
|
|
167
|
+
}, 500);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private render(): void {
|
|
171
|
+
process.stdout.write('\r\x1b[K'); // Clear line
|
|
172
|
+
|
|
173
|
+
const parts: string[] = [];
|
|
174
|
+
|
|
175
|
+
// Type-specific rendering
|
|
176
|
+
switch (this.options.type) {
|
|
177
|
+
case 'spinner':
|
|
178
|
+
parts.push(this.renderSpinner());
|
|
179
|
+
break;
|
|
180
|
+
case 'bar':
|
|
181
|
+
parts.push(this.renderBar());
|
|
182
|
+
break;
|
|
183
|
+
case 'dots':
|
|
184
|
+
parts.push(this.renderDots());
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Add message if provided
|
|
189
|
+
if (this.currentMessage && this.options.type !== 'bar') {
|
|
190
|
+
parts.push(chalk.dim(this.currentMessage));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add percentage if enabled
|
|
194
|
+
if (this.options.showPercentage && this.options.type === 'bar') {
|
|
195
|
+
const percentage = Math.round(this.progress);
|
|
196
|
+
parts.push(chalk.gray(`${percentage}%`));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Add elapsed time
|
|
200
|
+
const elapsed = Date.now() - this.startTime;
|
|
201
|
+
const seconds = Math.floor(elapsed / 1000);
|
|
202
|
+
if (seconds > 0) {
|
|
203
|
+
parts.push(chalk.gray(`(${seconds}s)`));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
process.stdout.write(parts.join(' ') + '\n');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private renderSpinner(): string {
|
|
210
|
+
const frame = this.spinnerFrames[this.currentFrame];
|
|
211
|
+
const color = this.getColor();
|
|
212
|
+
|
|
213
|
+
return `${chalk[color](frame)} ${this.currentMessage || 'Processing...'}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private renderBar(): string {
|
|
217
|
+
const width = this.options.width || this.barWidth;
|
|
218
|
+
const percentage = Math.max(0, Math.min(100, this.progress));
|
|
219
|
+
const filledLength = Math.round((percentage / 100) * width);
|
|
220
|
+
const emptyLength = width - filledLength;
|
|
221
|
+
|
|
222
|
+
const filled = chalk[this.currentColor](this.filledChar.repeat(filledLength));
|
|
223
|
+
const empty = chalk.gray(this.emptyChar.repeat(emptyLength));
|
|
224
|
+
|
|
225
|
+
const bar = `[${filled}${empty}]`;
|
|
226
|
+
|
|
227
|
+
let result = bar;
|
|
228
|
+
if (this.currentMessage) {
|
|
229
|
+
result += ` ${this.currentMessage}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private renderDots(): string {
|
|
236
|
+
const dots = '.'.repeat((this.currentFrame + 1) % 4);
|
|
237
|
+
const color = this.getColor();
|
|
238
|
+
|
|
239
|
+
return chalk[color](`${dots} ${this.currentMessage || 'Working...'}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private getColor(): keyof typeof chalk {
|
|
243
|
+
switch (this.currentColor) {
|
|
244
|
+
case 'blue': return 'blue';
|
|
245
|
+
case 'green': return 'green';
|
|
246
|
+
case 'yellow': return 'yellow';
|
|
247
|
+
case 'red': return 'red';
|
|
248
|
+
case 'cyan': return 'cyan';
|
|
249
|
+
default: return 'blue';
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private clear(): void {
|
|
254
|
+
if (this.isActive) {
|
|
255
|
+
process.stdout.write('\r\x1b[K\n');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Static utility methods
|
|
260
|
+
static async withProgress<T>(
|
|
261
|
+
task: (progress: ProgressIndicator) => Promise<T>,
|
|
262
|
+
message: string = 'Processing...'
|
|
263
|
+
): Promise<T> {
|
|
264
|
+
const progress = new ProgressIndicator({ type: 'bar', message });
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
progress.start(message);
|
|
268
|
+
|
|
269
|
+
// Simulate some initial progress
|
|
270
|
+
progress.update(10, 'Initializing...');
|
|
271
|
+
|
|
272
|
+
const result = await task(progress);
|
|
273
|
+
|
|
274
|
+
progress.complete('Completed successfully!');
|
|
275
|
+
return result;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
progress.fail(error as Error, 'Operation failed');
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
static async withSpinner<T>(
|
|
283
|
+
task: () => Promise<T>,
|
|
284
|
+
message: string = 'Processing...'
|
|
285
|
+
): Promise<T> {
|
|
286
|
+
const progress = new ProgressIndicator({ type: 'spinner', message });
|
|
287
|
+
progress.start(message);
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const result = await task();
|
|
291
|
+
progress.complete('Done!');
|
|
292
|
+
return result;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
progress.fail(error as Error, 'Failed');
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// QuickActions - Modern quick action menu for Nova CLI REPL
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import type { SessionInfo } from '../../../../core/types/config.js';
|
|
7
|
+
|
|
8
|
+
export interface QuickAction {
|
|
9
|
+
key: string;
|
|
10
|
+
label: string;
|
|
11
|
+
description: string;
|
|
12
|
+
action: () => void | Promise<void>;
|
|
13
|
+
category?: 'navigation' | 'session' | 'model' | 'tools' | 'help';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class QuickActions {
|
|
17
|
+
private actions: Map<string, QuickAction> = new Map();
|
|
18
|
+
private session: SessionInfo | null = null;
|
|
19
|
+
|
|
20
|
+
constructor(session?: SessionInfo) {
|
|
21
|
+
this.session = session || null;
|
|
22
|
+
this.initializeDefaultActions();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private initializeDefaultActions(): void {
|
|
26
|
+
// Navigation actions
|
|
27
|
+
this.addAction({
|
|
28
|
+
key: '?',
|
|
29
|
+
label: 'Help',
|
|
30
|
+
description: 'Show command help',
|
|
31
|
+
category: 'help',
|
|
32
|
+
action: () => this.showHelp()
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
this.addAction({
|
|
36
|
+
key: 'h',
|
|
37
|
+
label: 'History',
|
|
38
|
+
description: 'Browse previous sessions',
|
|
39
|
+
category: 'navigation',
|
|
40
|
+
action: () => this.showSessionHistory()
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
this.addAction({
|
|
44
|
+
key: 'c',
|
|
45
|
+
label: 'Clear',
|
|
46
|
+
description: 'Start new conversation',
|
|
47
|
+
category: 'session',
|
|
48
|
+
action: () => this.clearConversation()
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Model actions
|
|
52
|
+
this.addAction({
|
|
53
|
+
key: 'm',
|
|
54
|
+
label: 'Model',
|
|
55
|
+
description: 'Switch model (interactive)',
|
|
56
|
+
category: 'model',
|
|
57
|
+
action: () => this.switchModel()
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.addAction({
|
|
61
|
+
key: 'M',
|
|
62
|
+
label: 'Models',
|
|
63
|
+
description: 'List available models',
|
|
64
|
+
category: 'model',
|
|
65
|
+
action: () => this.listModels()
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Mode actions
|
|
69
|
+
this.addAction({
|
|
70
|
+
key: '1',
|
|
71
|
+
label: 'AUTO',
|
|
72
|
+
description: 'Auto mode (no approval)',
|
|
73
|
+
category: 'session',
|
|
74
|
+
action: () => this.setMode('auto')
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.addAction({
|
|
78
|
+
key: '2',
|
|
79
|
+
label: 'PLAN',
|
|
80
|
+
description: 'Plan mode (ask before action)',
|
|
81
|
+
category: 'session',
|
|
82
|
+
action: () => this.setMode('plan')
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
this.addAction({
|
|
86
|
+
key: '3',
|
|
87
|
+
label: 'ASK',
|
|
88
|
+
description: 'Ask mode (read-only)',
|
|
89
|
+
category: 'session',
|
|
90
|
+
action: () => this.setMode('ask')
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Tools actions
|
|
94
|
+
this.addAction({
|
|
95
|
+
key: 't',
|
|
96
|
+
label: 'Tools',
|
|
97
|
+
description: 'Manage built-in tools',
|
|
98
|
+
category: 'tools',
|
|
99
|
+
action: () => this.manageTools()
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.addAction({
|
|
103
|
+
key: 's',
|
|
104
|
+
label: 'Skills',
|
|
105
|
+
description: 'Use or manage skills',
|
|
106
|
+
category: 'tools',
|
|
107
|
+
action: () => this.manageSkills()
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.addAction({
|
|
111
|
+
key: 'p',
|
|
112
|
+
label: 'Profile',
|
|
113
|
+
description: 'View session profile',
|
|
114
|
+
category: 'session',
|
|
115
|
+
action: () => this.showProfile()
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// MCP actions
|
|
119
|
+
this.addAction({
|
|
120
|
+
key: 'C',
|
|
121
|
+
label: 'MCP Status',
|
|
122
|
+
description: 'Check MCP server connections',
|
|
123
|
+
category: 'tools',
|
|
124
|
+
action: () => this.checkMcpStatus()
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Memory actions
|
|
128
|
+
this.addAction({
|
|
129
|
+
key: 'i',
|
|
130
|
+
label: 'Init',
|
|
131
|
+
description: 'Generate NOVA.md project file',
|
|
132
|
+
category: 'session',
|
|
133
|
+
action: () => this.initProject()
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
this.addAction({
|
|
137
|
+
key: 'r',
|
|
138
|
+
label: 'Compress',
|
|
139
|
+
description: 'Optimize context window',
|
|
140
|
+
category: 'session',
|
|
141
|
+
action: () => this.compressContext()
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
addAction(action: QuickAction): void {
|
|
146
|
+
this.actions.set(action.key, action);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
removeAction(key: string): boolean {
|
|
150
|
+
return this.actions.delete(key);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getActionsByCategory(category: QuickAction['category']): QuickAction[] {
|
|
154
|
+
return Array.from(this.actions.values())
|
|
155
|
+
.filter(action => action.category === category)
|
|
156
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
showMenu(): void {
|
|
160
|
+
console.log('\n');
|
|
161
|
+
|
|
162
|
+
// Header
|
|
163
|
+
const width = Math.min(process.stdout.columns || 80, 60);
|
|
164
|
+
const border = '─'.repeat(width);
|
|
165
|
+
|
|
166
|
+
console.log(chalk.bgBlue.white.bold(' QUICK ACTIONS '));
|
|
167
|
+
console.log(chalk.blue(border));
|
|
168
|
+
|
|
169
|
+
// Group actions by category
|
|
170
|
+
const categories: Record<string, QuickAction[]> = {};
|
|
171
|
+
|
|
172
|
+
for (const action of this.actions.values()) {
|
|
173
|
+
const category = action.category || 'other';
|
|
174
|
+
if (!categories[category]) {
|
|
175
|
+
categories[category] = [];
|
|
176
|
+
}
|
|
177
|
+
categories[category].push(action);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Display categories and actions
|
|
181
|
+
for (const [categoryName, actions] of Object.entries(categories)) {
|
|
182
|
+
const displayName = this.formatCategoryName(categoryName);
|
|
183
|
+
|
|
184
|
+
console.log(chalk.yellow(`\n${displayName}:`));
|
|
185
|
+
console.log(chalk.gray('─'.repeat(displayName.length + 1)));
|
|
186
|
+
|
|
187
|
+
for (const action of actions.sort((a, b) => a.label.localeCompare(b.label))) {
|
|
188
|
+
const keyDisplay = chalk.cyan(`[${action.key}]`);
|
|
189
|
+
const labelDisplay = chalk.white(action.label.padEnd(12));
|
|
190
|
+
const descDisplay = chalk.gray(action.description);
|
|
191
|
+
|
|
192
|
+
console.log(` ${keyDisplay} ${labelDisplay} ${descDisplay}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log('');
|
|
197
|
+
console.log(chalk.dim('Enter a key to execute, or press Enter to cancel...'));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async handleInput(input: string): Promise<boolean> {
|
|
201
|
+
const trimmed = input.trim().toLowerCase();
|
|
202
|
+
|
|
203
|
+
if (!trimmed) {
|
|
204
|
+
return false; // Cancel
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const action = this.actions.get(trimmed);
|
|
208
|
+
if (action) {
|
|
209
|
+
try {
|
|
210
|
+
await action.action();
|
|
211
|
+
return true;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(chalk.red(`Error executing action: ${(error as Error).message}`));
|
|
214
|
+
return true; // Consumed
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check if it's a number (mode selection)
|
|
219
|
+
const numValue = parseInt(trimmed, 10);
|
|
220
|
+
if (numValue >= 1 && numValue <= 3) {
|
|
221
|
+
const modes = ['auto', 'plan', 'ask'];
|
|
222
|
+
await this.setMode(modes[numValue - 1]);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(chalk.yellow(`Unknown action: "${input}". Press ? for help.`));
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private formatCategoryName(category: string): string {
|
|
231
|
+
const names = {
|
|
232
|
+
navigation: 'Navigation',
|
|
233
|
+
session: 'Session Management',
|
|
234
|
+
model: 'Model Control',
|
|
235
|
+
tools: 'Tools & Extensions',
|
|
236
|
+
help: 'Help & Info',
|
|
237
|
+
other: 'Other'
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return names[category] || category.charAt(0).toUpperCase() + category.slice(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Action implementations
|
|
244
|
+
private showHelp(): void {
|
|
245
|
+
console.log('\n' + chalk.bgWhite.black(' COMMAND HELP '));
|
|
246
|
+
console.log(chalk.white('-'.repeat(50)));
|
|
247
|
+
|
|
248
|
+
console.log(chalk.white('\nAvailable commands:'));
|
|
249
|
+
console.log(chalk.cyan(' /help, /h, /?') + ' Show detailed help');
|
|
250
|
+
console.log(chalk.cyan(' /quit, /exit, /q') + ' Exit Nova CLI');
|
|
251
|
+
console.log(chalk.cyan(' /clear, /reset') + ' Start new conversation');
|
|
252
|
+
|
|
253
|
+
console.log(chalk.white('\nSession commands:'));
|
|
254
|
+
console.log(chalk.cyan(' /status') + ' Show session info');
|
|
255
|
+
console.log(chalk.cyan(' /history') + ' List previous sessions');
|
|
256
|
+
console.log(chalk.cyan(' /compress') + ' Optimize context');
|
|
257
|
+
|
|
258
|
+
console.log(chalk.white('\nModel commands:'));
|
|
259
|
+
console.log(chalk.cyan(' /model') + ' Switch model (interactive)');
|
|
260
|
+
console.log(chalk.cyan(' /model <id>') + ' Switch to specific model');
|
|
261
|
+
|
|
262
|
+
console.log(chalk.white('\nQuick shortcuts:'));
|
|
263
|
+
console.log(chalk.cyan(' @file.ts') + ' Inject file content');
|
|
264
|
+
console.log(chalk.cyan(' !command') + ' Execute shell command');
|
|
265
|
+
console.log(chalk.cyan(' \\') + ' Multi-line input');
|
|
266
|
+
|
|
267
|
+
console.log('');
|
|
268
|
+
console.log(chalk.gray('Press any key to continue...'));
|
|
269
|
+
process.stdin.setRawMode(true);
|
|
270
|
+
process.stdin.resume();
|
|
271
|
+
process.stdin.once('data', () => {
|
|
272
|
+
process.stdin.setRawMode(false);
|
|
273
|
+
process.stdin.pause();
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private showSessionHistory(): void {
|
|
278
|
+
// This would integrate with SessionManager
|
|
279
|
+
console.log(chalk.yellow('Opening session history...'));
|
|
280
|
+
console.log(chalk.gray('Use /history in REPL for full functionality'));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private clearConversation(): void {
|
|
284
|
+
console.log(chalk.yellow('Clearing conversation...'));
|
|
285
|
+
console.log(chalk.gray('Starting fresh session'));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private switchModel(): void {
|
|
289
|
+
console.log(chalk.yellow('Opening model selector...'));
|
|
290
|
+
console.log(chalk.gray('Use /model in REPL for interactive selection'));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private listModels(): void {
|
|
294
|
+
console.log(chalk.yellow('Listing available models...'));
|
|
295
|
+
console.log(chalk.gray('Run nova model list in terminal'));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private setMode(mode: 'auto' | 'plan' | 'ask'): void {
|
|
299
|
+
console.log(chalk.green(`Switched to ${mode.toUpperCase()} mode`));
|
|
300
|
+
console.log(chalk.gray('Use /mode to change interaction mode'));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private manageTools(): void {
|
|
304
|
+
console.log(chalk.yellow('Managing built-in tools...'));
|
|
305
|
+
console.log(chalk.gray('Use /tools in REPL to see available tools'));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private manageSkills(): void {
|
|
309
|
+
console.log(chalk.yellow('Managing skills...'));
|
|
310
|
+
console.log(chalk.gray('Use /skills in REPL to see available skills'));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private showProfile(): void {
|
|
314
|
+
if (!this.session) {
|
|
315
|
+
console.log(chalk.yellow('No active session'));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
console.log(chalk.bgGreen.black(' SESSION PROFILE '));
|
|
320
|
+
console.log(chalk.green('-'.repeat(50)));
|
|
321
|
+
|
|
322
|
+
const session = this.session;
|
|
323
|
+
console.log(chalk.white('Session ID:').padEnd(15) + chalk.gray(session.id.slice(0, 8)));
|
|
324
|
+
console.log(chalk.white('Model:').padEnd(15) + chalk.cyan(session.model));
|
|
325
|
+
console.log(chalk.white('Mode:').padEnd(15) + chalk.yellow(session.mode.toUpperCase()));
|
|
326
|
+
console.log(chalk.white('Turns:').padEnd(15) + chalk.blue((session.turnCount || 0).toString()));
|
|
327
|
+
|
|
328
|
+
const tokens = (session.totalInputTokens || 0) + (session.totalOutputTokens || 0);
|
|
329
|
+
console.log(chalk.white('Tokens:').padEnd(15) + chalk.magenta(tokens.toLocaleString()));
|
|
330
|
+
|
|
331
|
+
const duration = this.formatDuration(Date.now() - (session.createdAt || Date.now()));
|
|
332
|
+
console.log(chalk.white('Duration:').padEnd(15) + chalk.gray(duration));
|
|
333
|
+
|
|
334
|
+
console.log('');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private checkMcpStatus(): void {
|
|
338
|
+
console.log(chalk.yellow('Checking MCP server status...'));
|
|
339
|
+
console.log(chalk.gray('Use /mcp in REPL for detailed status'));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private initProject(): void {
|
|
343
|
+
console.log(chalk.yellow('Initializing project...'));
|
|
344
|
+
console.log(chalk.gray('Use /init in REPL to generate NOVA.md'));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private compressContext(): void {
|
|
348
|
+
console.log(chalk.yellow('Compressing context...'));
|
|
349
|
+
console.log(chalk.gray('Use /compress in REPL to optimize'));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private formatDuration(ms: number): string {
|
|
353
|
+
const seconds = Math.floor(ms / 1000);
|
|
354
|
+
const minutes = Math.floor(seconds / 60);
|
|
355
|
+
const hours = Math.floor(minutes / 60);
|
|
356
|
+
|
|
357
|
+
if (hours > 0) {
|
|
358
|
+
return `${hours}h ${minutes % 60}m`;
|
|
359
|
+
} else if (minutes > 0) {
|
|
360
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
361
|
+
} else {
|
|
362
|
+
return `${seconds}s`;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Static utility methods
|
|
367
|
+
static createFromSession(session: SessionInfo): QuickActions {
|
|
368
|
+
return new QuickActions(session);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
static getDefaultActions(): QuickAction[] {
|
|
372
|
+
return [
|
|
373
|
+
{
|
|
374
|
+
key: 'Ctrl+R',
|
|
375
|
+
label: 'Recent Sessions',
|
|
376
|
+
description: 'Show recent sessions',
|
|
377
|
+
category: 'navigation',
|
|
378
|
+
action: () => console.log('Recent sessions')
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
key: 'Ctrl+T',
|
|
382
|
+
label: 'Token Usage',
|
|
383
|
+
description: 'Show token statistics',
|
|
384
|
+
category: 'session',
|
|
385
|
+
action: () => console.log('Token usage')
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
key: 'Ctrl+M',
|
|
389
|
+
label: 'Change Mode',
|
|
390
|
+
description: 'Cycle through modes',
|
|
391
|
+
category: 'session',
|
|
392
|
+
action: () => console.log('Change mode')
|
|
393
|
+
}
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
}
|