@lanonasis/cli 1.5.2 → 2.0.1

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.
@@ -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
+ }