@orcapt/cli 1.0.3 → 1.0.5

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/bin/orca.js CHANGED
@@ -335,6 +335,7 @@ lambdaCmd
335
335
  .command('invoke <function-name>')
336
336
  .description('Invoke Lambda function')
337
337
  .option('--payload <json>', 'JSON payload')
338
+ .option('--path <path>', 'HTTP path to invoke (e.g., health, api/v1/users)')
338
339
  .action((functionName, options) => {
339
340
  requireAuth('lambda invoke');
340
341
  lambdaInvoke(functionName, options);
@@ -345,6 +346,8 @@ lambdaCmd
345
346
  .description('Get Lambda function logs')
346
347
  .option('--tail', 'Stream logs in real-time')
347
348
  .option('--since <time>', 'Show logs since (e.g., 1h, 30m)', '10m')
349
+ .option('--page <number>', 'Page number (default: 1)', '1')
350
+ .option('--per-page <number>', 'Number of logs per page (default: 100, max: 1000)', '100')
348
351
  .action((functionName, options) => {
349
352
  requireAuth('lambda logs');
350
353
  lambdaLogs(functionName, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orcapt/cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "CLI tool for managing Orca projects - Quick setup and deployment",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -9,6 +9,7 @@ const https = require('https');
9
9
  const http = require('http');
10
10
  const { getCredentials } = require('./login');
11
11
  const { API_BASE_URL, API_ENDPOINTS } = require('../config');
12
+ const { handleError } = require('../utils/errorHandler');
12
13
 
13
14
  /**
14
15
  * Make API request to orcapt Deploy API
@@ -109,15 +110,7 @@ async function dbCreate(options) {
109
110
 
110
111
  } catch (error) {
111
112
  spinner.fail(chalk.red('Failed to create database'));
112
-
113
- if (error.statusCode === 401) {
114
- console.log(chalk.red('\n✗ Authentication failed'));
115
- console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orcapt login\n'));
116
- } else if (error.response && error.response.detail) {
117
- console.log(chalk.red(`\n✗ ${error.response.detail}\n`));
118
- } else {
119
- console.log(chalk.red(`\n✗ ${error.message}\n`));
120
- }
113
+ handleError(error, 'Database creation');
121
114
  process.exit(1);
122
115
  }
123
116
  }
@@ -169,15 +162,7 @@ async function dbList() {
169
162
 
170
163
  } catch (error) {
171
164
  spinner.fail(chalk.red('Failed to list databases'));
172
-
173
- if (error.statusCode === 401) {
174
- console.log(chalk.red('\n✗ Authentication failed'));
175
- console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orcapt login\n'));
176
- } else if (error.response && error.response.detail) {
177
- console.log(chalk.red(`\n✗ ${error.response.detail}\n`));
178
- } else {
179
- console.log(chalk.red(`\n✗ ${error.message}\n`));
180
- }
165
+ handleError(error, 'Database listing');
181
166
  process.exit(1);
182
167
  }
183
168
  }
@@ -225,17 +210,7 @@ async function dbRemove(databaseName) {
225
210
 
226
211
  } catch (error) {
227
212
  spinner.fail(chalk.red('Failed to delete database'));
228
-
229
- if (error.statusCode === 401) {
230
- console.log(chalk.red('\n✗ Authentication failed'));
231
- console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orcapt login\n'));
232
- } else if (error.statusCode === 404) {
233
- console.log(chalk.red(`\n✗ Database '${databaseName}' not found or doesn't belong to your workspace\n`));
234
- } else if (error.response && error.response.detail) {
235
- console.log(chalk.red(`\n✗ ${error.response.detail}\n`));
236
- } else {
237
- console.log(chalk.red(`\n✗ ${error.message}\n`));
238
- }
213
+ handleError(error, 'Database deletion');
239
214
  process.exit(1);
240
215
  }
241
216
  }
@@ -11,6 +11,7 @@ const fs = require('fs');
11
11
  const path = require('path');
12
12
  const { getCredentials } = require('./login');
13
13
  const { API_BASE_URL, API_ENDPOINTS } = require('../config');
14
+ const { handleError } = require('../utils/errorHandler');
14
15
  const {
15
16
  checkDockerInstalled,
16
17
  checkDockerImage,
@@ -193,17 +194,7 @@ async function lambdaDeploy(functionName, options = {}) {
193
194
  });
194
195
  } catch (error) {
195
196
  spinner.fail(chalk.red('✗ Failed to request ECR credentials'));
196
- console.log();
197
-
198
- if (error.statusCode) {
199
- console.log(chalk.red('Status Code:'), error.statusCode);
200
- console.log(chalk.red('Response:'), JSON.stringify(error.response, null, 2));
201
- } else if (error.message) {
202
- console.log(chalk.red('Error:'), error.message);
203
- } else {
204
- console.log(chalk.red('Error:'), error);
205
- }
206
-
197
+ handleError(error, 'ECR credentials request');
207
198
  console.log();
208
199
  console.log(chalk.yellow('💡 Troubleshooting tips:'));
209
200
  console.log(chalk.white(' 1. Check if backend is running'));
@@ -289,22 +280,7 @@ async function lambdaDeploy(functionName, options = {}) {
289
280
 
290
281
  } catch (error) {
291
282
  console.log(chalk.red('\n✗ Deployment failed'));
292
-
293
- if (error.statusCode === 401) {
294
- console.log(chalk.red('Authentication failed'));
295
- console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orcapt login\n'));
296
- } else if (error.statusCode === 404) {
297
- console.log(chalk.red('Endpoint not found'));
298
- console.log(chalk.yellow('The Lambda API may not be implemented yet.'));
299
- console.log(chalk.cyan('See STORAGE_LAMBDA_ARCHITECTURE.md for details\n'));
300
- } else if (error.code === 'ECONNREFUSED') {
301
- console.log(chalk.red('Connection refused'));
302
- console.log(chalk.yellow('Cannot connect to Orcapt API:'), chalk.white(API_BASE_URL));
303
- console.log(chalk.cyan('Make sure the backend is running.\n'));
304
- } else {
305
- console.log(chalk.red(`Error: ${error.message}\n`));
306
- }
307
-
283
+ handleError(error, 'Lambda deployment');
308
284
  process.exit(1);
309
285
  }
310
286
  }
@@ -360,21 +336,7 @@ async function lambdaList() {
360
336
 
361
337
  } catch (error) {
362
338
  spinner.fail(chalk.red('Failed to list functions'));
363
-
364
- if (error.statusCode === 401) {
365
- console.log(chalk.red('\n✗ Authentication failed'));
366
- console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orcapt login\n'));
367
- } else if (error.statusCode === 404) {
368
- console.log(chalk.red('\n✗ Endpoint not found'));
369
- console.log(chalk.yellow('The Lambda API may not be implemented yet.\n'));
370
- } else if (error.code === 'ECONNREFUSED') {
371
- console.log(chalk.red('\n✗ Connection refused'));
372
- console.log(chalk.yellow('Cannot connect to Orcapt API:'), chalk.white(API_BASE_URL));
373
- console.log(chalk.cyan('Make sure the backend is running.\n'));
374
- } else {
375
- console.log(chalk.red(`\n✗ ${error.message}\n`));
376
- }
377
-
339
+ handleError(error, 'Lambda listing');
378
340
  process.exit(1);
379
341
  }
380
342
  }
@@ -403,6 +365,10 @@ async function lambdaInvoke(functionName, options = {}) {
403
365
  process.exit(1);
404
366
  }
405
367
  }
368
+
369
+ // Add path (default to 'health' if not provided)
370
+ const path = options.path || 'health';
371
+ console.log(chalk.white('Path: '), chalk.yellow(path));
406
372
 
407
373
  const spinner = ora('Invoking function...').start();
408
374
 
@@ -410,11 +376,16 @@ async function lambdaInvoke(functionName, options = {}) {
410
376
  // Call backend API to invoke Lambda function
411
377
  const startTime = Date.now();
412
378
  const endpoint = API_ENDPOINTS.LAMBDA_INVOKE.replace('{functionName}', functionName);
379
+ const requestBody = {
380
+ payload,
381
+ path: options.path || 'health' // Default path
382
+ };
383
+
413
384
  const response = await makeApiRequest(
414
385
  'POST',
415
386
  endpoint,
416
387
  credentials,
417
- { payload }
388
+ requestBody
418
389
  );
419
390
 
420
391
  const duration = Date.now() - startTime;
@@ -433,33 +404,43 @@ async function lambdaInvoke(functionName, options = {}) {
433
404
  }
434
405
 
435
406
  console.log(chalk.cyan('\nResponse:'));
436
- console.log(chalk.white(JSON.stringify(response.response || response, null, 2)));
407
+
408
+ // Parse and format the response nicely
409
+ const lambdaResponse = response.response || response;
410
+
411
+ // If response has statusCode and body (API Gateway format)
412
+ if (lambdaResponse.statusCode && lambdaResponse.body) {
413
+ console.log(chalk.white('Status Code: '), chalk.green(lambdaResponse.statusCode));
414
+
415
+ // Try to parse body as JSON
416
+ let bodyContent;
417
+ try {
418
+ bodyContent = JSON.parse(lambdaResponse.body);
419
+ console.log(chalk.white('\nBody:'));
420
+ console.log(chalk.green(JSON.stringify(bodyContent, null, 2)));
421
+ } catch (e) {
422
+ // If not JSON, show as string
423
+ console.log(chalk.white('\nBody:'));
424
+ console.log(chalk.green(lambdaResponse.body));
425
+ }
426
+
427
+ // Show headers if available
428
+ if (lambdaResponse.headers && Object.keys(lambdaResponse.headers).length > 0) {
429
+ console.log(chalk.white('\nHeaders:'));
430
+ Object.entries(lambdaResponse.headers).forEach(([key, value]) => {
431
+ console.log(chalk.gray(` ${key}: `), chalk.yellow(value));
432
+ });
433
+ }
434
+ } else {
435
+ // Regular response format
436
+ console.log(chalk.green(JSON.stringify(lambdaResponse, null, 2)));
437
+ }
437
438
 
438
439
  console.log(chalk.cyan('\n============================================================\n'));
439
440
 
440
441
  } catch (error) {
441
442
  spinner.fail(chalk.red('Invocation failed'));
442
-
443
- if (error.statusCode === 401) {
444
- console.log(chalk.red('\n✗ Authentication failed'));
445
- console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orcapt login\n'));
446
- } else if (error.statusCode === 404) {
447
- console.log(chalk.red('\n✗ Function not found'));
448
- console.log(chalk.yellow(`Function '${functionName}' does not exist or doesn't belong to your workspace.\n`));
449
- } else if (error.statusCode === 500) {
450
- console.log(chalk.red('\n✗ Function execution error'));
451
- if (error.response && error.response.error) {
452
- console.log(chalk.yellow('Error:'), chalk.white(error.response.error));
453
- }
454
- console.log();
455
- } else if (error.code === 'ECONNREFUSED') {
456
- console.log(chalk.red('\n✗ Connection refused'));
457
- console.log(chalk.yellow('Cannot connect to Orcapt API:'), chalk.white(API_BASE_URL));
458
- console.log(chalk.cyan('Make sure the backend is running.\n'));
459
- } else {
460
- console.log(chalk.red(`\n✗ ${error.message}\n`));
461
- }
462
-
443
+ handleError(error, 'Lambda invocation');
463
444
  process.exit(1);
464
445
  }
465
446
  }
@@ -486,11 +467,24 @@ async function lambdaLogs(functionName, options = {}) {
486
467
 
487
468
  const spinner = ora('Fetching logs...').start();
488
469
  const endpoint = API_ENDPOINTS.LAMBDA_LOGS.replace('{functionName}', functionName);
470
+
471
+ // Build query parameters for pagination
472
+ const queryParams = {};
473
+ if (options.page) {
474
+ queryParams.page = parseInt(options.page) || 1;
475
+ }
476
+ if (options.perPage) {
477
+ queryParams.per_page = Math.min(parseInt(options.perPage) || 100, 1000);
478
+ }
479
+
480
+ const queryString = new URLSearchParams(queryParams).toString();
481
+ const fullEndpoint = queryString ? `${endpoint}?${queryString}` : endpoint;
482
+
489
483
  try {
490
484
  // Call backend API to get logs
491
485
  const response = await makeApiRequest(
492
486
  'GET',
493
- endpoint,
487
+ fullEndpoint,
494
488
  credentials
495
489
  );
496
490
 
@@ -498,12 +492,37 @@ async function lambdaLogs(functionName, options = {}) {
498
492
 
499
493
  console.log(chalk.cyan('\n============================================================'));
500
494
 
495
+ // Show sync statistics
496
+ if (response.synced_from_cloudwatch !== undefined || response.skipped_duplicates !== undefined) {
497
+ console.log(chalk.cyan('📊 Sync Statistics:'));
498
+ if (response.total_cloudwatch_logs !== undefined) {
499
+ console.log(chalk.white(' CloudWatch logs: '), chalk.yellow(response.total_cloudwatch_logs));
500
+ }
501
+ if (response.synced_from_cloudwatch !== undefined) {
502
+ console.log(chalk.white(' Newly synced: '), chalk.green(response.synced_from_cloudwatch));
503
+ }
504
+ if (response.skipped_duplicates !== undefined) {
505
+ console.log(chalk.white(' Skipped (dup): '), chalk.gray(response.skipped_duplicates));
506
+ }
507
+ console.log();
508
+ }
509
+
501
510
  if (!response.logs || response.logs.length === 0) {
502
- console.log(chalk.yellow('No logs found'));
511
+ console.log(chalk.yellow('No logs found in database'));
503
512
  console.log(chalk.cyan('\nTry invoking the function first:'));
504
513
  console.log(chalk.white(' orcapt lambda invoke'), chalk.cyan(functionName));
505
514
  } else {
506
- console.log(chalk.green(`✓ Found ${response.logs.length} log entries`));
515
+ // Show pagination info
516
+ if (response.pagination) {
517
+ const pagination = response.pagination;
518
+ console.log(chalk.cyan('📄 Pagination:'));
519
+ console.log(chalk.white(' Page: '), chalk.yellow(`${pagination.current_page} / ${pagination.total_pages}`));
520
+ console.log(chalk.white(' Per page: '), chalk.yellow(pagination.per_page));
521
+ console.log(chalk.white(' Total logs: '), chalk.yellow(pagination.total));
522
+ console.log();
523
+ }
524
+
525
+ console.log(chalk.green(`✓ Showing ${response.logs.length} log entries (page ${response.pagination?.current_page || 1})`));
507
526
  console.log(chalk.cyan('============================================================\n'));
508
527
 
509
528
  response.logs.forEach(log => {
@@ -521,6 +540,21 @@ async function lambdaLogs(functionName, options = {}) {
521
540
  chalk.white(log.message)
522
541
  );
523
542
  });
543
+
544
+ // Show pagination navigation hints
545
+ if (response.pagination) {
546
+ const pagination = response.pagination;
547
+ console.log(chalk.cyan('\n============================================================'));
548
+ if (pagination.has_prev_page) {
549
+ console.log(chalk.white(' Previous page: '), chalk.cyan(`orcapt lambda logs ${functionName} --page ${pagination.current_page - 1}`));
550
+ }
551
+ if (pagination.has_next_page) {
552
+ console.log(chalk.white(' Next page: '), chalk.cyan(`orcapt lambda logs ${functionName} --page ${pagination.current_page + 1}`));
553
+ }
554
+ if (!pagination.has_prev_page && !pagination.has_next_page) {
555
+ console.log(chalk.gray(' (No more pages)'));
556
+ }
557
+ }
524
558
  }
525
559
 
526
560
  console.log(chalk.cyan('\n============================================================'));
@@ -534,21 +568,7 @@ async function lambdaLogs(functionName, options = {}) {
534
568
 
535
569
  } catch (error) {
536
570
  spinner.fail(chalk.red('Failed to fetch logs'));
537
-
538
- if (error.statusCode === 401) {
539
- console.log(chalk.red('\n✗ Authentication failed'));
540
- console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orcapt login\n'));
541
- } else if (error.statusCode === 404) {
542
- console.log(chalk.red('\n✗ Function not found'));
543
- console.log(chalk.yellow(`Function '${functionName}' does not exist or doesn't belong to your workspace.\n`));
544
- } else if (error.code === 'ECONNREFUSED') {
545
- console.log(chalk.red('\n✗ Connection refused'));
546
- console.log(chalk.yellow('Cannot connect to Orcapt API:'), chalk.white(API_BASE_URL));
547
- console.log(chalk.cyan('Make sure the backend is running.\n'));
548
- } else {
549
- console.log(chalk.red(`\n✗ ${error.message}\n`));
550
- }
551
-
571
+ handleError(error, 'Lambda logs');
552
572
  process.exit(1);
553
573
  }
554
574
  }
@@ -572,12 +592,12 @@ async function lambdaRemove(functionName) {
572
592
  console.log(chalk.yellow('ECR repository will be kept for potential rollback.\n'));
573
593
 
574
594
  const spinner = ora('Removing function...').start();
575
-
595
+ const endpoint = API_ENDPOINTS.LAMBDA_DELETE.replace('{functionName}', functionName);
576
596
  try {
577
597
  // Call backend API to delete Lambda function
578
598
  const response = await makeApiRequest(
579
599
  'DELETE',
580
- `${API_ENDPOINTS.LAMBDA_DELETE}/${functionName}`,
600
+ endpoint,
581
601
  credentials
582
602
  );
583
603
 
@@ -595,24 +615,7 @@ async function lambdaRemove(functionName) {
595
615
 
596
616
  } catch (error) {
597
617
  spinner.fail(chalk.red('Failed to remove function'));
598
-
599
- if (error.statusCode === 401) {
600
- console.log(chalk.red('\n✗ Authentication failed'));
601
- console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orcapt login\n'));
602
- } else if (error.statusCode === 404) {
603
- console.log(chalk.red('\n✗ Function not found'));
604
- console.log(chalk.yellow(`Function '${functionName}' does not exist or doesn't belong to your workspace.\n`));
605
- } else if (error.statusCode === 409) {
606
- console.log(chalk.red('\n✗ Conflict'));
607
- console.log(chalk.yellow('Function may be currently in use. Please try again later.\n'));
608
- } else if (error.code === 'ECONNREFUSED') {
609
- console.log(chalk.red('\n✗ Connection refused'));
610
- console.log(chalk.yellow('Cannot connect to Orcapt API:'), chalk.white(API_BASE_URL));
611
- console.log(chalk.cyan('Make sure the backend is running.\n'));
612
- } else {
613
- console.log(chalk.red(`\n✗ ${error.message}\n`));
614
- }
615
-
618
+ handleError(error, 'Lambda removal');
616
619
  process.exit(1);
617
620
  }
618
621
  }
@@ -628,12 +631,12 @@ async function lambdaInfo(functionName) {
628
631
  const credentials = requireAuth();
629
632
 
630
633
  const spinner = ora('Fetching function details...').start();
631
-
634
+ const endpoint = API_ENDPOINTS.LAMBDA_INFO.replace('{functionName}', functionName);
632
635
  try {
633
636
  // Call backend API to get function details
634
637
  const response = await makeApiRequest(
635
638
  'GET',
636
- `${API_ENDPOINTS.LAMBDA_INFO}/${functionName}`,
639
+ endpoint,
637
640
  credentials
638
641
  );
639
642
 
@@ -706,21 +709,7 @@ async function lambdaInfo(functionName) {
706
709
 
707
710
  } catch (error) {
708
711
  spinner.fail(chalk.red('Failed to fetch function details'));
709
-
710
- if (error.statusCode === 401) {
711
- console.log(chalk.red('\n✗ Authentication failed'));
712
- console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orcapt login\n'));
713
- } else if (error.statusCode === 404) {
714
- console.log(chalk.red('\n✗ Function not found'));
715
- console.log(chalk.yellow(`Function '${functionName}' does not exist or doesn't belong to your workspace.\n`));
716
- } else if (error.code === 'ECONNREFUSED') {
717
- console.log(chalk.red('\n✗ Connection refused'));
718
- console.log(chalk.yellow('Cannot connect to Orcapt API:'), chalk.white(API_BASE_URL));
719
- console.log(chalk.cyan('Make sure the backend is running.\n'));
720
- } else {
721
- console.log(chalk.red(`\n✗ ${error.message}\n`));
722
- }
723
-
712
+ handleError(error, 'Lambda info');
724
713
  process.exit(1);
725
714
  }
726
715
  }
@@ -11,6 +11,7 @@ const https = require('https');
11
11
  const http = require('http');
12
12
  const { getCredentials } = require('./login');
13
13
  const { API_BASE_URL, API_ENDPOINTS } = require('../config');
14
+ const { handleError } = require('../utils/errorHandler');
14
15
 
15
16
  /**
16
17
  * Make API request to orcapt Deploy API
@@ -253,18 +254,7 @@ async function bucketCreate(bucketName, options = {}) {
253
254
 
254
255
  } catch (error) {
255
256
  spinner.fail(chalk.red('✗ Failed to create bucket'));
256
-
257
- if (error.response) {
258
- console.log(chalk.red(`\n✗ ${error.response.message || 'Unknown error'}`));
259
- if (error.statusCode === 409) {
260
- console.log(chalk.yellow(' Bucket name already exists for this workspace'));
261
- } else if (error.statusCode === 422) {
262
- console.log(chalk.yellow(' Invalid bucket name or parameters'));
263
- }
264
- } else {
265
- console.log(chalk.red(`\n✗ ${error.message}`));
266
- }
267
- console.log('');
257
+ handleError(error, 'Bucket creation');
268
258
  process.exit(1);
269
259
  }
270
260
  }
@@ -336,12 +326,7 @@ async function bucketList() {
336
326
 
337
327
  } catch (error) {
338
328
  spinner.fail(chalk.red('✗ Failed to list buckets'));
339
-
340
- if (error.response) {
341
- console.log(chalk.red(`\n✗ ${error.response.message || 'Unknown error'}\n`));
342
- } else {
343
- console.log(chalk.red(`\n✗ ${error.message}\n`));
344
- }
329
+ handleError(error, 'Bucket listing');
345
330
  process.exit(1);
346
331
  }
347
332
  }
@@ -414,18 +399,7 @@ async function fileUpload(bucketName, localPath, options = {}) {
414
399
 
415
400
  } catch (error) {
416
401
  spinner.fail(chalk.red('✗ Upload failed'));
417
-
418
- if (error.response) {
419
- console.log(chalk.red(`\n✗ ${error.response.message || 'Unknown error'}`));
420
- if (error.statusCode === 404) {
421
- console.log(chalk.yellow(' Bucket not found'));
422
- } else if (error.statusCode === 413) {
423
- console.log(chalk.yellow(' File too large (max 100MB)'));
424
- }
425
- } else {
426
- console.log(chalk.red(`\n✗ ${error.message}`));
427
- }
428
- console.log('');
402
+ handleError(error, 'File upload');
429
403
  process.exit(1);
430
404
  }
431
405
  }
@@ -504,16 +478,7 @@ async function fileDownload(bucketName, fileKey, localPath) {
504
478
 
505
479
  } catch (error) {
506
480
  spinner.fail(chalk.red('✗ Download failed'));
507
-
508
- if (error.response) {
509
- console.log(chalk.red(`\n✗ ${error.response.message || 'Unknown error'}`));
510
- if (error.statusCode === 404) {
511
- console.log(chalk.yellow(' File or bucket not found'));
512
- }
513
- } else {
514
- console.log(chalk.red(`\n✗ ${error.message}`));
515
- }
516
- console.log('');
481
+ handleError(error, 'File download');
517
482
  process.exit(1);
518
483
  }
519
484
  }
@@ -560,16 +525,7 @@ async function bucketInfo(bucketName) {
560
525
 
561
526
  } catch (error) {
562
527
  spinner.fail(chalk.red('✗ Failed to get bucket info'));
563
-
564
- if (error.response) {
565
- console.log(chalk.red(`\n✗ ${error.response.message || 'Unknown error'}`));
566
- if (error.statusCode === 404) {
567
- console.log(chalk.yellow(' Bucket not found'));
568
- }
569
- } else {
570
- console.log(chalk.red(`\n✗ ${error.message}`));
571
- }
572
- console.log('');
528
+ handleError(error, 'Bucket info');
573
529
  process.exit(1);
574
530
  }
575
531
  }
@@ -604,20 +560,7 @@ async function bucketDelete(bucketName, options = {}) {
604
560
 
605
561
  } catch (error) {
606
562
  spinner.fail(chalk.red('✗ Failed to delete bucket'));
607
-
608
- if (error.response) {
609
- console.log(chalk.red(`\n✗ ${error.response.message || 'Unknown error'}`));
610
- if (error.statusCode === 404) {
611
- console.log(chalk.yellow(' Bucket not found'));
612
- } else if (error.statusCode === 400 && error.response.file_count) {
613
- console.log(chalk.yellow(` Bucket contains ${error.response.file_count} file(s)`));
614
- console.log(chalk.cyan(' Use --force to delete anyway:'));
615
- console.log(chalk.white(' '), chalk.yellow(`orcapt storage bucket delete ${bucketName} --force`));
616
- }
617
- } else {
618
- console.log(chalk.red(`\n✗ ${error.message}`));
619
- }
620
- console.log('');
563
+ handleError(error, 'Bucket deletion');
621
564
  process.exit(1);
622
565
  }
623
566
  }
@@ -713,16 +656,7 @@ async function fileList(bucketName, options = {}) {
713
656
 
714
657
  } catch (error) {
715
658
  spinner.fail(chalk.red('✗ Failed to list files'));
716
-
717
- if (error.response) {
718
- console.log(chalk.red(`\n✗ ${error.response.message || 'Unknown error'}`));
719
- if (error.statusCode === 404) {
720
- console.log(chalk.yellow(' Bucket not found'));
721
- }
722
- } else {
723
- console.log(chalk.red(`\n✗ ${error.message}`));
724
- }
725
- console.log('');
659
+ handleError(error, 'File listing');
726
660
  process.exit(1);
727
661
  }
728
662
  }
@@ -755,16 +689,7 @@ async function fileDelete(bucketName, fileKey) {
755
689
 
756
690
  } catch (error) {
757
691
  spinner.fail(chalk.red('✗ Failed to delete file'));
758
-
759
- if (error.response) {
760
- console.log(chalk.red(`\n✗ ${error.response.message || 'Unknown error'}`));
761
- if (error.statusCode === 404) {
762
- console.log(chalk.yellow(' File or bucket not found'));
763
- }
764
- } else {
765
- console.log(chalk.red(`\n✗ ${error.message}`));
766
- }
767
- console.log('');
692
+ handleError(error, 'File deletion');
768
693
  process.exit(1);
769
694
  }
770
695
  }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Unified Error Handler for Orca CLI
3
+ * Handles errors from different API services and formats them for CLI display
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+
8
+ /**
9
+ * Extract error message from API response
10
+ * Supports multiple response formats:
11
+ * - databaseasservice: { success: false, error: "message" }
12
+ * - gptclone-api/platform-api: { result: null, message: "message" }
13
+ * - Generic: { detail: "message" } or { message: "message" }
14
+ */
15
+ function extractErrorMessage(error) {
16
+ if (!error.response) {
17
+ return error.message || 'Unknown error occurred';
18
+ }
19
+
20
+ const response = error.response;
21
+
22
+ // databaseasservice format: { success: false, error: "message" }
23
+ if (response.error) {
24
+ return response.error;
25
+ }
26
+
27
+ // gptclone-api/platform-api format: { result: null, message: "message" }
28
+ if (response.message) {
29
+ return response.message;
30
+ }
31
+
32
+ // Generic format: { detail: "message" }
33
+ if (response.detail) {
34
+ return response.detail;
35
+ }
36
+
37
+ // Laravel validation errors: { errors: { field: ["message"] } }
38
+ if (response.errors && typeof response.errors === 'object') {
39
+ const errorMessages = [];
40
+ for (const [field, messages] of Object.entries(response.errors)) {
41
+ if (Array.isArray(messages)) {
42
+ errorMessages.push(...messages);
43
+ } else {
44
+ errorMessages.push(messages);
45
+ }
46
+ }
47
+ if (errorMessages.length > 0) {
48
+ return errorMessages.join(', ');
49
+ }
50
+ }
51
+
52
+ // Fallback: try to stringify the response
53
+ if (typeof response === 'string') {
54
+ return response;
55
+ }
56
+
57
+ return 'Unknown error occurred';
58
+ }
59
+
60
+ /**
61
+ * Get user-friendly error message based on status code
62
+ */
63
+ function getStatusMessage(statusCode) {
64
+ const messages = {
65
+ 400: 'Bad Request - Please check your input parameters',
66
+ 401: 'Authentication failed - Your session may have expired',
67
+ 403: 'Forbidden - You don\'t have permission to perform this action',
68
+ 404: 'Resource not found',
69
+ 409: 'Conflict - Resource already exists',
70
+ 422: 'Validation error - Please check your input',
71
+ 500: 'Internal server error - Please try again later',
72
+ 502: 'Bad Gateway - Service temporarily unavailable',
73
+ 503: 'Service unavailable - Please try again later',
74
+ 504: 'Gateway timeout - Request took too long',
75
+ };
76
+
77
+ return messages[statusCode] || `HTTP ${statusCode} error`;
78
+ }
79
+
80
+ /**
81
+ * Handle and display error to CLI
82
+ */
83
+ function handleError(error, context = 'Operation') {
84
+ const statusCode = error.statusCode || error.status || (error.response && error.response.statusCode);
85
+ const errorMessage = extractErrorMessage(error);
86
+
87
+ // Network/connection errors
88
+ if (error.code === 'ECONNREFUSED') {
89
+ console.log(chalk.red(`\n✗ Connection refused`));
90
+ console.log(chalk.yellow('The API server is not reachable. Please check:'));
91
+ console.log(chalk.white(' 1. Is the backend service running?'));
92
+ console.log(chalk.white(' 2. Is the API URL correct?'));
93
+ console.log(chalk.white(' 3. Check your network connection\n'));
94
+ return;
95
+ }
96
+
97
+ if (error.code === 'ETIMEDOUT' || error.code === 'ENOTFOUND') {
98
+ console.log(chalk.red(`\n✗ Network error: ${error.message}`));
99
+ console.log(chalk.yellow('Please check your network connection and API URL\n'));
100
+ return;
101
+ }
102
+
103
+ // HTTP status code errors
104
+ if (statusCode) {
105
+ const statusMessage = getStatusMessage(statusCode);
106
+
107
+ console.log(chalk.red(`\n✗ ${context} failed`));
108
+ console.log(chalk.red(` Status: ${statusCode} - ${statusMessage}`));
109
+
110
+ if (errorMessage && errorMessage !== 'Unknown error occurred') {
111
+ console.log(chalk.yellow(` Error: ${errorMessage}`));
112
+ }
113
+
114
+ // Specific handling for common status codes
115
+ if (statusCode === 401) {
116
+ console.log(chalk.cyan('\nPlease run:'), chalk.white('orcapt login'), chalk.cyan('to authenticate\n'));
117
+ } else if (statusCode === 404) {
118
+ console.log(chalk.yellow('\nThe requested resource was not found or doesn\'t belong to your workspace\n'));
119
+ } else if (statusCode === 422) {
120
+ console.log(chalk.yellow('\nPlease check your input parameters and try again\n'));
121
+ } else if (statusCode >= 500) {
122
+ console.log(chalk.yellow('\nThis appears to be a server-side error. Please try again later.\n'));
123
+ console.log(chalk.gray('If the problem persists, contact support.\n'));
124
+ } else {
125
+ console.log('');
126
+ }
127
+ return;
128
+ }
129
+
130
+ // Generic error
131
+ console.log(chalk.red(`\n✗ ${context} failed`));
132
+ console.log(chalk.red(` ${errorMessage}\n`));
133
+ }
134
+
135
+ /**
136
+ * Format error for programmatic use (returns error object)
137
+ */
138
+ function formatError(error) {
139
+ return {
140
+ statusCode: error.statusCode || error.status || (error.response && error.response.statusCode),
141
+ message: extractErrorMessage(error),
142
+ code: error.code,
143
+ response: error.response,
144
+ };
145
+ }
146
+
147
+ module.exports = {
148
+ handleError,
149
+ formatError,
150
+ extractErrorMessage,
151
+ getStatusMessage,
152
+ };
153
+