@lanonasis/cli 1.5.2 → 2.0.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.
- package/README.md +12 -3
- package/dist/core/achievements.d.ts +102 -0
- package/dist/core/achievements.js +425 -0
- package/dist/core/architecture.d.ts +145 -0
- package/dist/core/architecture.js +355 -0
- package/dist/core/dashboard.d.ts +71 -0
- package/dist/core/dashboard.js +569 -0
- package/dist/core/error-handler.d.ts +97 -0
- package/dist/core/error-handler.js +380 -0
- package/dist/core/power-mode.d.ts +118 -0
- package/dist/core/power-mode.js +460 -0
- package/dist/core/progress.d.ts +160 -0
- package/dist/core/progress.js +428 -0
- package/dist/core/welcome.d.ts +40 -0
- package/dist/core/welcome.js +466 -0
- package/dist/enhanced-cli.d.ts +15 -0
- package/dist/enhanced-cli.js +296 -0
- package/dist/index-simple.js +2 -2
- package/dist/utils/mcp-client.js +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Error Handling and Recovery System
|
|
3
|
+
* Provides intelligent error messages and recovery suggestions
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import boxen from 'boxen';
|
|
7
|
+
export class ErrorHandler {
|
|
8
|
+
stateManager;
|
|
9
|
+
errorHistory = [];
|
|
10
|
+
constructor(stateManager) {
|
|
11
|
+
this.stateManager = stateManager;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Main error handling method
|
|
15
|
+
*/
|
|
16
|
+
handle(error) {
|
|
17
|
+
const cliError = this.normalizeError(error);
|
|
18
|
+
this.errorHistory.push(cliError);
|
|
19
|
+
// Log to debug if verbose mode
|
|
20
|
+
if (process.env.CLI_VERBOSE === 'true') {
|
|
21
|
+
this.logDebugInfo(cliError);
|
|
22
|
+
}
|
|
23
|
+
// Display user-friendly error
|
|
24
|
+
this.displayError(cliError);
|
|
25
|
+
// Offer recovery options
|
|
26
|
+
if (cliError.recoveryActions && cliError.recoveryActions.length > 0) {
|
|
27
|
+
this.offerRecovery(cliError.recoveryActions);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Normalize various error types into CLIError
|
|
32
|
+
*/
|
|
33
|
+
normalizeError(error) {
|
|
34
|
+
if (error instanceof Error) {
|
|
35
|
+
const cliError = error;
|
|
36
|
+
// Enhance known errors with better context
|
|
37
|
+
if (error.message.includes('ECONNREFUSED')) {
|
|
38
|
+
return this.createConnectionError(error);
|
|
39
|
+
}
|
|
40
|
+
else if (error.message.includes('UNAUTHORIZED') || error.message.includes('401')) {
|
|
41
|
+
return this.createAuthError(error);
|
|
42
|
+
}
|
|
43
|
+
else if (error.message.includes('ENOTFOUND')) {
|
|
44
|
+
return this.createNetworkError(error);
|
|
45
|
+
}
|
|
46
|
+
else if (error.message.includes('TIMEOUT')) {
|
|
47
|
+
return this.createTimeoutError(error);
|
|
48
|
+
}
|
|
49
|
+
else if (error.message.includes('RATE_LIMIT')) {
|
|
50
|
+
return this.createRateLimitError(error);
|
|
51
|
+
}
|
|
52
|
+
return cliError;
|
|
53
|
+
}
|
|
54
|
+
// Handle non-Error objects
|
|
55
|
+
return {
|
|
56
|
+
name: 'UnknownError',
|
|
57
|
+
message: String(error),
|
|
58
|
+
severity: 'error'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create specific error types with recovery suggestions
|
|
63
|
+
*/
|
|
64
|
+
createConnectionError(originalError) {
|
|
65
|
+
return {
|
|
66
|
+
...originalError,
|
|
67
|
+
name: 'ConnectionError',
|
|
68
|
+
message: 'Cannot connect to Onasis service',
|
|
69
|
+
severity: 'error',
|
|
70
|
+
code: 'ECONNREFUSED',
|
|
71
|
+
suggestion: 'The service might be down or your network connection might be unavailable.',
|
|
72
|
+
recoveryActions: [
|
|
73
|
+
{
|
|
74
|
+
label: 'Check internet connection',
|
|
75
|
+
command: 'ping api.lanonasis.com',
|
|
76
|
+
description: 'Test network connectivity'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: 'Verify service URL',
|
|
80
|
+
command: 'onasis config get api-url',
|
|
81
|
+
description: 'Check configured service endpoint'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: 'Try alternative URL',
|
|
85
|
+
command: 'onasis config set api-url <new-url>',
|
|
86
|
+
description: 'Update service endpoint'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
label: 'Use local mode',
|
|
90
|
+
command: 'onasis --offline',
|
|
91
|
+
description: 'Work offline with cached data'
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
createAuthError(originalError) {
|
|
97
|
+
return {
|
|
98
|
+
...originalError,
|
|
99
|
+
name: 'AuthenticationError',
|
|
100
|
+
message: 'Authentication failed',
|
|
101
|
+
severity: 'error',
|
|
102
|
+
code: 'UNAUTHORIZED',
|
|
103
|
+
suggestion: 'Your session may have expired or your credentials might be invalid.',
|
|
104
|
+
recoveryActions: [
|
|
105
|
+
{
|
|
106
|
+
label: 'Re-authenticate',
|
|
107
|
+
command: 'onasis auth login',
|
|
108
|
+
description: 'Sign in again with your credentials'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
label: 'Check API keys',
|
|
112
|
+
command: 'onasis api-keys verify',
|
|
113
|
+
description: 'Verify your API keys are valid'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: 'Reset credentials',
|
|
117
|
+
command: 'onasis auth reset',
|
|
118
|
+
description: 'Clear and reset authentication'
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
createNetworkError(originalError) {
|
|
124
|
+
return {
|
|
125
|
+
...originalError,
|
|
126
|
+
name: 'NetworkError',
|
|
127
|
+
message: 'Network request failed',
|
|
128
|
+
severity: 'error',
|
|
129
|
+
code: 'ENOTFOUND',
|
|
130
|
+
suggestion: 'Unable to resolve the service domain. Check your network settings.',
|
|
131
|
+
recoveryActions: [
|
|
132
|
+
{
|
|
133
|
+
label: 'Check DNS',
|
|
134
|
+
command: 'nslookup api.lanonasis.com',
|
|
135
|
+
description: 'Verify DNS resolution'
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
label: 'Try with IP',
|
|
139
|
+
command: 'onasis config set api-url https://<ip-address>',
|
|
140
|
+
description: 'Use direct IP instead of domain'
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
label: 'Check proxy settings',
|
|
144
|
+
command: 'onasis config show proxy',
|
|
145
|
+
description: 'Review proxy configuration'
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
createTimeoutError(originalError) {
|
|
151
|
+
return {
|
|
152
|
+
...originalError,
|
|
153
|
+
name: 'TimeoutError',
|
|
154
|
+
message: 'Request timed out',
|
|
155
|
+
severity: 'warning',
|
|
156
|
+
code: 'TIMEOUT',
|
|
157
|
+
suggestion: 'The operation took too long. The service might be slow or overloaded.',
|
|
158
|
+
recoveryActions: [
|
|
159
|
+
{
|
|
160
|
+
label: 'Retry operation',
|
|
161
|
+
command: 'onasis retry',
|
|
162
|
+
description: 'Retry the last operation'
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
label: 'Increase timeout',
|
|
166
|
+
command: 'onasis config set timeout 30000',
|
|
167
|
+
description: 'Set longer timeout (30 seconds)'
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
label: 'Check service status',
|
|
171
|
+
command: 'onasis status',
|
|
172
|
+
description: 'Check if service is operational'
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
createRateLimitError(originalError) {
|
|
178
|
+
return {
|
|
179
|
+
...originalError,
|
|
180
|
+
name: 'RateLimitError',
|
|
181
|
+
message: 'Rate limit exceeded',
|
|
182
|
+
severity: 'warning',
|
|
183
|
+
code: 'RATE_LIMIT',
|
|
184
|
+
suggestion: 'You have made too many requests. Please wait before trying again.',
|
|
185
|
+
recoveryActions: [
|
|
186
|
+
{
|
|
187
|
+
label: 'Check limits',
|
|
188
|
+
command: 'onasis api-keys limits',
|
|
189
|
+
description: 'View your current rate limits'
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
label: 'Upgrade plan',
|
|
193
|
+
command: 'onasis account upgrade',
|
|
194
|
+
description: 'Upgrade for higher limits'
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Display error in user-friendly format
|
|
201
|
+
*/
|
|
202
|
+
displayError(error) {
|
|
203
|
+
const icon = this.getErrorIcon(error.severity || 'error');
|
|
204
|
+
const color = this.getErrorColor(error.severity || 'error');
|
|
205
|
+
const errorBox = boxen(`${icon} ${chalk.bold(error.name || 'Error')}\n\n` +
|
|
206
|
+
`${error.message}\n` +
|
|
207
|
+
(error.suggestion ? `\n${chalk.yellow('💡 ' + error.suggestion)}` : ''), {
|
|
208
|
+
padding: 1,
|
|
209
|
+
borderStyle: 'round',
|
|
210
|
+
borderColor: color,
|
|
211
|
+
title: error.code ? `Error Code: ${error.code}` : undefined,
|
|
212
|
+
titleAlignment: 'right'
|
|
213
|
+
});
|
|
214
|
+
console.error(errorBox);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Offer recovery actions to the user
|
|
218
|
+
*/
|
|
219
|
+
offerRecovery(actions) {
|
|
220
|
+
console.log(chalk.bold('\n🔧 Possible Solutions:\n'));
|
|
221
|
+
actions.forEach((action, index) => {
|
|
222
|
+
console.log(` ${chalk.cyan(`${index + 1}.`)} ${chalk.bold(action.label)}\n` +
|
|
223
|
+
` ${chalk.gray('Command:')} ${chalk.green(action.command)}\n` +
|
|
224
|
+
(action.description ? ` ${chalk.dim(action.description)}\n` : ''));
|
|
225
|
+
});
|
|
226
|
+
console.log(chalk.dim('\nRun any of the above commands to resolve the issue.'));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Log debug information for verbose mode
|
|
230
|
+
*/
|
|
231
|
+
logDebugInfo(error) {
|
|
232
|
+
console.error(chalk.dim('\n--- Debug Information ---'));
|
|
233
|
+
console.error(chalk.dim('Timestamp:'), new Date().toISOString());
|
|
234
|
+
console.error(chalk.dim('Error Type:'), error.name);
|
|
235
|
+
console.error(chalk.dim('Error Code:'), error.code || 'N/A');
|
|
236
|
+
if (error.stack) {
|
|
237
|
+
console.error(chalk.dim('Stack Trace:'));
|
|
238
|
+
console.error(chalk.dim(error.stack));
|
|
239
|
+
}
|
|
240
|
+
if (error.context) {
|
|
241
|
+
console.error(chalk.dim('Context:'));
|
|
242
|
+
console.error(chalk.dim(JSON.stringify(error.context, null, 2)));
|
|
243
|
+
}
|
|
244
|
+
console.error(chalk.dim('--- End Debug Information ---\n'));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get error icon based on severity
|
|
248
|
+
*/
|
|
249
|
+
getErrorIcon(severity) {
|
|
250
|
+
switch (severity) {
|
|
251
|
+
case 'error': return chalk.red('✖');
|
|
252
|
+
case 'warning': return chalk.yellow('⚠');
|
|
253
|
+
case 'info': return chalk.blue('ℹ');
|
|
254
|
+
default: return chalk.red('✖');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get error color based on severity
|
|
259
|
+
*/
|
|
260
|
+
getErrorColor(severity) {
|
|
261
|
+
switch (severity) {
|
|
262
|
+
case 'error': return 'red';
|
|
263
|
+
case 'warning': return 'yellow';
|
|
264
|
+
case 'info': return 'blue';
|
|
265
|
+
default: return 'red';
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get error history for debugging
|
|
270
|
+
*/
|
|
271
|
+
getErrorHistory() {
|
|
272
|
+
return this.errorHistory;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Clear error history
|
|
276
|
+
*/
|
|
277
|
+
clearHistory() {
|
|
278
|
+
this.errorHistory = [];
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Retry last failed operation
|
|
282
|
+
*/
|
|
283
|
+
async retryLastOperation() {
|
|
284
|
+
const lastError = this.errorHistory[this.errorHistory.length - 1];
|
|
285
|
+
if (lastError && lastError.context?.operation) {
|
|
286
|
+
console.log(chalk.cyan('🔄 Retrying last operation...'));
|
|
287
|
+
// Implementation would retry the stored operation
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
console.log(chalk.yellow('No operation to retry'));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Global error boundary for the CLI
|
|
296
|
+
*/
|
|
297
|
+
export class ErrorBoundary {
|
|
298
|
+
errorHandler;
|
|
299
|
+
constructor(errorHandler) {
|
|
300
|
+
this.errorHandler = errorHandler;
|
|
301
|
+
this.setupGlobalHandlers();
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Setup global error handlers
|
|
305
|
+
*/
|
|
306
|
+
setupGlobalHandlers() {
|
|
307
|
+
// Handle uncaught exceptions
|
|
308
|
+
process.on('uncaughtException', (error) => {
|
|
309
|
+
this.errorHandler.handle({
|
|
310
|
+
...error,
|
|
311
|
+
name: 'UncaughtException',
|
|
312
|
+
severity: 'error',
|
|
313
|
+
suggestion: 'An unexpected error occurred. This might be a bug in the application.'
|
|
314
|
+
});
|
|
315
|
+
// Give time for error to be displayed before exiting
|
|
316
|
+
setTimeout(() => process.exit(1), 100);
|
|
317
|
+
});
|
|
318
|
+
// Handle unhandled promise rejections
|
|
319
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
320
|
+
this.errorHandler.handle({
|
|
321
|
+
name: 'UnhandledRejection',
|
|
322
|
+
message: String(reason),
|
|
323
|
+
severity: 'error',
|
|
324
|
+
suggestion: 'A promise was rejected without being handled. Check your async operations.',
|
|
325
|
+
context: { promise: String(promise) }
|
|
326
|
+
});
|
|
327
|
+
setTimeout(() => process.exit(1), 100);
|
|
328
|
+
});
|
|
329
|
+
// Handle SIGINT (Ctrl+C)
|
|
330
|
+
process.on('SIGINT', () => {
|
|
331
|
+
console.log(chalk.yellow('\n\n👋 Gracefully shutting down...'));
|
|
332
|
+
this.cleanup();
|
|
333
|
+
process.exit(0);
|
|
334
|
+
});
|
|
335
|
+
// Handle SIGTERM
|
|
336
|
+
process.on('SIGTERM', () => {
|
|
337
|
+
console.log(chalk.yellow('\n\n🛑 Received termination signal...'));
|
|
338
|
+
this.cleanup();
|
|
339
|
+
process.exit(0);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Cleanup before exit
|
|
344
|
+
*/
|
|
345
|
+
cleanup() {
|
|
346
|
+
// Save any pending state
|
|
347
|
+
// Close any open connections
|
|
348
|
+
// Flush any buffers
|
|
349
|
+
console.log(chalk.dim('Cleanup complete'));
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Wrap async functions with error handling
|
|
353
|
+
*/
|
|
354
|
+
wrapAsync(fn) {
|
|
355
|
+
return (async (...args) => {
|
|
356
|
+
try {
|
|
357
|
+
return await fn(...args);
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
this.errorHandler.handle(error);
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Validation error for input validation
|
|
368
|
+
*/
|
|
369
|
+
export class ValidationError extends Error {
|
|
370
|
+
severity = 'warning';
|
|
371
|
+
suggestion;
|
|
372
|
+
constructor(message, field, suggestion) {
|
|
373
|
+
super(message);
|
|
374
|
+
this.name = 'ValidationError';
|
|
375
|
+
if (field) {
|
|
376
|
+
this.message = `Invalid ${field}: ${message}`;
|
|
377
|
+
}
|
|
378
|
+
this.suggestion = suggestion;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Power User Mode
|
|
3
|
+
* Streamlined interface for expert users with advanced features
|
|
4
|
+
*/
|
|
5
|
+
import { StateManager } from './architecture.js';
|
|
6
|
+
export declare class PowerUserMode {
|
|
7
|
+
private stateManager;
|
|
8
|
+
private commandHistory;
|
|
9
|
+
private historyIndex;
|
|
10
|
+
private rl?;
|
|
11
|
+
private smartSuggestions;
|
|
12
|
+
private aliases;
|
|
13
|
+
constructor(stateManager: StateManager);
|
|
14
|
+
/**
|
|
15
|
+
* Enter power user mode
|
|
16
|
+
*/
|
|
17
|
+
enter(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Exit power user mode
|
|
20
|
+
*/
|
|
21
|
+
exit(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Show power mode banner
|
|
24
|
+
*/
|
|
25
|
+
private showBanner;
|
|
26
|
+
/**
|
|
27
|
+
* Get command prompt
|
|
28
|
+
*/
|
|
29
|
+
private getPrompt;
|
|
30
|
+
/**
|
|
31
|
+
* Setup key bindings for enhanced navigation
|
|
32
|
+
*/
|
|
33
|
+
private setupKeyBindings;
|
|
34
|
+
/**
|
|
35
|
+
* Start the REPL loop
|
|
36
|
+
*/
|
|
37
|
+
private startREPL;
|
|
38
|
+
/**
|
|
39
|
+
* Process power mode commands
|
|
40
|
+
*/
|
|
41
|
+
private processCommand;
|
|
42
|
+
/**
|
|
43
|
+
* Quick create command
|
|
44
|
+
*/
|
|
45
|
+
private quickCreate;
|
|
46
|
+
/**
|
|
47
|
+
* Quick search command
|
|
48
|
+
*/
|
|
49
|
+
private quickSearch;
|
|
50
|
+
/**
|
|
51
|
+
* Quick list command
|
|
52
|
+
*/
|
|
53
|
+
private quickList;
|
|
54
|
+
/**
|
|
55
|
+
* Quick delete command
|
|
56
|
+
*/
|
|
57
|
+
private quickDelete;
|
|
58
|
+
/**
|
|
59
|
+
* Quick update command
|
|
60
|
+
*/
|
|
61
|
+
private quickUpdate;
|
|
62
|
+
/**
|
|
63
|
+
* Handle topic commands
|
|
64
|
+
*/
|
|
65
|
+
private handleTopic;
|
|
66
|
+
/**
|
|
67
|
+
* Handle API commands
|
|
68
|
+
*/
|
|
69
|
+
private handleApi;
|
|
70
|
+
/**
|
|
71
|
+
* Handle pipe operations
|
|
72
|
+
*/
|
|
73
|
+
private handlePipe;
|
|
74
|
+
/**
|
|
75
|
+
* Handle format changes
|
|
76
|
+
*/
|
|
77
|
+
private handleFormat;
|
|
78
|
+
/**
|
|
79
|
+
* Execute system command
|
|
80
|
+
*/
|
|
81
|
+
private executeSystemCommand;
|
|
82
|
+
/**
|
|
83
|
+
* Show help
|
|
84
|
+
*/
|
|
85
|
+
private showHelp;
|
|
86
|
+
/**
|
|
87
|
+
* Show command history
|
|
88
|
+
*/
|
|
89
|
+
private showHistory;
|
|
90
|
+
/**
|
|
91
|
+
* Handle alias management
|
|
92
|
+
*/
|
|
93
|
+
private handleAlias;
|
|
94
|
+
/**
|
|
95
|
+
* Expand aliases in command
|
|
96
|
+
*/
|
|
97
|
+
private expandAliases;
|
|
98
|
+
/**
|
|
99
|
+
* Tab completion function
|
|
100
|
+
*/
|
|
101
|
+
private completer;
|
|
102
|
+
/**
|
|
103
|
+
* Parse command arguments
|
|
104
|
+
*/
|
|
105
|
+
private parseArgs;
|
|
106
|
+
/**
|
|
107
|
+
* Generate a simple ID
|
|
108
|
+
*/
|
|
109
|
+
private generateId;
|
|
110
|
+
/**
|
|
111
|
+
* Load aliases from storage
|
|
112
|
+
*/
|
|
113
|
+
private loadAliases;
|
|
114
|
+
/**
|
|
115
|
+
* Save aliases to storage
|
|
116
|
+
*/
|
|
117
|
+
private saveAliases;
|
|
118
|
+
}
|