raqeb-cli 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/interactive.js +315 -10
- package/package.json +1 -1
package/lib/interactive.js
CHANGED
|
@@ -2,15 +2,42 @@ const readline = require('readline');
|
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
4
|
const Table = require('cli-table3');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
5
8
|
|
|
6
9
|
class InteractiveShell {
|
|
7
10
|
constructor(apiClient) {
|
|
8
11
|
this.client = apiClient;
|
|
9
12
|
this.history = [];
|
|
10
13
|
this.rl = null;
|
|
14
|
+
this.config = this.loadConfig();
|
|
15
|
+
this.userInfo = null;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
loadConfig() {
|
|
19
|
+
try {
|
|
20
|
+
const configPath = path.join(os.homedir(), '.raqeb', 'config.json');
|
|
21
|
+
if (fs.existsSync(configPath)) {
|
|
22
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
// Config not found or invalid
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async fetchUserInfo() {
|
|
31
|
+
try {
|
|
32
|
+
const response = await this.client.get('/auth/me');
|
|
33
|
+
this.userInfo = response.data;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
this.userInfo = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async start() {
|
|
40
|
+
await this.fetchUserInfo();
|
|
14
41
|
this.showWelcome();
|
|
15
42
|
this.setupReadline();
|
|
16
43
|
this.rl.prompt();
|
|
@@ -55,10 +82,31 @@ class InteractiveShell {
|
|
|
55
82
|
}
|
|
56
83
|
|
|
57
84
|
showWelcome() {
|
|
58
|
-
console.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
console.log(chalk.cyan(
|
|
85
|
+
console.clear();
|
|
86
|
+
|
|
87
|
+
// ASCII Logo
|
|
88
|
+
console.log(chalk.cyan.bold(`
|
|
89
|
+
██████╗ ██████╗ ██████╗ ███████╗██████╗
|
|
90
|
+
██╔══██╗██╔═══██╗██╔═══██╗██╔════╝██╔══██╗
|
|
91
|
+
██████╔╝███████║██║ ██║█████╗ ██████╔╝
|
|
92
|
+
██╔══██╗██╔══██║██║▄▄ ██║██╔══╝ ██╔══██╗
|
|
93
|
+
██║ ██║██║ ██║╚██████╔╝███████╗██████╔╝
|
|
94
|
+
╚═╝ ╚═╝╚═╝ ╚═╝ ╚══▀▀═╝ ╚══════╝╚═════╝
|
|
95
|
+
`));
|
|
96
|
+
|
|
97
|
+
console.log(chalk.cyan('╔═══════════════════════════════════════════════════════════╗'));
|
|
98
|
+
console.log(chalk.cyan('║ Raqeb CLI - Interactive Mode v1.3.0 ║'));
|
|
99
|
+
console.log(chalk.cyan('╚═══════════════════════════════════════════════════════════╝'));
|
|
100
|
+
|
|
101
|
+
// Show user info if available
|
|
102
|
+
if (this.userInfo) {
|
|
103
|
+
console.log(chalk.gray('\n📊 Session Information:'));
|
|
104
|
+
console.log(chalk.white(` User: ${chalk.green(this.userInfo.email || 'Unknown')}`));
|
|
105
|
+
console.log(chalk.white(` Tenant: ${chalk.green(this.userInfo.tenant_name || this.userInfo.tenant_id || 'Unknown')}`));
|
|
106
|
+
console.log(chalk.white(` Role: ${chalk.yellow(this.userInfo.role || 'user')}`));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(chalk.cyan('\n💡 Type / to see all commands, or /help for help\n'));
|
|
62
110
|
}
|
|
63
111
|
|
|
64
112
|
async handleCommand(input) {
|
|
@@ -319,7 +367,34 @@ class InteractiveShell {
|
|
|
319
367
|
}
|
|
320
368
|
|
|
321
369
|
async listSecrets() {
|
|
322
|
-
|
|
370
|
+
try {
|
|
371
|
+
const response = await this.client.get('/service-accounts/secrets');
|
|
372
|
+
const secrets = response.data;
|
|
373
|
+
|
|
374
|
+
if (!secrets || secrets.length === 0) {
|
|
375
|
+
console.log(chalk.yellow('\nNo secrets found\n'));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
console.log(chalk.bold('\n🔐 Secrets:\n'));
|
|
380
|
+
const table = new Table({
|
|
381
|
+
head: [chalk.cyan('Name'), chalk.cyan('Created'), chalk.cyan('Updated')],
|
|
382
|
+
style: { head: ['cyan'] }
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
secrets.forEach(secret => {
|
|
386
|
+
table.push([
|
|
387
|
+
secret.name,
|
|
388
|
+
new Date(secret.created_at).toLocaleString(),
|
|
389
|
+
secret.updated_at ? new Date(secret.updated_at).toLocaleString() : 'Never'
|
|
390
|
+
]);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
console.log(table.toString());
|
|
394
|
+
console.log(chalk.gray(`\nTotal: ${secrets.length} secret(s)\n`));
|
|
395
|
+
} catch (error) {
|
|
396
|
+
throw new Error(error.response?.data?.detail || error.message);
|
|
397
|
+
}
|
|
323
398
|
}
|
|
324
399
|
|
|
325
400
|
async getSecret(secretId) {
|
|
@@ -344,19 +419,249 @@ class InteractiveShell {
|
|
|
344
419
|
}
|
|
345
420
|
|
|
346
421
|
async setSecret() {
|
|
347
|
-
|
|
422
|
+
try {
|
|
423
|
+
const answers = await inquirer.prompt([
|
|
424
|
+
{
|
|
425
|
+
type: 'input',
|
|
426
|
+
name: 'name',
|
|
427
|
+
message: 'Secret name:',
|
|
428
|
+
validate: (value) => value.trim() !== '' || 'Name is required'
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
type: 'password',
|
|
432
|
+
name: 'value',
|
|
433
|
+
message: 'Secret value:',
|
|
434
|
+
mask: '*',
|
|
435
|
+
validate: (value) => value.trim() !== '' || 'Value is required'
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
type: 'input',
|
|
439
|
+
name: 'description',
|
|
440
|
+
message: 'Description (optional):',
|
|
441
|
+
default: ''
|
|
442
|
+
}
|
|
443
|
+
]);
|
|
444
|
+
|
|
445
|
+
await this.client.post('/service-accounts/secrets', {
|
|
446
|
+
name: answers.name,
|
|
447
|
+
value: answers.value,
|
|
448
|
+
description: answers.description || undefined
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
console.log(chalk.green(`\n✓ Secret '${answers.name}' created successfully\n`));
|
|
452
|
+
} catch (error) {
|
|
453
|
+
throw new Error(error.response?.data?.detail || error.message);
|
|
454
|
+
}
|
|
348
455
|
}
|
|
349
456
|
|
|
350
457
|
async deleteSecret(secretName) {
|
|
351
|
-
|
|
458
|
+
if (!secretName) {
|
|
459
|
+
console.log(chalk.red('\n❌ Secret name required'));
|
|
460
|
+
console.log(chalk.gray('Usage: /secrets delete <name>\n'));
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
const confirm = await inquirer.prompt([{
|
|
466
|
+
type: 'confirm',
|
|
467
|
+
name: 'confirmed',
|
|
468
|
+
message: `Delete secret '${secretName}'?`,
|
|
469
|
+
default: false
|
|
470
|
+
}]);
|
|
471
|
+
|
|
472
|
+
if (!confirm.confirmed) {
|
|
473
|
+
console.log(chalk.gray('\nCancelled\n'));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
await this.client.delete(`/service-accounts/secrets/${secretName}`);
|
|
478
|
+
console.log(chalk.green(`\n✓ Secret '${secretName}' deleted successfully\n`));
|
|
479
|
+
} catch (error) {
|
|
480
|
+
throw new Error(error.response?.data?.detail || error.message);
|
|
481
|
+
}
|
|
352
482
|
}
|
|
353
483
|
|
|
354
484
|
async handleKeys(input) {
|
|
355
|
-
|
|
485
|
+
const parts = input.split(' ');
|
|
486
|
+
const subcommand = parts[1];
|
|
487
|
+
|
|
488
|
+
if (!subcommand || subcommand === 'help') {
|
|
489
|
+
console.log(chalk.bold('\n🔑 API Keys Commands:\n'));
|
|
490
|
+
console.log(' /keys list List all API keys');
|
|
491
|
+
console.log(' /keys create Create new API key');
|
|
492
|
+
console.log(' /keys delete <id> Delete API key');
|
|
493
|
+
console.log();
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
switch (subcommand) {
|
|
498
|
+
case 'list':
|
|
499
|
+
await this.listKeys();
|
|
500
|
+
break;
|
|
501
|
+
case 'create':
|
|
502
|
+
await this.createKey();
|
|
503
|
+
break;
|
|
504
|
+
case 'delete':
|
|
505
|
+
await this.deleteKey(parts[2]);
|
|
506
|
+
break;
|
|
507
|
+
default:
|
|
508
|
+
console.log(chalk.red(`Unknown keys command: ${subcommand}`));
|
|
509
|
+
console.log(chalk.gray('Type /keys for available commands'));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async listKeys() {
|
|
514
|
+
try {
|
|
515
|
+
const response = await this.client.get('/service-accounts/api-keys');
|
|
516
|
+
const keys = response.data;
|
|
517
|
+
|
|
518
|
+
if (!keys || keys.length === 0) {
|
|
519
|
+
console.log(chalk.yellow('\nNo API keys found\n'));
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
console.log(chalk.bold('\n🔑 API Keys:\n'));
|
|
524
|
+
const table = new Table({
|
|
525
|
+
head: [chalk.cyan('ID'), chalk.cyan('Name'), chalk.cyan('Prefix'), chalk.cyan('Created'), chalk.cyan('Last Used')],
|
|
526
|
+
style: { head: ['cyan'] }
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
keys.forEach(key => {
|
|
530
|
+
table.push([
|
|
531
|
+
key.id.substring(0, 8) + '...',
|
|
532
|
+
key.name || 'Unnamed',
|
|
533
|
+
key.api_key_prefix || 'N/A',
|
|
534
|
+
new Date(key.created_at).toLocaleDateString(),
|
|
535
|
+
key.last_used_at ? new Date(key.last_used_at).toLocaleDateString() : 'Never'
|
|
536
|
+
]);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
console.log(table.toString());
|
|
540
|
+
console.log(chalk.gray(`\nTotal: ${keys.length} key(s)\n`));
|
|
541
|
+
} catch (error) {
|
|
542
|
+
throw new Error(error.response?.data?.detail || error.message);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async createKey() {
|
|
547
|
+
try {
|
|
548
|
+
const answers = await inquirer.prompt([
|
|
549
|
+
{
|
|
550
|
+
type: 'input',
|
|
551
|
+
name: 'name',
|
|
552
|
+
message: 'Key name:',
|
|
553
|
+
validate: (value) => value.trim() !== '' || 'Name is required'
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
type: 'checkbox',
|
|
557
|
+
name: 'scopes',
|
|
558
|
+
message: 'Select scopes:',
|
|
559
|
+
choices: [
|
|
560
|
+
{ name: 'Read Secrets', value: 'secrets:read', checked: true },
|
|
561
|
+
{ name: 'Write Secrets', value: 'secrets:write' },
|
|
562
|
+
{ name: 'Read Databases', value: 'databases:read', checked: true },
|
|
563
|
+
{ name: 'Write Databases', value: 'databases:write' },
|
|
564
|
+
{ name: 'Manage API Keys', value: 'api-keys:manage' }
|
|
565
|
+
]
|
|
566
|
+
}
|
|
567
|
+
]);
|
|
568
|
+
|
|
569
|
+
const response = await this.client.post('/service-accounts/api-keys', {
|
|
570
|
+
name: answers.name,
|
|
571
|
+
scopes: answers.scopes
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
console.log(chalk.green('\n✓ API Key Created!\n'));
|
|
575
|
+
console.log(chalk.yellow('⚠️ Save this key now - it won\'t be shown again:\n'));
|
|
576
|
+
console.log(chalk.white.bold(response.data.api_key));
|
|
577
|
+
console.log();
|
|
578
|
+
} catch (error) {
|
|
579
|
+
throw new Error(error.response?.data?.detail || error.message);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async deleteKey(keyId) {
|
|
584
|
+
if (!keyId) {
|
|
585
|
+
console.log(chalk.red('\n❌ Key ID required'));
|
|
586
|
+
console.log(chalk.gray('Usage: /keys delete <id>\n'));
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
const confirm = await inquirer.prompt([{
|
|
592
|
+
type: 'confirm',
|
|
593
|
+
name: 'confirmed',
|
|
594
|
+
message: `Delete API key '${keyId}'?`,
|
|
595
|
+
default: false
|
|
596
|
+
}]);
|
|
597
|
+
|
|
598
|
+
if (!confirm.confirmed) {
|
|
599
|
+
console.log(chalk.gray('\nCancelled\n'));
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
await this.client.delete(`/service-accounts/api-keys/${keyId}`);
|
|
604
|
+
console.log(chalk.green(`\n✓ API key deleted successfully\n`));
|
|
605
|
+
} catch (error) {
|
|
606
|
+
throw new Error(error.response?.data?.detail || error.message);
|
|
607
|
+
}
|
|
356
608
|
}
|
|
357
609
|
|
|
358
610
|
async handleAudit(input) {
|
|
359
|
-
|
|
611
|
+
const parts = input.split(' ');
|
|
612
|
+
const subcommand = parts[1];
|
|
613
|
+
|
|
614
|
+
if (!subcommand || subcommand === 'help') {
|
|
615
|
+
console.log(chalk.bold('\n📋 Audit Commands:\n'));
|
|
616
|
+
console.log(' /audit logs View recent audit logs');
|
|
617
|
+
console.log(' /audit logs <limit> View last N logs');
|
|
618
|
+
console.log();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
switch (subcommand) {
|
|
623
|
+
case 'logs':
|
|
624
|
+
await this.showAuditLogs(parts[2] ? parseInt(parts[2]) : 10);
|
|
625
|
+
break;
|
|
626
|
+
default:
|
|
627
|
+
console.log(chalk.red(`Unknown audit command: ${subcommand}`));
|
|
628
|
+
console.log(chalk.gray('Type /audit for available commands'));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
async showAuditLogs(limit = 10) {
|
|
633
|
+
try {
|
|
634
|
+
const response = await this.client.get('/audit/logs', {
|
|
635
|
+
params: { limit }
|
|
636
|
+
});
|
|
637
|
+
const logs = response.data;
|
|
638
|
+
|
|
639
|
+
if (!logs || logs.length === 0) {
|
|
640
|
+
console.log(chalk.yellow('\nNo audit logs found\n'));
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
console.log(chalk.bold(`\n📋 Recent Audit Logs (Last ${limit}):\n`));
|
|
645
|
+
const table = new Table({
|
|
646
|
+
head: [chalk.cyan('Time'), chalk.cyan('User'), chalk.cyan('Action'), chalk.cyan('Resource')],
|
|
647
|
+
style: { head: ['cyan'] },
|
|
648
|
+
colWidths: [20, 25, 20, 30]
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
logs.forEach(log => {
|
|
652
|
+
table.push([
|
|
653
|
+
new Date(log.timestamp).toLocaleString(),
|
|
654
|
+
log.user_email || 'System',
|
|
655
|
+
log.action,
|
|
656
|
+
log.resource_type + '/' + (log.resource_id || 'N/A')
|
|
657
|
+
]);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
console.log(table.toString());
|
|
661
|
+
console.log(chalk.gray(`\nShowing ${logs.length} log(s)\n`));
|
|
662
|
+
} catch (error) {
|
|
663
|
+
throw new Error(error.response?.data?.detail || error.message);
|
|
664
|
+
}
|
|
360
665
|
}
|
|
361
666
|
}
|
|
362
667
|
|