@orcapt/cli 1.0.2 → 1.0.4

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 CHANGED
@@ -409,22 +409,6 @@ orcapt storage download my-bucket file.txt ./downloads/
409
409
  orcapt storage delete my-bucket file.txt
410
410
  ```
411
411
 
412
- #### Permission Management
413
-
414
- ```bash
415
- # Add permission
416
- orcapt storage permission add my-bucket \
417
- --target-type=user \
418
- --target-id=user123 \
419
- --read \
420
- --write
421
-
422
- # List permissions
423
- orcapt storage permission list my-bucket
424
- ```
425
-
426
- ---
427
-
428
412
  ### šŸ—„ļø Database Commands
429
413
 
430
414
  Create and manage PostgreSQL databases.
package/bin/orca.js CHANGED
@@ -15,17 +15,15 @@ const { login, isLoggedIn, getCredentials, clearCredentials } = require('../src/
15
15
  const { uiInit, uiStart, uiRemove } = require('../src/commands/ui');
16
16
  const { dbCreate, dbList, dbRemove } = require('../src/commands/db');
17
17
  const fetchDoc = require('../src/commands/fetch-doc');
18
- const {
19
- bucketCreate,
20
- bucketList,
18
+ const {
19
+ bucketCreate,
20
+ bucketList,
21
21
  bucketInfo,
22
22
  bucketDelete,
23
- fileUpload,
23
+ fileUpload,
24
24
  fileDownload,
25
25
  fileList,
26
- fileDelete,
27
- permissionAdd,
28
- permissionList
26
+ fileDelete
29
27
  } = require('../src/commands/storage');
30
28
  const { lambdaDeploy, lambdaList, lambdaInvoke, lambdaLogs, lambdaRemove, lambdaInfo } = require('../src/commands/lambda');
31
29
 
@@ -296,36 +294,7 @@ storageCmd
296
294
  fileDelete(bucket, fileKey);
297
295
  });
298
296
 
299
- // Permission commands
300
- const permissionCmd = storageCmd
301
- .command('permission')
302
- .description('Manage bucket permissions');
303
-
304
- permissionCmd
305
- .command('add <bucket>')
306
- .description('Add permission to bucket')
307
- .option('--target-type <type>', 'Target type (user, workspace, public, api_key)', 'user')
308
- .option('--target-id <id>', 'Target ID (user ID, workspace ID, etc.)')
309
- .option('--resource-type <type>', 'Resource type (bucket, folder, file)', 'bucket')
310
- .option('--resource-path <path>', 'Resource path (for folder/file permissions)')
311
- .option('--read', 'Grant read permission', false)
312
- .option('--write', 'Grant write permission', false)
313
- .option('--delete', 'Grant delete permission', false)
314
- .option('--list', 'Grant list permission', false)
315
- .option('--valid-until <date>', 'Permission expiry date (ISO 8601)')
316
- .option('--reason <text>', 'Reason for granting permission')
317
- .action((bucket, options) => {
318
- requireAuth('storage permission add');
319
- permissionAdd(bucket, options);
320
- });
321
-
322
- permissionCmd
323
- .command('list <bucket>')
324
- .description('List bucket permissions')
325
- .action((bucket) => {
326
- requireAuth('storage permission list');
327
- permissionList(bucket);
328
- });
297
+ // Permission commands removed as per user request
329
298
 
330
299
  // Ship command - Deploy Docker images to Lambda
331
300
  program
@@ -366,6 +335,7 @@ lambdaCmd
366
335
  .command('invoke <function-name>')
367
336
  .description('Invoke Lambda function')
368
337
  .option('--payload <json>', 'JSON payload')
338
+ .option('--path <path>', 'HTTP path to invoke (e.g., health, api/v1/users)')
369
339
  .action((functionName, options) => {
370
340
  requireAuth('lambda invoke');
371
341
  lambdaInvoke(functionName, options);
@@ -376,6 +346,8 @@ lambdaCmd
376
346
  .description('Get Lambda function logs')
377
347
  .option('--tail', 'Stream logs in real-time')
378
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')
379
351
  .action((functionName, options) => {
380
352
  requireAuth('lambda logs');
381
353
  lambdaLogs(functionName, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orcapt/cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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
  }
@@ -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
  }
@@ -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
@@ -79,31 +80,31 @@ function uploadFileToApi(endpoint, credentials, filePath, bucketName, options =
79
80
  const fileStream = fs.createReadStream(filePath);
80
81
  const fileName = path.basename(filePath);
81
82
  const boundary = `----FormBoundary${Date.now()}`;
82
-
83
+
83
84
  const folderPath = options.folder || '';
84
-
85
+
85
86
  // Build multipart form data
86
87
  let formData = '';
87
-
88
+
88
89
  // Add folder_path field
89
90
  if (folderPath) {
90
91
  formData += `--${boundary}\r\n`;
91
92
  formData += `Content-Disposition: form-data; name="folder_path"\r\n\r\n`;
92
93
  formData += `${folderPath}\r\n`;
93
94
  }
94
-
95
+
95
96
  // Add visibility field
96
97
  if (options.visibility) {
97
98
  formData += `--${boundary}\r\n`;
98
99
  formData += `Content-Disposition: form-data; name="visibility"\r\n\r\n`;
99
100
  formData += `${options.visibility}\r\n`;
100
101
  }
101
-
102
+
102
103
  // Add generate_url field
103
104
  formData += `--${boundary}\r\n`;
104
105
  formData += `Content-Disposition: form-data; name="generate_url"\r\n\r\n`;
105
106
  formData += `true\r\n`;
106
-
107
+
107
108
  // Add file field header
108
109
  formData += `--${boundary}\r\n`;
109
110
  formData += `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`;
@@ -206,7 +207,7 @@ async function bucketCreate(bucketName, options = {}) {
206
207
  console.log(chalk.cyan('============================================================\n'));
207
208
 
208
209
  const credentials = requireAuth();
209
-
210
+
210
211
  console.log(chalk.white('Bucket: '), chalk.yellow(bucketName));
211
212
  console.log(chalk.white('Workspace: '), chalk.yellow(credentials.workspace));
212
213
  console.log(chalk.white('Visibility: '), chalk.yellow(options.public ? 'public' : 'private'));
@@ -237,7 +238,7 @@ async function bucketCreate(bucketName, options = {}) {
237
238
  );
238
239
 
239
240
  spinner.succeed(chalk.green('āœ“ Bucket created successfully!'));
240
-
241
+
241
242
  console.log(chalk.cyan('\nšŸ“¦ Bucket Details:'));
242
243
  console.log(chalk.white(' Name: '), chalk.yellow(response.bucket.bucket_name));
243
244
  console.log(chalk.white(' AWS Bucket: '), chalk.gray(response.bucket.aws_bucket_name));
@@ -245,7 +246,7 @@ async function bucketCreate(bucketName, options = {}) {
245
246
  console.log(chalk.white(' Status: '), chalk.green(response.bucket.status));
246
247
  console.log(chalk.white(' Visibility: '), chalk.yellow(response.bucket.visibility));
247
248
  console.log(chalk.white(' Encryption: '), chalk.yellow(response.bucket.encryption_enabled ? 'Enabled' : 'Disabled'));
248
-
249
+
249
250
  console.log(chalk.cyan('\nšŸ’” Next Steps:'));
250
251
  console.log(chalk.white(' Upload file: '), chalk.yellow(`orcapt storage upload ${bucketName} <file-path>`));
251
252
  console.log(chalk.white(' List files: '), chalk.yellow(`orcapt storage files ${bucketName}`));
@@ -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
  }
@@ -316,7 +306,7 @@ async function bucketList() {
316
306
  const files = String(bucket.file_count).padEnd(10);
317
307
  const size = bucket.total_size.padEnd(15);
318
308
  const visibility = bucket.visibility.padEnd(15);
319
- const status = bucket.status === 'active'
309
+ const status = bucket.status === 'active'
320
310
  ? chalk.green(bucket.status.padEnd(15))
321
311
  : chalk.yellow(bucket.status.padEnd(15));
322
312
  const created = new Date(bucket.created_at).toLocaleDateString();
@@ -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
  }
@@ -370,11 +355,11 @@ async function fileUpload(bucketName, localPath, options = {}) {
370
355
  console.log(chalk.white('File: '), chalk.yellow(fileName));
371
356
  console.log(chalk.white('Local Path: '), chalk.gray(localPath));
372
357
  console.log(chalk.white('Size: '), chalk.yellow(`${fileSizeMB} MB`));
373
-
358
+
374
359
  if (options.folder) {
375
360
  console.log(chalk.white('Folder: '), chalk.yellow(options.folder));
376
361
  }
377
-
362
+
378
363
  console.log(chalk.white('Visibility: '), chalk.yellow(options.public ? 'public' : 'private'));
379
364
 
380
365
  const spinner = ora('Uploading file...').start();
@@ -394,19 +379,19 @@ async function fileUpload(bucketName, localPath, options = {}) {
394
379
  );
395
380
 
396
381
  spinner.succeed(chalk.green('āœ“ File uploaded successfully!'));
397
-
382
+
398
383
  console.log(chalk.cyan('\nšŸ“„ File Details:'));
399
384
  console.log(chalk.white(' Name: '), chalk.yellow(response.file.file_name));
400
385
  console.log(chalk.white(' Key: '), chalk.gray(response.file.file_key));
401
386
  console.log(chalk.white(' Size: '), chalk.yellow(response.file.file_size));
402
387
  console.log(chalk.white(' Type: '), chalk.yellow(response.file.mime_type));
403
388
  console.log(chalk.white(' Visibility: '), chalk.yellow(response.file.visibility));
404
-
389
+
405
390
  if (response.file.download_url) {
406
391
  console.log(chalk.cyan('\nšŸ”— Download URL (valid for 60 minutes):'));
407
392
  console.log(chalk.gray(response.file.download_url));
408
393
  }
409
-
394
+
410
395
  console.log(chalk.cyan('\nšŸ’” Next Steps:'));
411
396
  console.log(chalk.white(' List files: '), chalk.yellow(`orcapt storage files ${bucketName}`));
412
397
  console.log(chalk.white(' Download: '), chalk.yellow(`orcapt storage download ${bucketName} ${response.file.file_key}`));
@@ -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
  }
@@ -469,7 +443,7 @@ async function fileDownload(bucketName, fileKey, localPath) {
469
443
  const httpModule = isHttps ? https : http;
470
444
 
471
445
  const fileWriter = fs.createWriteStream(outputPath);
472
-
446
+
473
447
  httpModule.get(downloadUrl, (res) => {
474
448
  if (res.statusCode !== 200) {
475
449
  reject(new Error(`Failed to download: ${res.statusCode}`));
@@ -483,18 +457,18 @@ async function fileDownload(bucketName, fileKey, localPath) {
483
457
  resolve();
484
458
  });
485
459
  }).on('error', (err) => {
486
- fs.unlink(outputPath, () => {});
460
+ fs.unlink(outputPath, () => { });
487
461
  reject(err);
488
462
  });
489
463
 
490
464
  fileWriter.on('error', (err) => {
491
- fs.unlink(outputPath, () => {});
465
+ fs.unlink(outputPath, () => { });
492
466
  reject(err);
493
467
  });
494
468
  });
495
469
 
496
470
  spinner.succeed(chalk.green('āœ“ File downloaded successfully!'));
497
-
471
+
498
472
  console.log(chalk.cyan('\nšŸ“„ File Details:'));
499
473
  console.log(chalk.white(' Name: '), chalk.yellow(response.file.file_name));
500
474
  console.log(chalk.white(' Size: '), chalk.yellow(formatBytes(response.file.file_size_bytes)));
@@ -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
  }
@@ -550,26 +515,17 @@ async function bucketInfo(bucketName) {
550
515
  console.log(chalk.white(' Total Size: '), chalk.cyan(bucket.total_size));
551
516
  console.log(chalk.white(' Versioning: '), bucket.versioning_enabled ? chalk.green('Enabled') : chalk.gray('Disabled'));
552
517
  console.log(chalk.white(' Encryption: '), bucket.encryption_enabled ? chalk.green(`Enabled (${bucket.encryption_type})`) : chalk.gray('Disabled'));
553
-
518
+
554
519
  if (bucket.description) {
555
520
  console.log(chalk.white(' Description: '), chalk.gray(bucket.description));
556
521
  }
557
-
522
+
558
523
  console.log(chalk.white(' Created: '), chalk.gray(new Date(bucket.created_at).toLocaleString()));
559
524
  console.log('');
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
  }
@@ -586,7 +542,7 @@ async function bucketDelete(bucketName, options = {}) {
586
542
 
587
543
  console.log(chalk.white('Bucket: '), chalk.yellow(bucketName));
588
544
  console.log(chalk.white('Workspace:'), chalk.yellow(credentials.workspace));
589
-
545
+
590
546
  if (options.force) {
591
547
  console.log(chalk.yellow('\nāš ļø Force delete enabled - all files will be deleted'));
592
548
  }
@@ -594,9 +550,9 @@ async function bucketDelete(bucketName, options = {}) {
594
550
  const spinner = ora('Deleting bucket...').start();
595
551
 
596
552
  try {
597
- const endpoint = API_ENDPOINTS.STORAGE_BUCKET_LIST.replace('/list', `/${bucketName}`) +
598
- (options.force ? '?force=true' : '');
599
-
553
+ const endpoint = API_ENDPOINTS.STORAGE_BUCKET_LIST.replace('/list', `/${bucketName}`) +
554
+ (options.force ? '?force=true' : '');
555
+
600
556
  await makeApiRequest('DELETE', endpoint, credentials);
601
557
 
602
558
  spinner.succeed(chalk.green('āœ“ Bucket deleted successfully!'));
@@ -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
  }
@@ -642,7 +585,7 @@ async function fileList(bucketName, options = {}) {
642
585
  try {
643
586
  let endpoint = API_ENDPOINTS.STORAGE_FILE_LIST.replace('{bucketName}', bucketName);
644
587
  const params = [];
645
-
588
+
646
589
  if (options.folder) {
647
590
  params.push(`folder_path=${encodeURIComponent(options.folder)}`);
648
591
  }
@@ -652,7 +595,7 @@ async function fileList(bucketName, options = {}) {
652
595
  if (options.perPage) {
653
596
  params.push(`per_page=${options.perPage}`);
654
597
  }
655
-
598
+
656
599
  if (params.length > 0) {
657
600
  endpoint += '?' + params.join('&');
658
601
  }
@@ -681,12 +624,12 @@ async function fileList(bucketName, options = {}) {
681
624
  console.log(chalk.white('─'.repeat(120)));
682
625
 
683
626
  response.files.forEach(file => {
684
- const name = (file.file_name.length > 38
685
- ? file.file_name.substring(0, 35) + '...'
627
+ const name = (file.file_name.length > 38
628
+ ? file.file_name.substring(0, 35) + '...'
686
629
  : file.file_name).padEnd(40);
687
630
  const size = file.file_size.padEnd(15);
688
- const type = (file.mime_type.length > 18
689
- ? file.mime_type.substring(0, 15) + '...'
631
+ const type = (file.mime_type.length > 18
632
+ ? file.mime_type.substring(0, 15) + '...'
690
633
  : file.mime_type).padEnd(20);
691
634
  const downloads = String(file.download_count).padEnd(12);
692
635
  const uploaded = new Date(file.uploaded_at).toLocaleDateString();
@@ -701,28 +644,19 @@ async function fileList(bucketName, options = {}) {
701
644
  });
702
645
 
703
646
  console.log(chalk.white('─'.repeat(120)));
704
-
647
+
705
648
  if (response.pagination.last_page > 1) {
706
649
  console.log(chalk.gray(`Page ${response.pagination.current_page} of ${response.pagination.last_page}`));
707
650
  if (response.pagination.current_page < response.pagination.last_page) {
708
651
  console.log(chalk.cyan('Next page: '), chalk.yellow(`orcapt storage files ${bucketName} --page ${response.pagination.current_page + 1}`));
709
652
  }
710
653
  }
711
-
654
+
712
655
  console.log('');
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,143 +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('');
768
- process.exit(1);
769
- }
770
- }
771
-
772
- /**
773
- * Permission Add Command
774
- */
775
- async function permissionAdd(bucketName, options = {}) {
776
- console.log(chalk.cyan('\n============================================================'));
777
- console.log(chalk.cyan('šŸ” Adding Permission'));
778
- console.log(chalk.cyan('============================================================\n'));
779
-
780
- const credentials = requireAuth();
781
-
782
- console.log(chalk.white('Bucket: '), chalk.yellow(bucketName));
783
- console.log(chalk.white('Target Type: '), chalk.yellow(options.targetType || 'user'));
784
- console.log(chalk.white('Target ID: '), chalk.yellow(options.targetId || 'N/A'));
785
- console.log(chalk.white('Resource Type:'), chalk.yellow(options.resourceType || 'bucket'));
786
-
787
- const spinner = ora('Adding permission...').start();
788
-
789
- try {
790
- const requestBody = {
791
- target_type: options.targetType || 'user',
792
- target_id: options.targetId,
793
- resource_type: options.resourceType || 'bucket',
794
- resource_path: options.resourcePath || null,
795
- can_read: options.read || false,
796
- can_write: options.write || false,
797
- can_delete: options.delete || false,
798
- can_list: options.list || false,
799
- valid_until: options.validUntil || null,
800
- reason: options.reason || null
801
- };
802
-
803
- const endpoint = API_ENDPOINTS.STORAGE_PERMISSION_ADD
804
- .replace('{bucketName}', bucketName);
805
-
806
- const response = await makeApiRequest('POST', endpoint, credentials, requestBody);
807
-
808
- spinner.succeed(chalk.green('āœ“ Permission added successfully!'));
809
-
810
- console.log(chalk.cyan('\nšŸ” Permission Details:'));
811
- console.log(chalk.white(' ID: '), chalk.yellow(response.permission.id));
812
- console.log(chalk.white(' Target: '), chalk.yellow(`${response.permission.target_type}:${response.permission.target_id || 'all'}`));
813
- console.log(chalk.white(' Resource: '), chalk.yellow(`${response.permission.resource_type}:${response.permission.resource_path || 'all'}`));
814
- console.log(chalk.white(' Permissions: '), chalk.green(response.permission.permissions.join(', ')));
815
-
816
- if (response.permission.valid_until) {
817
- console.log(chalk.white(' Valid Until: '), chalk.gray(new Date(response.permission.valid_until).toLocaleString()));
818
- }
819
-
820
- console.log('');
821
-
822
- } catch (error) {
823
- spinner.fail(chalk.red('āœ— Failed to add permission'));
824
-
825
- if (error.response) {
826
- console.log(chalk.red(`\nāœ— ${error.response.message || 'Unknown error'}`));
827
- if (error.statusCode === 404) {
828
- console.log(chalk.yellow(' Bucket not found'));
829
- } else if (error.statusCode === 422) {
830
- console.log(chalk.yellow(' Invalid permission parameters'));
831
- }
832
- } else {
833
- console.log(chalk.red(`\nāœ— ${error.message}`));
834
- }
835
- console.log('');
836
- process.exit(1);
837
- }
838
- }
839
-
840
- /**
841
- * Permission List Command
842
- */
843
- async function permissionList(bucketName) {
844
- console.log(chalk.cyan('\n============================================================'));
845
- console.log(chalk.cyan('šŸ” Listing Permissions'));
846
- console.log(chalk.cyan('============================================================\n'));
847
-
848
- const credentials = requireAuth();
849
- console.log(chalk.white('Bucket:'), chalk.yellow(bucketName));
850
-
851
- const spinner = ora('Fetching permissions...').start();
852
-
853
- try {
854
- const endpoint = API_ENDPOINTS.STORAGE_PERMISSION_LIST
855
- .replace('{bucketName}', bucketName);
856
-
857
- const response = await makeApiRequest('GET', endpoint, credentials);
858
-
859
- spinner.succeed(chalk.green(`āœ“ Found ${response.count} permission(s)`));
860
-
861
- if (response.count === 0) {
862
- console.log(chalk.yellow('\nšŸ“­ No permissions found'));
863
- console.log(chalk.cyan('\nšŸ’” Add a permission:'));
864
- console.log(chalk.white(' '), chalk.yellow(`orcapt storage permission add ${bucketName} --target-type user --target-id USER_ID --read`));
865
- console.log('');
866
- return;
867
- }
868
-
869
- console.log('');
870
- response.permissions.forEach(perm => {
871
- const statusIcon = perm.is_valid ? 'āœ“' : 'āœ—';
872
- const statusColor = perm.is_valid ? chalk.green : chalk.red;
873
-
874
- console.log(statusColor(`${statusIcon} Permission #${perm.id}`));
875
- console.log(chalk.white(' Target: '), chalk.yellow(`${perm.target_type}:${perm.target_id || 'all'}`));
876
- console.log(chalk.white(' Resource: '), chalk.yellow(`${perm.resource_type}:${perm.resource_path || 'all'}`));
877
- console.log(chalk.white(' Actions: '), chalk.green(perm.permissions.join(', ')));
878
- console.log(chalk.white(' Status: '), statusColor(perm.status));
879
-
880
- if (perm.valid_until) {
881
- console.log(chalk.white(' Valid Until:'), chalk.gray(new Date(perm.valid_until).toLocaleString()));
882
- }
883
-
884
- console.log('');
885
- });
886
-
887
- } catch (error) {
888
- spinner.fail(chalk.red('āœ— Failed to list permissions'));
889
-
890
- if (error.response) {
891
- console.log(chalk.red(`\nāœ— ${error.response.message || 'Unknown error'}\n`));
892
- } else {
893
- console.log(chalk.red(`\nāœ— ${error.message}\n`));
894
- }
692
+ handleError(error, 'File deletion');
895
693
  process.exit(1);
896
694
  }
897
695
  }
@@ -904,8 +702,6 @@ module.exports = {
904
702
  fileUpload,
905
703
  fileDownload,
906
704
  fileList,
907
- fileDelete,
908
- permissionAdd,
909
- permissionList
705
+ fileDelete
910
706
  };
911
707
 
@@ -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
+