nova-terminal-assistant 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (192) hide show
  1. package/README.md +358 -0
  2. package/bin/nova +38 -0
  3. package/bin/nova.js +12 -0
  4. package/package.json +67 -0
  5. package/src/cli/commands/SmartCompletion.ts +458 -0
  6. package/src/cli/index.ts +5 -0
  7. package/src/cli/startup/IFlowRepl.ts +212 -0
  8. package/src/cli/startup/InkBasedRepl.ts +1056 -0
  9. package/src/cli/startup/InteractiveRepl.ts +2833 -0
  10. package/src/cli/startup/NovaApp.ts +1861 -0
  11. package/src/cli/startup/index.ts +4 -0
  12. package/src/cli/startup/parseArgs.ts +293 -0
  13. package/src/cli/test-modules.ts +27 -0
  14. package/src/cli/ui/IFlowDropdown.ts +425 -0
  15. package/src/cli/ui/ModernReplUI.ts +276 -0
  16. package/src/cli/ui/SimpleSelector2.ts +215 -0
  17. package/src/cli/ui/components/ConfirmDialog.ts +176 -0
  18. package/src/cli/ui/components/ErrorPanel.ts +364 -0
  19. package/src/cli/ui/components/InkAppRunner.tsx +67 -0
  20. package/src/cli/ui/components/InkComponents.tsx +613 -0
  21. package/src/cli/ui/components/NovaInkApp.tsx +312 -0
  22. package/src/cli/ui/components/ProgressBar.ts +177 -0
  23. package/src/cli/ui/components/ProgressIndicator.ts +298 -0
  24. package/src/cli/ui/components/QuickActions.ts +396 -0
  25. package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
  26. package/src/cli/ui/components/StatusBar.ts +194 -0
  27. package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
  28. package/src/cli/ui/components/index.ts +27 -0
  29. package/src/cli/ui/ink-prototype.tsx +347 -0
  30. package/src/cli/utils/CliUI.ts +336 -0
  31. package/src/cli/utils/CompletionHelper.ts +388 -0
  32. package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
  33. package/src/cli/utils/EnhancedCompleter.ts +513 -0
  34. package/src/cli/utils/ErrorEnhancer.ts +429 -0
  35. package/src/cli/utils/OutputFormatter.ts +193 -0
  36. package/src/cli/utils/index.ts +9 -0
  37. package/src/core/agents/AgentOrchestrator.ts +515 -0
  38. package/src/core/agents/index.ts +17 -0
  39. package/src/core/audit/AuditLogger.ts +509 -0
  40. package/src/core/audit/index.ts +11 -0
  41. package/src/core/auth/AuthManager.d.ts.map +1 -0
  42. package/src/core/auth/AuthManager.ts +138 -0
  43. package/src/core/auth/index.d.ts.map +1 -0
  44. package/src/core/auth/index.ts +2 -0
  45. package/src/core/config/ConfigManager.d.ts.map +1 -0
  46. package/src/core/config/ConfigManager.test.ts +183 -0
  47. package/src/core/config/ConfigManager.ts +1219 -0
  48. package/src/core/config/index.d.ts.map +1 -0
  49. package/src/core/config/index.ts +1 -0
  50. package/src/core/context/ContextBuilder.d.ts.map +1 -0
  51. package/src/core/context/ContextBuilder.ts +171 -0
  52. package/src/core/context/ContextCompressor.d.ts.map +1 -0
  53. package/src/core/context/ContextCompressor.ts +642 -0
  54. package/src/core/context/LayeredMemoryManager.ts +657 -0
  55. package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
  56. package/src/core/context/MemoryDiscovery.ts +175 -0
  57. package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
  58. package/src/core/context/defaultSystemPrompt.ts +35 -0
  59. package/src/core/context/index.d.ts.map +1 -0
  60. package/src/core/context/index.ts +22 -0
  61. package/src/core/extensions/SkillGenerator.ts +421 -0
  62. package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
  63. package/src/core/extensions/SkillInstaller.ts +257 -0
  64. package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
  65. package/src/core/extensions/SkillRegistry.ts +361 -0
  66. package/src/core/extensions/SkillValidator.ts +525 -0
  67. package/src/core/extensions/index.ts +15 -0
  68. package/src/core/index.d.ts.map +1 -0
  69. package/src/core/index.ts +42 -0
  70. package/src/core/mcp/McpManager.d.ts.map +1 -0
  71. package/src/core/mcp/McpManager.ts +632 -0
  72. package/src/core/mcp/index.d.ts.map +1 -0
  73. package/src/core/mcp/index.ts +2 -0
  74. package/src/core/model/ModelClient.d.ts.map +1 -0
  75. package/src/core/model/ModelClient.ts +217 -0
  76. package/src/core/model/ModelConnectionTester.ts +363 -0
  77. package/src/core/model/ModelValidator.ts +348 -0
  78. package/src/core/model/index.d.ts.map +1 -0
  79. package/src/core/model/index.ts +6 -0
  80. package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
  81. package/src/core/model/providers/AnthropicProvider.ts +279 -0
  82. package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
  83. package/src/core/model/providers/CodingPlanProvider.ts +210 -0
  84. package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
  85. package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
  86. package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
  87. package/src/core/model/providers/OllamaManager.ts +201 -0
  88. package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
  89. package/src/core/model/providers/OllamaProvider.ts +73 -0
  90. package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  91. package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
  92. package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
  93. package/src/core/model/providers/OpenAIProvider.ts +29 -0
  94. package/src/core/model/providers/index.d.ts.map +1 -0
  95. package/src/core/model/providers/index.ts +12 -0
  96. package/src/core/model/types.d.ts.map +1 -0
  97. package/src/core/model/types.ts +77 -0
  98. package/src/core/security/ApprovalManager.d.ts.map +1 -0
  99. package/src/core/security/ApprovalManager.ts +174 -0
  100. package/src/core/security/FileFilter.d.ts.map +1 -0
  101. package/src/core/security/FileFilter.ts +141 -0
  102. package/src/core/security/HookExecutor.d.ts.map +1 -0
  103. package/src/core/security/HookExecutor.ts +178 -0
  104. package/src/core/security/SandboxExecutor.ts +447 -0
  105. package/src/core/security/index.d.ts.map +1 -0
  106. package/src/core/security/index.ts +8 -0
  107. package/src/core/session/AgentLoop.d.ts.map +1 -0
  108. package/src/core/session/AgentLoop.ts +501 -0
  109. package/src/core/session/SessionManager.d.ts.map +1 -0
  110. package/src/core/session/SessionManager.test.ts +183 -0
  111. package/src/core/session/SessionManager.ts +460 -0
  112. package/src/core/session/index.d.ts.map +1 -0
  113. package/src/core/session/index.ts +3 -0
  114. package/src/core/telemetry/Telemetry.d.ts.map +1 -0
  115. package/src/core/telemetry/Telemetry.ts +90 -0
  116. package/src/core/telemetry/TelemetryService.ts +531 -0
  117. package/src/core/telemetry/index.d.ts.map +1 -0
  118. package/src/core/telemetry/index.ts +12 -0
  119. package/src/core/testing/AutoFixer.ts +385 -0
  120. package/src/core/testing/ErrorAnalyzer.ts +499 -0
  121. package/src/core/testing/TestRunner.ts +265 -0
  122. package/src/core/testing/agent-cli-tests.ts +538 -0
  123. package/src/core/testing/index.ts +11 -0
  124. package/src/core/tools/ToolRegistry.d.ts.map +1 -0
  125. package/src/core/tools/ToolRegistry.test.ts +206 -0
  126. package/src/core/tools/ToolRegistry.ts +260 -0
  127. package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
  128. package/src/core/tools/impl/EditFileTool.ts +97 -0
  129. package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
  130. package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
  131. package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
  132. package/src/core/tools/impl/MemoryTool.ts +102 -0
  133. package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
  134. package/src/core/tools/impl/ReadFileTool.ts +58 -0
  135. package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
  136. package/src/core/tools/impl/SearchContentTool.ts +94 -0
  137. package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
  138. package/src/core/tools/impl/SearchFileTool.ts +61 -0
  139. package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
  140. package/src/core/tools/impl/ShellTool.ts +118 -0
  141. package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
  142. package/src/core/tools/impl/TaskTool.ts +207 -0
  143. package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
  144. package/src/core/tools/impl/TodoTool.ts +122 -0
  145. package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
  146. package/src/core/tools/impl/WebFetchTool.ts +103 -0
  147. package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
  148. package/src/core/tools/impl/WebSearchTool.ts +89 -0
  149. package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
  150. package/src/core/tools/impl/WriteFileTool.ts +49 -0
  151. package/src/core/tools/impl/index.d.ts.map +1 -0
  152. package/src/core/tools/impl/index.ts +16 -0
  153. package/src/core/tools/index.d.ts.map +1 -0
  154. package/src/core/tools/index.ts +7 -0
  155. package/src/core/tools/schemas/execution.d.ts.map +1 -0
  156. package/src/core/tools/schemas/execution.ts +42 -0
  157. package/src/core/tools/schemas/file.d.ts.map +1 -0
  158. package/src/core/tools/schemas/file.ts +119 -0
  159. package/src/core/tools/schemas/index.d.ts.map +1 -0
  160. package/src/core/tools/schemas/index.ts +11 -0
  161. package/src/core/tools/schemas/memory.d.ts.map +1 -0
  162. package/src/core/tools/schemas/memory.ts +52 -0
  163. package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
  164. package/src/core/tools/schemas/orchestration.ts +44 -0
  165. package/src/core/tools/schemas/search.d.ts.map +1 -0
  166. package/src/core/tools/schemas/search.ts +112 -0
  167. package/src/core/tools/schemas/todo.d.ts.map +1 -0
  168. package/src/core/tools/schemas/todo.ts +32 -0
  169. package/src/core/tools/schemas/web.d.ts.map +1 -0
  170. package/src/core/tools/schemas/web.ts +86 -0
  171. package/src/core/types/config.d.ts.map +1 -0
  172. package/src/core/types/config.ts +200 -0
  173. package/src/core/types/errors.d.ts.map +1 -0
  174. package/src/core/types/errors.ts +204 -0
  175. package/src/core/types/index.d.ts.map +1 -0
  176. package/src/core/types/index.ts +8 -0
  177. package/src/core/types/session.d.ts.map +1 -0
  178. package/src/core/types/session.ts +216 -0
  179. package/src/core/types/tools.d.ts.map +1 -0
  180. package/src/core/types/tools.ts +157 -0
  181. package/src/core/utils/CheckpointManager.d.ts.map +1 -0
  182. package/src/core/utils/CheckpointManager.ts +327 -0
  183. package/src/core/utils/Logger.d.ts.map +1 -0
  184. package/src/core/utils/Logger.ts +98 -0
  185. package/src/core/utils/RetryManager.ts +471 -0
  186. package/src/core/utils/TokenCounter.d.ts.map +1 -0
  187. package/src/core/utils/TokenCounter.ts +414 -0
  188. package/src/core/utils/VectorMemoryStore.ts +440 -0
  189. package/src/core/utils/helpers.d.ts.map +1 -0
  190. package/src/core/utils/helpers.ts +89 -0
  191. package/src/core/utils/index.d.ts.map +1 -0
  192. package/src/core/utils/index.ts +19 -0
@@ -0,0 +1,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
+ }