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