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,425 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// IFlowDropdown - True iFlow CLI style dropdown interface for Nova CLI
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import type { SessionInfo } from '../core/types/config.js';
|
|
7
|
+
|
|
8
|
+
export interface DropdownItem {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
description: string;
|
|
12
|
+
category: 'navigation' | 'session' | 'model' | 'tools' | 'help' | 'mcp' | 'skills';
|
|
13
|
+
icon?: string;
|
|
14
|
+
shortcut?: string;
|
|
15
|
+
action?: () => void | Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DropdownConfig {
|
|
19
|
+
maxHeight?: number; // Maximum dropdown height
|
|
20
|
+
animationDuration?: number; // Animation speed in ms
|
|
21
|
+
showIcons?: boolean; // Display icons
|
|
22
|
+
enableSearch?: boolean; // Enable real-time search
|
|
23
|
+
theme?: 'light' | 'dark'; // Theme selection
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class IFlowDropdown {
|
|
27
|
+
private items: DropdownItem[] = [];
|
|
28
|
+
private filteredItems: DropdownItem[] = [];
|
|
29
|
+
private selectedIndex: number = 0;
|
|
30
|
+
private isVisible: boolean = false;
|
|
31
|
+
private config: DropdownConfig;
|
|
32
|
+
|
|
33
|
+
constructor(config: DropdownConfig = {}) {
|
|
34
|
+
this.config = {
|
|
35
|
+
maxHeight: 15,
|
|
36
|
+
animationDuration: 200,
|
|
37
|
+
showIcons: true,
|
|
38
|
+
enableSearch: true,
|
|
39
|
+
theme: 'dark',
|
|
40
|
+
...config
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.initializeItems();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Show dropdown with suggestions based on input
|
|
48
|
+
*/
|
|
49
|
+
async show(input: string, session: SessionInfo | null): Promise<DropdownItem | null> {
|
|
50
|
+
if (!input.startsWith('/')) return null;
|
|
51
|
+
|
|
52
|
+
// Filter items based on input and context
|
|
53
|
+
this.filterItems(input, session);
|
|
54
|
+
|
|
55
|
+
if (this.filteredItems.length === 0) return null;
|
|
56
|
+
|
|
57
|
+
this.isVisible = true;
|
|
58
|
+
this.selectedIndex = 0;
|
|
59
|
+
|
|
60
|
+
await this.render(input);
|
|
61
|
+
return await this.handleInput(input);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Initialize all dropdown items
|
|
66
|
+
*/
|
|
67
|
+
private initializeItems(): void {
|
|
68
|
+
this.items = [
|
|
69
|
+
// Navigation commands
|
|
70
|
+
{
|
|
71
|
+
id: 'help',
|
|
72
|
+
label: 'Help',
|
|
73
|
+
description: 'Show detailed help information',
|
|
74
|
+
category: 'navigation',
|
|
75
|
+
icon: '?',
|
|
76
|
+
shortcut: '/h'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'quit',
|
|
80
|
+
label: 'Quit',
|
|
81
|
+
description: 'Exit Nova CLI (session auto-saved)',
|
|
82
|
+
category: 'navigation',
|
|
83
|
+
icon: '✗',
|
|
84
|
+
shortcut: '/exit'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'clear',
|
|
88
|
+
label: 'Clear',
|
|
89
|
+
description: 'Clear conversation and start new session',
|
|
90
|
+
category: 'navigation',
|
|
91
|
+
icon: '🗑️',
|
|
92
|
+
shortcut: '/reset'
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// Session management
|
|
96
|
+
{
|
|
97
|
+
id: 'status',
|
|
98
|
+
label: 'Status',
|
|
99
|
+
description: 'Show current session statistics and info',
|
|
100
|
+
category: 'session',
|
|
101
|
+
icon: '📊'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'history',
|
|
105
|
+
label: 'History',
|
|
106
|
+
description: 'Browse and manage previous sessions',
|
|
107
|
+
category: 'session',
|
|
108
|
+
icon: '📚'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'compress',
|
|
112
|
+
label: 'Compress',
|
|
113
|
+
description: 'Optimize context window size',
|
|
114
|
+
category: 'session',
|
|
115
|
+
icon: '⚡'
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// Model commands
|
|
119
|
+
{
|
|
120
|
+
id: 'model',
|
|
121
|
+
label: 'Model',
|
|
122
|
+
description: 'Switch or list available models',
|
|
123
|
+
category: 'model',
|
|
124
|
+
icon: '🤖'
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'models',
|
|
128
|
+
label: 'Models',
|
|
129
|
+
description: 'List all available models',
|
|
130
|
+
category: 'model',
|
|
131
|
+
icon: '🤖'
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// Mode commands
|
|
135
|
+
{
|
|
136
|
+
id: 'mode',
|
|
137
|
+
label: 'Mode',
|
|
138
|
+
description: 'Change interaction mode (AUTO/PLAN/ASK)',
|
|
139
|
+
category: 'session',
|
|
140
|
+
icon: '🔄'
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'auto',
|
|
144
|
+
label: 'Auto',
|
|
145
|
+
description: 'Switch to AUTO mode (no approval needed)',
|
|
146
|
+
category: 'session',
|
|
147
|
+
icon: '🚀'
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 'plan',
|
|
151
|
+
label: 'Plan',
|
|
152
|
+
description: 'Switch to PLAN mode (confirm before action)',
|
|
153
|
+
category: 'session',
|
|
154
|
+
icon: '📋'
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'ask',
|
|
158
|
+
label: 'Ask',
|
|
159
|
+
description: 'Switch to ASK mode (read-only questions)',
|
|
160
|
+
category: 'session',
|
|
161
|
+
icon: '❓'
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// Tool commands
|
|
165
|
+
{
|
|
166
|
+
id: 'tools',
|
|
167
|
+
label: 'Tools',
|
|
168
|
+
description: 'Manage built-in tools and capabilities',
|
|
169
|
+
category: 'tools',
|
|
170
|
+
icon: '🛠️'
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'skills',
|
|
174
|
+
label: 'Skills',
|
|
175
|
+
description: 'Use or manage AI skills',
|
|
176
|
+
category: 'skills',
|
|
177
|
+
icon: '🧩'
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'init',
|
|
181
|
+
label: 'Init',
|
|
182
|
+
description: 'Generate NOVA.md project memory file',
|
|
183
|
+
category: 'tools',
|
|
184
|
+
icon: '📝'
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// MCP commands
|
|
188
|
+
{
|
|
189
|
+
id: 'mcp',
|
|
190
|
+
label: 'MCP',
|
|
191
|
+
description: 'Manage MCP server connections',
|
|
192
|
+
category: 'mcp',
|
|
193
|
+
icon: '🌐'
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'mcp-status',
|
|
197
|
+
label: 'MCP Status',
|
|
198
|
+
description: 'Check MCP server connection status',
|
|
199
|
+
category: 'mcp',
|
|
200
|
+
icon: '🌐'
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// Memory commands
|
|
204
|
+
{
|
|
205
|
+
id: 'memory',
|
|
206
|
+
label: 'Memory',
|
|
207
|
+
description: 'Manage persistent notes and memory',
|
|
208
|
+
category: 'session',
|
|
209
|
+
icon: '💾'
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: 'profile',
|
|
213
|
+
label: 'Profile',
|
|
214
|
+
description: 'Show detailed session profile',
|
|
215
|
+
category: 'session',
|
|
216
|
+
icon: '👤'
|
|
217
|
+
}
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Filter items based on input and session context
|
|
223
|
+
*/
|
|
224
|
+
private filterItems(input: string, session: SessionInfo | null): void {
|
|
225
|
+
const baseInput = input.slice(1).toLowerCase(); // Remove / and lowercase
|
|
226
|
+
|
|
227
|
+
this.filteredItems = this.items.filter(item => {
|
|
228
|
+
// Basic text matching
|
|
229
|
+
const matchesText = item.label.toLowerCase().includes(baseInput) ||
|
|
230
|
+
item.description.toLowerCase().includes(baseInput) ||
|
|
231
|
+
item.id.toLowerCase().startsWith(baseInput);
|
|
232
|
+
|
|
233
|
+
if (!matchesText) return false;
|
|
234
|
+
|
|
235
|
+
// Context-based filtering
|
|
236
|
+
if (session?.mode === 'ask') {
|
|
237
|
+
// In ask mode, hide editing commands
|
|
238
|
+
if (['clear', 'memory-add'].includes(item.id)) return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Mode-specific suggestions based on recent errors would go here
|
|
242
|
+
// For now, return basic filtered results
|
|
243
|
+
|
|
244
|
+
return true;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Sort by relevance
|
|
248
|
+
this.sortItems();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Sort items by relevance
|
|
253
|
+
*/
|
|
254
|
+
private sortItems(): void {
|
|
255
|
+
this.filteredItems.sort((a, b) => {
|
|
256
|
+
// Exact match first
|
|
257
|
+
if (a.id === b.id) return 0;
|
|
258
|
+
if (a.id.toLowerCase() === this.currentInput.toLowerCase()) return -1;
|
|
259
|
+
if (b.id.toLowerCase() === this.currentInput.toLowerCase()) return 1;
|
|
260
|
+
|
|
261
|
+
// Then by startsWith
|
|
262
|
+
const aStarts = a.id.toLowerCase().startsWith(this.currentInput.toLowerCase());
|
|
263
|
+
const bStarts = b.id.toLowerCase().startsWith(this.currentInput.toLowerCase());
|
|
264
|
+
|
|
265
|
+
if (aStarts && !bStarts) return -1;
|
|
266
|
+
if (!aStarts && bStarts) return 1;
|
|
267
|
+
|
|
268
|
+
// Finally alphabetically
|
|
269
|
+
return a.label.localeCompare(b.label);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Render the dropdown UI
|
|
275
|
+
*/
|
|
276
|
+
private async render(input: string): Promise<void> {
|
|
277
|
+
console.clear();
|
|
278
|
+
|
|
279
|
+
// Header
|
|
280
|
+
const width = Math.min(process.stdout.columns || 80, 70);
|
|
281
|
+
const border = '━'.repeat(width);
|
|
282
|
+
const title = ' NOVA CLI COMMAND SUGGESTIONS ';
|
|
283
|
+
const header = `╭${border}╮\n│${title.padEnd(width)}│\n├${'─'.repeat(width)}┤`;
|
|
284
|
+
|
|
285
|
+
console.log(chalk.bgBlue.black(header));
|
|
286
|
+
console.log(chalk.blue(`│ Use ↑↓ to navigate, Enter to select, Esc to cancel${' '.repeat(width - 64)}│`));
|
|
287
|
+
console.log(chalk.blue(`│ Input: ${chalk.cyan(input)}${' '.repeat(width - 18 - input.length)}│`));
|
|
288
|
+
console.log(chalk.blue(`├${'─'.repeat(width)}┤`));
|
|
289
|
+
|
|
290
|
+
// Items
|
|
291
|
+
const displayCount = Math.min(
|
|
292
|
+
this.filteredItems.length,
|
|
293
|
+
this.config.maxHeight || 15
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
for (let i = 0; i < displayCount; i++) {
|
|
297
|
+
const item = this.filteredItems[i];
|
|
298
|
+
const isSelected = i === this.selectedIndex;
|
|
299
|
+
|
|
300
|
+
// Selection indicator
|
|
301
|
+
const prefix = isSelected ? chalk.green('▶ ') : chalk.gray(' ');
|
|
302
|
+
const indent = isSelected ? ' ' : '·';
|
|
303
|
+
|
|
304
|
+
// Icon
|
|
305
|
+
let iconDisplay = '';
|
|
306
|
+
if (this.config.showIcons && item.icon) {
|
|
307
|
+
iconDisplay = isSelected ? chalk.white(item.icon) : chalk.gray(item.icon);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Label and description
|
|
311
|
+
const labelDisplay = isSelected
|
|
312
|
+
? chalk.cyan.bold(item.label)
|
|
313
|
+
: chalk.white(item.label);
|
|
314
|
+
|
|
315
|
+
const descDisplay = isSelected
|
|
316
|
+
? chalk.yellow(item.description)
|
|
317
|
+
: chalk.gray(item.description);
|
|
318
|
+
|
|
319
|
+
// Shortcut
|
|
320
|
+
let shortcutDisplay = '';
|
|
321
|
+
if (item.shortcut) {
|
|
322
|
+
shortcutDisplay = isSelected
|
|
323
|
+
? chalk.magenta(` [${item.shortcut}]`)
|
|
324
|
+
: chalk.gray(` (${item.shortcut})`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const line = `${prefix}${indent}${iconDisplay} ${labelDisplay} ${descDisplay}${shortcutDisplay}`;
|
|
328
|
+
console.log(chalk.blue(`│${line.padEnd(width)}│`));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Footer
|
|
332
|
+
const footer = `╰${'━'.repeat(width)}╯`;
|
|
333
|
+
console.log(chalk.blue(footer));
|
|
334
|
+
console.log('');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Handle user input for dropdown navigation
|
|
339
|
+
*/
|
|
340
|
+
private async handleInput(input: string): Promise<DropdownItem | null> {
|
|
341
|
+
return new Promise((resolve) => {
|
|
342
|
+
const readline = require('node:readline');
|
|
343
|
+
const rl = readline.createInterface({
|
|
344
|
+
input: process.stdin,
|
|
345
|
+
output: process.stdout
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Set raw mode for direct key capture
|
|
349
|
+
const wasRaw = process.stdin.isRaw;
|
|
350
|
+
if (process.stdin.isTTY) {
|
|
351
|
+
process.stdin.setRawMode(true);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const handleKeyPress = (key: string) => {
|
|
355
|
+
switch (key) {
|
|
356
|
+
case '\x1b[A': // Up arrow
|
|
357
|
+
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
358
|
+
this.render(input);
|
|
359
|
+
break;
|
|
360
|
+
|
|
361
|
+
case '\x1b[B': // Down arrow
|
|
362
|
+
this.selectedIndex = Math.min(
|
|
363
|
+
this.filteredItems.length - 1,
|
|
364
|
+
this.selectedIndex + 1
|
|
365
|
+
);
|
|
366
|
+
this.render(input);
|
|
367
|
+
break;
|
|
368
|
+
|
|
369
|
+
case '\r': // Enter
|
|
370
|
+
case '\n':
|
|
371
|
+
rl.close();
|
|
372
|
+
if (process.stdin.isTTY) {
|
|
373
|
+
process.stdin.setRawMode(wasRaw);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const selected = this.filteredItems[this.selectedIndex];
|
|
377
|
+
if (selected) {
|
|
378
|
+
console.log(chalk.green(`✓ Executing: ${selected.label}`));
|
|
379
|
+
setTimeout(() => {
|
|
380
|
+
if (selected.action) {
|
|
381
|
+
selected.action();
|
|
382
|
+
}
|
|
383
|
+
resolve(selected);
|
|
384
|
+
}, 500);
|
|
385
|
+
} else {
|
|
386
|
+
resolve(null);
|
|
387
|
+
}
|
|
388
|
+
break;
|
|
389
|
+
|
|
390
|
+
case '\x1b': // Escape
|
|
391
|
+
rl.close();
|
|
392
|
+
if (process.stdin.isTTY) {
|
|
393
|
+
process.stdin.setRawMode(wasRaw);
|
|
394
|
+
}
|
|
395
|
+
console.log(chalk.gray('\nCancelled.'));
|
|
396
|
+
resolve(null);
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
process.stdin.on('data', handleKeyPress);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Hide dropdown
|
|
407
|
+
*/
|
|
408
|
+
hide(): void {
|
|
409
|
+
this.isVisible = false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get all available items (for testing)
|
|
414
|
+
*/
|
|
415
|
+
getAllItems(): DropdownItem[] {
|
|
416
|
+
return [...this.items];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get items by category
|
|
421
|
+
*/
|
|
422
|
+
getItemsByCategory(category: DropdownItem['category']): DropdownItem[] {
|
|
423
|
+
return this.items.filter(item => item.category === category);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ModernReplUI - Integrated modern UI for Nova CLI REPL
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import type { SessionInfo, NovaConfig } from '../core/types/config.js';
|
|
7
|
+
|
|
8
|
+
import { StatusBar } from './components/StatusBar.js';
|
|
9
|
+
import { ProgressIndicator } from './components/ProgressIndicator.js';
|
|
10
|
+
import { ErrorPanel } from './components/ErrorPanel.js';
|
|
11
|
+
import { QuickActions } from './components/QuickActions.js';
|
|
12
|
+
|
|
13
|
+
export interface ModernReplOptions {
|
|
14
|
+
showStatusBar?: boolean;
|
|
15
|
+
showInputBox?: boolean;
|
|
16
|
+
enableQuickActions?: boolean;
|
|
17
|
+
compactMode?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ModernReplUI {
|
|
21
|
+
private session: SessionInfo | null = null;
|
|
22
|
+
private config: NovaConfig | null = null;
|
|
23
|
+
private options: ModernReplOptions = {};
|
|
24
|
+
|
|
25
|
+
// UI Components
|
|
26
|
+
private statusBar: StatusBar;
|
|
27
|
+
private progressIndicator: ProgressIndicator;
|
|
28
|
+
private errorPanel: ErrorPanel;
|
|
29
|
+
private quickActions: QuickActions;
|
|
30
|
+
|
|
31
|
+
constructor(options: ModernReplOptions = {}) {
|
|
32
|
+
this.options = {
|
|
33
|
+
showStatusBar: true,
|
|
34
|
+
showInputBox: true,
|
|
35
|
+
enableQuickActions: true,
|
|
36
|
+
compactMode: false,
|
|
37
|
+
...options
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Initialize components
|
|
41
|
+
this.statusBar = new StatusBar({ compact: this.options.compactMode });
|
|
42
|
+
this.progressIndicator = new ProgressIndicator();
|
|
43
|
+
this.errorPanel = new ErrorPanel({ compact: this.options.compactMode });
|
|
44
|
+
this.quickActions = new QuickActions(this.session);
|
|
45
|
+
|
|
46
|
+
console.log(chalk.bgGreen.black.bold(' NOVA CLI v0.1.0 '));
|
|
47
|
+
console.log(chalk.green('-'.repeat(50)));
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async start(): Promise<void> {
|
|
52
|
+
// Clear screen and setup
|
|
53
|
+
console.clear();
|
|
54
|
+
|
|
55
|
+
if (this.options.showStatusBar) {
|
|
56
|
+
this.renderStatusBar();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Show welcome message
|
|
60
|
+
this.showWelcomeMessage();
|
|
61
|
+
|
|
62
|
+
// Main input loop would be handled by parent component
|
|
63
|
+
console.log(chalk.dim('Type /help for commands, or press Ctrl+C to exit...'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
updateSession(session: SessionInfo): void {
|
|
67
|
+
this.session = session;
|
|
68
|
+
this.quickActions = new QuickActions(session);
|
|
69
|
+
|
|
70
|
+
if (this.options.showStatusBar) {
|
|
71
|
+
this.statusBar.update(session, this.config!);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updateConfig(config: NovaConfig): void {
|
|
76
|
+
this.config = config;
|
|
77
|
+
|
|
78
|
+
if (this.options.showStatusBar) {
|
|
79
|
+
this.statusBar.update(this.session!, config);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Progress management
|
|
84
|
+
showProgress(message: string = 'Processing...', type: 'spinner' | 'bar' | 'dots' = 'spinner'): void {
|
|
85
|
+
this.progressIndicator.start(message);
|
|
86
|
+
this.progressIndicator.options.type = type;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
updateProgress(progress: number, message?: string): void {
|
|
90
|
+
this.progressIndicator.update(progress, message);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
completeProgress(message: string = 'Done!'): void {
|
|
94
|
+
this.progressIndicator.complete(message);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
failProgress(error: Error | string, message: string = 'Failed'): void {
|
|
98
|
+
this.progressIndicator.fail(error, message);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Error handling
|
|
102
|
+
handleError(error: Error | string, context?: any): void {
|
|
103
|
+
this.errorPanel.display(error, context);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Quick actions
|
|
107
|
+
showQuickMenu(): Promise<string> {
|
|
108
|
+
if (!this.options.enableQuickActions) {
|
|
109
|
+
return Promise.resolve('');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.quickActions.showMenu();
|
|
113
|
+
return this.quickActions.handleInput('');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Input box rendering
|
|
117
|
+
renderInputBox(prompt: string = 'NOVA > '): void {
|
|
118
|
+
if (!this.options.showInputBox) return;
|
|
119
|
+
|
|
120
|
+
const width = Math.min(process.stdout.columns || 80, 100);
|
|
121
|
+
const border = '─'.repeat(width);
|
|
122
|
+
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log(chalk.blue('┌' + border + '┐'));
|
|
125
|
+
console.log(chalk.blue('│') + ' '.repeat(width) + chalk.blue('│'));
|
|
126
|
+
console.log(chalk.blue('│') + chalk.white(` ${prompt}`).padEnd(width - 2) + chalk.blue('│'));
|
|
127
|
+
console.log(chalk.blue('│') + ' '.repeat(width) + chalk.blue('│'));
|
|
128
|
+
console.log(chalk.blue('└' + border + '┘'));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
clearInputBox(): void {
|
|
132
|
+
if (!this.options.showInputBox) return;
|
|
133
|
+
|
|
134
|
+
const width = Math.min(process.stdout.columns || 80, 100);
|
|
135
|
+
console.log(chalk.blue('┌' + ' '.repeat(width) + '┐'));
|
|
136
|
+
console.log(chalk.blue('│') + ' '.repeat(width) + chalk.blue('│'));
|
|
137
|
+
console.log(chalk.blue('│') + ' '.repeat(width) + chalk.blue('│'));
|
|
138
|
+
console.log(chalk.blue('│') + ' '.repeat(width) + chalk.blue('│'));
|
|
139
|
+
console.log(chalk.blue('└' + ' '.repeat(width) + '┘'));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Utility methods
|
|
143
|
+
private renderStatusBar(): void {
|
|
144
|
+
if (this.session && this.config) {
|
|
145
|
+
console.log(this.statusBar.render());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private showWelcomeMessage(): void {
|
|
150
|
+
const messages = [
|
|
151
|
+
chalk.cyan('🚀 Welcome to Nova CLI - Your AI-Powered Terminal Assistant'),
|
|
152
|
+
'',
|
|
153
|
+
chalk.yellow('✨ Features:'),
|
|
154
|
+
' • Multiple AI model providers (OpenAI, Anthropic, Ollama, etc.)',
|
|
155
|
+
' • Smart file operations with @file references',
|
|
156
|
+
' • Built-in tools for code analysis and generation',
|
|
157
|
+
' • MCP server integration for extended functionality',
|
|
158
|
+
' • Session persistence and history management',
|
|
159
|
+
'',
|
|
160
|
+
chalk.blue('📖 Quick Start:'),
|
|
161
|
+
' • Type your request and press Enter',
|
|
162
|
+
' • Use @filename to reference files',
|
|
163
|
+
' • Use !command to execute shell commands',
|
|
164
|
+
' • Press /help for command reference',
|
|
165
|
+
'',
|
|
166
|
+
chalk.gray('Press Ctrl+C at any time to cancel current operation')
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
messages.forEach(msg => console.log(msg));
|
|
170
|
+
console.log('');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Static utility methods
|
|
174
|
+
static async withModernUI<T>(
|
|
175
|
+
task: (ui: ModernReplUI) => Promise<T>,
|
|
176
|
+
options?: ModernReplOptions
|
|
177
|
+
): Promise<T> {
|
|
178
|
+
const ui = new ModernReplUI(options);
|
|
179
|
+
await ui.start();
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const result = await task(ui);
|
|
183
|
+
return result;
|
|
184
|
+
} finally {
|
|
185
|
+
// Cleanup
|
|
186
|
+
console.clear();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
static async createFromExisting(
|
|
191
|
+
originalRepl: any,
|
|
192
|
+
options?: ModernReplOptions
|
|
193
|
+
): Promise<ModernReplUI> {
|
|
194
|
+
const ui = new ModernReplUI(options);
|
|
195
|
+
|
|
196
|
+
// Hook into existing REPL methods
|
|
197
|
+
if (originalRepl.printBanner) {
|
|
198
|
+
const originalPrintBanner = originalRepl.printBanner.bind(originalRepl);
|
|
199
|
+
originalRepl.printBanner = async function() {
|
|
200
|
+
await originalPrintBanner();
|
|
201
|
+
console.log('\n' + ui.statusBar.render());
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return ui;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Configuration helpers
|
|
209
|
+
setOption<K extends keyof ModernReplOptions>(key: K, value: ModernReplOptions[K]): void {
|
|
210
|
+
this.options[key] = value;
|
|
211
|
+
|
|
212
|
+
switch (key) {
|
|
213
|
+
case 'showStatusBar':
|
|
214
|
+
if (value && this.session && this.config) {
|
|
215
|
+
this.statusBar.update(this.session, this.config);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case 'compactMode':
|
|
219
|
+
this.statusBar.options.compact = value as boolean;
|
|
220
|
+
if (this.session && this.config) {
|
|
221
|
+
this.statusBar.update(this.session, this.config);
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
case 'enableQuickActions':
|
|
225
|
+
// Actions can be toggled dynamically
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
getStatus(): ReplUIStatus {
|
|
231
|
+
return {
|
|
232
|
+
hasSession: !!this.session,
|
|
233
|
+
hasConfig: !!this.config,
|
|
234
|
+
sessionStats: this.session ? {
|
|
235
|
+
id: this.session.id,
|
|
236
|
+
model: this.session.model,
|
|
237
|
+
turnCount: this.session.turnCount || 0,
|
|
238
|
+
tokenUsage: (this.session.totalInputTokens || 0) + (this.session.totalOutputTokens || 0),
|
|
239
|
+
duration: Date.now() - (this.session.createdAt || Date.now())
|
|
240
|
+
} : null,
|
|
241
|
+
uiOptions: { ...this.options },
|
|
242
|
+
components: {
|
|
243
|
+
statusBar: !!this.statusBar,
|
|
244
|
+
progressIndicator: !!this.progressIndicator,
|
|
245
|
+
errorPanel: !!this.errorPanel,
|
|
246
|
+
quickActions: !!this.quickActions
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Cleanup
|
|
252
|
+
dispose(): void {
|
|
253
|
+
this.progressIndicator.stop();
|
|
254
|
+
console.clear();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Type definitions
|
|
259
|
+
interface ReplUIStatus {
|
|
260
|
+
hasSession: boolean;
|
|
261
|
+
hasConfig: boolean;
|
|
262
|
+
sessionStats: {
|
|
263
|
+
id: string;
|
|
264
|
+
model: string;
|
|
265
|
+
turnCount: number;
|
|
266
|
+
tokenUsage: number;
|
|
267
|
+
duration: number;
|
|
268
|
+
} | null;
|
|
269
|
+
uiOptions: ModernReplOptions;
|
|
270
|
+
components: {
|
|
271
|
+
statusBar: boolean;
|
|
272
|
+
progressIndicator: boolean;
|
|
273
|
+
errorPanel: boolean;
|
|
274
|
+
quickActions: boolean;
|
|
275
|
+
};
|
|
276
|
+
}
|