@orcapt/cli 1.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.
@@ -0,0 +1,736 @@
1
+ /**
2
+ * Orca Lambda Commands
3
+ * Deploy and manage Docker images on AWS Lambda
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const ora = require('ora');
8
+ const https = require('https');
9
+ const http = require('http');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const { getCredentials } = require('./login');
13
+ const { API_BASE_URL, API_ENDPOINTS } = require('../config');
14
+ const {
15
+ checkDockerInstalled,
16
+ checkDockerImage,
17
+ getImageSize,
18
+ pushImageToECR
19
+ } = require('../utils/docker-helper');
20
+
21
+ /**
22
+ * Make API request to Orca Deploy API
23
+ */
24
+ function makeApiRequest(method, endpoint, credentials, body = null) {
25
+ return new Promise((resolve, reject) => {
26
+ const url = new URL(endpoint, API_BASE_URL);
27
+ const isHttps = url.protocol === 'https:';
28
+ const httpModule = isHttps ? https : http;
29
+
30
+ const options = {
31
+ hostname: url.hostname,
32
+ port: url.port || (isHttps ? 443 : 80),
33
+ path: url.pathname,
34
+ method: method,
35
+ headers: {
36
+ 'x-workspace': credentials.workspace,
37
+ 'x-token': credentials.token,
38
+ 'Content-Type': 'application/json',
39
+ 'x-mode' : credentials.mode
40
+ }
41
+ };
42
+
43
+ const req = httpModule.request(options, (res) => {
44
+ let data = '';
45
+
46
+ res.on('data', (chunk) => {
47
+ data += chunk;
48
+ });
49
+
50
+ res.on('end', () => {
51
+ try {
52
+ const response = JSON.parse(data);
53
+ if (res.statusCode >= 200 && res.statusCode < 300) {
54
+ resolve(response);
55
+ } else {
56
+ reject({ statusCode: res.statusCode, response });
57
+ }
58
+ } catch (error) {
59
+ reject(new Error(`Invalid response: ${data}`));
60
+ }
61
+ });
62
+ });
63
+
64
+ req.on('error', (error) => {
65
+ reject(error);
66
+ });
67
+
68
+ if (body) {
69
+ req.write(JSON.stringify(body));
70
+ }
71
+
72
+ req.end();
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Check authentication
78
+ */
79
+ function requireAuth() {
80
+ const credentials = getCredentials();
81
+ if (!credentials) {
82
+ console.log(chalk.red('\nāœ— Not authenticated'));
83
+ console.log(chalk.cyan('Please run:'), chalk.yellow('orca login'), chalk.cyan('first\n'));
84
+ process.exit(1);
85
+ }
86
+ return credentials;
87
+ }
88
+
89
+ /**
90
+ * Lambda Deploy Command
91
+ */
92
+ async function lambdaDeploy(functionName, options = {}) {
93
+ console.log(chalk.cyan('\n============================================================'));
94
+ console.log(chalk.cyan('šŸš€ Deploying Lambda Function'));
95
+ console.log(chalk.cyan('============================================================\n'));
96
+
97
+ const credentials = requireAuth();
98
+
99
+ if (!options.image) {
100
+ console.log(chalk.red('āœ— Docker image is required'));
101
+ console.log(chalk.cyan('Usage:'), chalk.white('orca lambda deploy <function-name> --image <docker-image>\n'));
102
+ process.exit(1);
103
+ }
104
+
105
+ // Parse environment variables from array to object
106
+ const environmentVars = {};
107
+
108
+ // Read from .env file if provided
109
+ if (options.envFile) {
110
+ try {
111
+ const envFilePath = path.resolve(process.cwd(), options.envFile);
112
+ if (!fs.existsSync(envFilePath)) {
113
+ console.log(chalk.red(`āœ— Environment file not found: ${envFilePath}\n`));
114
+ process.exit(1);
115
+ }
116
+
117
+ const envFileContent = fs.readFileSync(envFilePath, 'utf8');
118
+ envFileContent.split('\n').forEach(line => {
119
+ line = line.trim();
120
+ // Skip empty lines and comments
121
+ if (!line || line.startsWith('#')) return;
122
+
123
+ const [key, ...valueParts] = line.split('=');
124
+ if (key && valueParts.length > 0) {
125
+ let value = valueParts.join('=').trim();
126
+ // Remove quotes if present
127
+ if ((value.startsWith('"') && value.endsWith('"')) ||
128
+ (value.startsWith("'") && value.endsWith("'"))) {
129
+ value = value.slice(1, -1);
130
+ }
131
+ environmentVars[key.trim()] = value;
132
+ }
133
+ });
134
+ } catch (error) {
135
+ console.log(chalk.red(`āœ— Failed to read .env file: ${error.message}\n`));
136
+ process.exit(1);
137
+ }
138
+ }
139
+
140
+ // Parse from --env flags (these override .env file)
141
+ if (options.env && Array.isArray(options.env)) {
142
+ options.env.forEach(envStr => {
143
+ const [key, ...valueParts] = envStr.split('=');
144
+ if (key && valueParts.length > 0) {
145
+ environmentVars[key.trim()] = valueParts.join('=').trim();
146
+ }
147
+ });
148
+ }
149
+
150
+ console.log(chalk.white('Function: '), chalk.yellow(functionName));
151
+ console.log(chalk.white('Image: '), chalk.yellow(options.image));
152
+ console.log(chalk.white('Memory: '), chalk.yellow(`${options.memory || 512} MB`));
153
+ console.log(chalk.white('Timeout: '), chalk.yellow(`${options.timeout || 30}s`));
154
+ console.log(chalk.white('Workspace: '), chalk.yellow(credentials.workspace));
155
+ if (Object.keys(environmentVars).length > 0) {
156
+ console.log(chalk.white('Env Vars:'));
157
+ Object.entries(environmentVars).forEach(([key, value]) => {
158
+ console.log(chalk.gray(' '), chalk.cyan(key) + chalk.gray('=') + chalk.yellow(value));
159
+ });
160
+ }
161
+ console.log();
162
+
163
+ try {
164
+ // Step 1: Check if Docker is installed
165
+ let spinner = ora('Step 1/9: Checking Docker...').start();
166
+ await checkDockerInstalled();
167
+ spinner.succeed(chalk.green('āœ“ Docker is installed and running'));
168
+
169
+ // Step 2: Check if image exists locally
170
+ spinner = ora('Step 2/9: Checking Docker image...').start();
171
+ const imageExists = await checkDockerImage(options.image);
172
+ if (!imageExists) {
173
+ spinner.fail(chalk.red('āœ— Docker image not found locally'));
174
+ console.log(chalk.yellow('\nPlease build the image first:'));
175
+ console.log(chalk.white(' docker build -t'), chalk.cyan(options.image), chalk.white('.'));
176
+ console.log();
177
+ process.exit(1);
178
+ }
179
+ const imageSize = await getImageSize(options.image);
180
+ spinner.succeed(chalk.green(`āœ“ Found image '${options.image}' (${imageSize})`));
181
+
182
+ // Step 3: Request ECR credentials from API
183
+ spinner = ora('Step 3/9: Requesting ECR credentials...').start();
184
+
185
+ let deploymentRequest;
186
+ try {
187
+ deploymentRequest = await makeApiRequest('POST', API_ENDPOINTS.LAMBDA_DEPLOY, credentials, {
188
+ function_name: functionName,
189
+ image_tag: options.image,
190
+ memory_mb: options.memory || 512,
191
+ timeout_seconds: options.timeout || 30,
192
+ environment_vars: environmentVars
193
+ });
194
+ } catch (error) {
195
+ 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
+
207
+ console.log();
208
+ console.log(chalk.yellow('šŸ’” Troubleshooting tips:'));
209
+ console.log(chalk.white(' 1. Check if backend is running'));
210
+ console.log(chalk.white(' 2. Verify your authentication: orca whoami'));
211
+ console.log(chalk.white(' 3. Check backend logs for errors'));
212
+ console.log(chalk.white(' 4. Ensure AWS credentials are configured in backend .env'));
213
+ console.log();
214
+ process.exit(1);
215
+ }
216
+
217
+ if (!deploymentRequest.ecr_url || !deploymentRequest.repository_uri) {
218
+ spinner.fail(chalk.red('āœ— Invalid response from API'));
219
+ console.log(chalk.yellow('\nAPI Response:'), deploymentRequest);
220
+ process.exit(1);
221
+ }
222
+
223
+ spinner.succeed(chalk.green('āœ“ ECR credentials received'));
224
+ console.log(chalk.gray(` Repository: ${deploymentRequest.repository_uri}`));
225
+
226
+ // Step 4-6: Push image to ECR (handled by helper)
227
+ const remoteImage = await pushImageToECR(
228
+ options.image,
229
+ deploymentRequest.ecr_url,
230
+ deploymentRequest.ecr_username,
231
+ deploymentRequest.ecr_password,
232
+ deploymentRequest.repository_uri
233
+ );
234
+
235
+ // Step 7: Notify API that image is pushed
236
+ spinner = ora('Step 7/9: Creating Lambda function...').start();
237
+ const confirmResponse = await makeApiRequest(
238
+ 'POST',
239
+ `${API_ENDPOINTS.LAMBDA_DEPLOY}/confirm`,
240
+ credentials,
241
+ {
242
+ deployment_id: deploymentRequest.deployment_id,
243
+ function_name: functionName,
244
+ image_uri: remoteImage
245
+ }
246
+ );
247
+ spinner.succeed(chalk.green('āœ“ Lambda function created'));
248
+
249
+ // Step 8: Create API Gateway (if configured)
250
+ if (confirmResponse.invoke_url) {
251
+ spinner = ora('Step 8/9: Configuring API Gateway...').start();
252
+ spinner.succeed(chalk.green('āœ“ API Gateway configured'));
253
+ } else {
254
+ console.log(chalk.gray(' ā“˜ API Gateway not configured'));
255
+ }
256
+
257
+ // Step 9: Save to database
258
+ spinner = ora('Step 9/9: Saving deployment info...').start();
259
+ spinner.succeed(chalk.green('āœ“ Deployment completed'));
260
+
261
+ // Success message
262
+ console.log(chalk.cyan('\n============================================================'));
263
+ console.log(chalk.green('āœ“ Function deployed successfully!'));
264
+ console.log(chalk.cyan('============================================================\n'));
265
+
266
+ console.log(chalk.white('Function Details:'));
267
+ console.log(chalk.white(' Name: '), chalk.yellow(functionName));
268
+ console.log(chalk.white(' Image: '), chalk.yellow(remoteImage));
269
+ console.log(chalk.white(' Region: '), chalk.yellow(confirmResponse.region || 'us-east-1'));
270
+ console.log(chalk.white(' Memory: '), chalk.yellow(`${options.memory || 512} MB`));
271
+ console.log(chalk.white(' Timeout: '), chalk.yellow(`${options.timeout || 30}s`));
272
+ console.log(chalk.white(' Status: '), chalk.green('Active'));
273
+
274
+ if (confirmResponse.invoke_url) {
275
+ console.log(chalk.cyan('\nInvoke URL:'));
276
+ console.log(chalk.white(' '), chalk.yellow(confirmResponse.invoke_url));
277
+ console.log(chalk.cyan('\nTry it:'));
278
+ console.log(chalk.white(' curl'), chalk.cyan(confirmResponse.invoke_url));
279
+ console.log(chalk.white(' orca lambda invoke'), chalk.cyan(functionName));
280
+ }
281
+
282
+ if (confirmResponse.sqs_queue_url) {
283
+ console.log(chalk.cyan('\nSQS Queue (for async processing):'));
284
+ console.log(chalk.white(' '), chalk.yellow(confirmResponse.sqs_queue_url));
285
+ console.log(chalk.gray(' Note: SQS_QUEUE_URL is automatically set as an env variable'));
286
+ }
287
+
288
+ console.log(chalk.cyan('\n============================================================\n'));
289
+
290
+ } catch (error) {
291
+ 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('orca 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 Orca 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
+
308
+ process.exit(1);
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Lambda List Command
314
+ */
315
+ async function lambdaList() {
316
+ console.log(chalk.cyan('\n============================================================'));
317
+ console.log(chalk.cyan('šŸ“‹ Listing Lambda Functions'));
318
+ console.log(chalk.cyan('============================================================\n'));
319
+
320
+ const credentials = requireAuth();
321
+ console.log(chalk.white('Workspace:'), chalk.yellow(credentials.workspace));
322
+
323
+ const spinner = ora('Fetching functions...').start();
324
+
325
+ try {
326
+ // Call backend API to get list of functions
327
+ const response = await makeApiRequest('GET', API_ENDPOINTS.LAMBDA_LIST, credentials);
328
+
329
+ spinner.succeed(chalk.green('Functions retrieved'));
330
+
331
+ console.log(chalk.cyan('\n============================================================'));
332
+
333
+ if (response.count === 0) {
334
+ console.log(chalk.yellow('No Lambda functions found'));
335
+ console.log(chalk.cyan('\nCreate one with:'), chalk.white('orca lambda deploy <function-name> --image <docker-image>'));
336
+ } else {
337
+ console.log(chalk.green(`āœ“ Found ${response.count} function${response.count > 1 ? 's' : ''}`));
338
+ console.log(chalk.cyan('============================================================\n'));
339
+
340
+ response.functions.forEach((func, index) => {
341
+ console.log(chalk.white(` ${index + 1}. ${func.function_name}`));
342
+ console.log(chalk.gray(` Image: ${func.image_uri || 'N/A'}`));
343
+ console.log(chalk.gray(` Status: ${func.status}`));
344
+ console.log(chalk.gray(` Memory: ${func.memory_mb} MB`));
345
+ console.log(chalk.gray(` Timeout: ${func.timeout_seconds}s`));
346
+ if (func.invoke_url) {
347
+ console.log(chalk.gray(` URL: ${func.invoke_url}`));
348
+ }
349
+ if (func.sqs_queue_url) {
350
+ console.log(chalk.gray(` SQS: ${func.sqs_queue_url}`));
351
+ }
352
+ if (func.deployed_at) {
353
+ console.log(chalk.gray(` Deployed: ${new Date(func.deployed_at).toLocaleString()}`));
354
+ }
355
+ console.log();
356
+ });
357
+ }
358
+
359
+ console.log(chalk.cyan('============================================================\n'));
360
+
361
+ } catch (error) {
362
+ 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('orca 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 Orca 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
+
378
+ process.exit(1);
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Lambda Invoke Command
384
+ */
385
+ async function lambdaInvoke(functionName, options = {}) {
386
+ console.log(chalk.cyan('\n============================================================'));
387
+ console.log(chalk.cyan('ā–¶ļø Invoking Lambda Function'));
388
+ console.log(chalk.cyan('============================================================\n'));
389
+
390
+ const credentials = requireAuth();
391
+
392
+ console.log(chalk.white('Function: '), chalk.yellow(functionName));
393
+
394
+ // Parse payload if provided
395
+ let payload = {};
396
+ if (options.payload) {
397
+ try {
398
+ payload = JSON.parse(options.payload);
399
+ console.log(chalk.white('Payload: '), chalk.yellow(JSON.stringify(payload)));
400
+ } catch (e) {
401
+ console.log(chalk.red('\nāœ— Invalid JSON payload'));
402
+ console.log(chalk.yellow('Please provide valid JSON:'), chalk.white('--payload \'{"key": "value"}\'\n'));
403
+ process.exit(1);
404
+ }
405
+ }
406
+
407
+ const spinner = ora('Invoking function...').start();
408
+
409
+ try {
410
+ // Call backend API to invoke Lambda function
411
+ const startTime = Date.now();
412
+ const endpoint = API_ENDPOINTS.LAMBDA_INVOKE.replace('{functionName}', functionName);
413
+ const response = await makeApiRequest(
414
+ 'POST',
415
+ endpoint,
416
+ credentials,
417
+ { payload }
418
+ );
419
+
420
+ const duration = Date.now() - startTime;
421
+
422
+ spinner.succeed(chalk.green('Function invoked successfully'));
423
+
424
+ console.log(chalk.cyan('\n============================================================'));
425
+ console.log(chalk.green('āœ“ Invocation Result'));
426
+ console.log(chalk.cyan('============================================================\n'));
427
+
428
+ console.log(chalk.white('Status: '), chalk.green(response.statusCode || 200));
429
+ console.log(chalk.white('Duration: '), chalk.yellow(`${duration}ms`));
430
+
431
+ if (response.executedVersion) {
432
+ console.log(chalk.white('Version: '), chalk.yellow(response.executedVersion));
433
+ }
434
+
435
+ console.log(chalk.cyan('\nResponse:'));
436
+ console.log(chalk.white(JSON.stringify(response.response || response, null, 2)));
437
+
438
+ console.log(chalk.cyan('\n============================================================\n'));
439
+
440
+ } catch (error) {
441
+ 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('orca 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 Orca 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
+
463
+ process.exit(1);
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Lambda Logs Command
469
+ */
470
+ async function lambdaLogs(functionName, options = {}) {
471
+ console.log(chalk.cyan('\n============================================================'));
472
+ console.log(chalk.cyan('šŸ“œ Lambda Function Logs'));
473
+ console.log(chalk.cyan('============================================================\n'));
474
+
475
+ const credentials = requireAuth();
476
+
477
+ console.log(chalk.white('Function: '), chalk.yellow(functionName));
478
+
479
+ if (options.since) {
480
+ console.log(chalk.white('Since: '), chalk.yellow(options.since));
481
+ }
482
+
483
+ if (options.tail) {
484
+ console.log(chalk.white('Mode: '), chalk.yellow('Live streaming'));
485
+ }
486
+
487
+ const spinner = ora('Fetching logs...').start();
488
+ const endpoint = API_ENDPOINTS.LAMBDA_LOGS.replace('{functionName}', functionName);
489
+ try {
490
+ // Call backend API to get logs
491
+ const response = await makeApiRequest(
492
+ 'GET',
493
+ endpoint,
494
+ credentials
495
+ );
496
+
497
+ spinner.succeed(chalk.green('Logs retrieved'));
498
+
499
+ console.log(chalk.cyan('\n============================================================'));
500
+
501
+ if (!response.logs || response.logs.length === 0) {
502
+ console.log(chalk.yellow('No logs found'));
503
+ console.log(chalk.cyan('\nTry invoking the function first:'));
504
+ console.log(chalk.white(' orca lambda invoke'), chalk.cyan(functionName));
505
+ } else {
506
+ console.log(chalk.green(`āœ“ Found ${response.logs.length} log entries`));
507
+ console.log(chalk.cyan('============================================================\n'));
508
+
509
+ response.logs.forEach(log => {
510
+ const timestamp = new Date(log.timestamp).toLocaleString();
511
+ const level = log.level.toUpperCase();
512
+
513
+ let levelColor = chalk.white;
514
+ if (level === 'ERROR') levelColor = chalk.red;
515
+ else if (level === 'WARNING') levelColor = chalk.yellow;
516
+ else if (level === 'INFO') levelColor = chalk.blue;
517
+
518
+ console.log(
519
+ chalk.gray(`[${timestamp}]`),
520
+ levelColor(`${level}:`),
521
+ chalk.white(log.message)
522
+ );
523
+ });
524
+ }
525
+
526
+ console.log(chalk.cyan('\n============================================================'));
527
+
528
+ if (options.tail) {
529
+ console.log(chalk.yellow('\n⚠ Live streaming not yet supported'));
530
+ console.log(chalk.cyan('Showing latest logs only\n'));
531
+ } else {
532
+ console.log();
533
+ }
534
+
535
+ } catch (error) {
536
+ 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('orca 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 Orca 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
+
552
+ process.exit(1);
553
+ }
554
+ }
555
+
556
+ /**
557
+ * Lambda Remove Command
558
+ */
559
+ async function lambdaRemove(functionName) {
560
+ console.log(chalk.cyan('\n============================================================'));
561
+ console.log(chalk.cyan('šŸ—‘ļø Removing Lambda Function'));
562
+ console.log(chalk.cyan('============================================================\n'));
563
+
564
+ const credentials = requireAuth();
565
+
566
+ console.log(chalk.white('Function: '), chalk.yellow(functionName));
567
+ console.log(chalk.white('Workspace: '), chalk.yellow(credentials.workspace));
568
+
569
+ // Confirmation prompt
570
+ console.log(chalk.red('\nāš ļø WARNING: This will permanently delete the function!'));
571
+ console.log(chalk.yellow('The Lambda function and all its configurations will be removed.'));
572
+ console.log(chalk.yellow('ECR repository will be kept for potential rollback.\n'));
573
+
574
+ const spinner = ora('Removing function...').start();
575
+
576
+ try {
577
+ // Call backend API to delete Lambda function
578
+ const response = await makeApiRequest(
579
+ 'DELETE',
580
+ `${API_ENDPOINTS.LAMBDA_DELETE}/${functionName}`,
581
+ credentials
582
+ );
583
+
584
+ spinner.succeed(chalk.green('Function removed successfully'));
585
+
586
+ console.log(chalk.cyan('\n============================================================'));
587
+ console.log(chalk.green('āœ“ Function Removed'));
588
+ console.log(chalk.cyan('============================================================'));
589
+ console.log(chalk.white('Function: '), chalk.yellow(functionName));
590
+ console.log(chalk.white('Workspace: '), chalk.yellow(credentials.workspace));
591
+ console.log(chalk.cyan('============================================================\n'));
592
+
593
+ console.log(chalk.gray('Note: ECR repository has been kept for potential rollback.'));
594
+ console.log(chalk.gray('You can manually clean it up if needed.\n'));
595
+
596
+ } catch (error) {
597
+ 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('orca 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 Orca 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
+
616
+ process.exit(1);
617
+ }
618
+ }
619
+
620
+ /**
621
+ * Lambda Info Command - Get function details
622
+ */
623
+ async function lambdaInfo(functionName) {
624
+ console.log(chalk.cyan('\n============================================================'));
625
+ console.log(chalk.cyan('šŸ” Lambda Function Details'));
626
+ console.log(chalk.cyan('============================================================\n'));
627
+
628
+ const credentials = requireAuth();
629
+
630
+ const spinner = ora('Fetching function details...').start();
631
+
632
+ try {
633
+ // Call backend API to get function details
634
+ const response = await makeApiRequest(
635
+ 'GET',
636
+ `${API_ENDPOINTS.LAMBDA_INFO}/${functionName}`,
637
+ credentials
638
+ );
639
+
640
+ spinner.succeed(chalk.green('Function details retrieved'));
641
+
642
+ console.log(chalk.cyan('\n============================================================'));
643
+ console.log(chalk.green('āœ“ Function Information'));
644
+ console.log(chalk.cyan('============================================================\n'));
645
+
646
+ console.log(chalk.white('Name: '), chalk.yellow(response.function_name));
647
+ console.log(chalk.white('Status: '),
648
+ response.status === 'active' ? chalk.green(response.status) : chalk.yellow(response.status)
649
+ );
650
+ console.log(chalk.white('Image: '), chalk.yellow(response.image_uri || 'N/A'));
651
+ console.log(chalk.white('Region: '), chalk.yellow(response.aws_region || 'us-east-1'));
652
+ console.log(chalk.white('Memory: '), chalk.yellow(`${response.memory_mb} MB`));
653
+ console.log(chalk.white('Timeout: '), chalk.yellow(`${response.timeout_seconds}s`));
654
+
655
+ if (response.function_arn) {
656
+ console.log(chalk.white('Function ARN: '), chalk.gray(response.function_arn));
657
+ }
658
+
659
+ if (response.created_at) {
660
+ console.log(chalk.white('Created: '), chalk.yellow(new Date(response.created_at).toLocaleString()));
661
+ }
662
+
663
+ if (response.deployed_at) {
664
+ console.log(chalk.white('Last Deploy: '), chalk.yellow(new Date(response.deployed_at).toLocaleString()));
665
+ }
666
+
667
+ // Environment Variables
668
+ if (response.environment_vars && Object.keys(response.environment_vars).length > 0) {
669
+ console.log(chalk.cyan('\nEnvironment Variables:'));
670
+ Object.entries(response.environment_vars).forEach(([key, value]) => {
671
+ // Hide sensitive values
672
+ const displayValue = key.toLowerCase().includes('password') ||
673
+ key.toLowerCase().includes('secret') ||
674
+ key.toLowerCase().includes('key')
675
+ ? '***hidden***' : value;
676
+ console.log(chalk.white(` ${key}:`.padEnd(20)), chalk.yellow(displayValue));
677
+ });
678
+ }
679
+
680
+ // Invoke URL
681
+ if (response.invoke_url) {
682
+ console.log(chalk.cyan('\nInvoke URL:'));
683
+ console.log(chalk.white(' '), chalk.yellow(response.invoke_url));
684
+ }
685
+
686
+ // Metrics (if available)
687
+ if (response.metrics) {
688
+ console.log(chalk.cyan('\nMetrics (Last 24h):'));
689
+ console.log(chalk.white(' Invocations: '), chalk.yellow(response.metrics.invocations || 0));
690
+ console.log(chalk.white(' Errors: '), chalk.yellow(response.metrics.errors || 0));
691
+ if (response.metrics.avg_duration) {
692
+ console.log(chalk.white(' Avg Duration:'), chalk.yellow(`${response.metrics.avg_duration}ms`));
693
+ }
694
+ if (response.metrics.total_cost) {
695
+ console.log(chalk.white(' Total Cost: '), chalk.yellow(`$${response.metrics.total_cost}`));
696
+ }
697
+ }
698
+
699
+ console.log(chalk.cyan('\n============================================================'));
700
+ console.log(chalk.gray('\nCommands:'));
701
+ console.log(chalk.white(' Invoke: '), chalk.cyan(`orca lambda invoke ${functionName}`));
702
+ console.log(chalk.white(' Logs: '), chalk.cyan(`orca lambda logs ${functionName}`));
703
+ console.log(chalk.white(' Update: '), chalk.cyan(`orca lambda update ${functionName} --image new-image:tag`));
704
+ console.log(chalk.white(' Remove: '), chalk.cyan(`orca lambda remove ${functionName}`));
705
+ console.log();
706
+
707
+ } catch (error) {
708
+ 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('orca 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 Orca 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
+
724
+ process.exit(1);
725
+ }
726
+ }
727
+
728
+ module.exports = {
729
+ lambdaDeploy,
730
+ lambdaList,
731
+ lambdaInvoke,
732
+ lambdaLogs,
733
+ lambdaRemove,
734
+ lambdaInfo
735
+ };
736
+