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 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
@@ -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
- start() {
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.log(chalk.cyan('\n╔═══════════════════════════════════════════════════════════╗'));
59
- console.log(chalk.cyan('║ Raqeb CLI - Interactive Mode v1.1.0 ║'));
60
- console.log(chalk.cyan('║ Type / to see all commands, or /help for help ║'));
61
- console.log(chalk.cyan('╚═══════════════════════════════════════════════════════════╝\n'));
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
- console.log(chalk.yellow('\n⚠️ Secrets list not yet implemented\n'));
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.post('/service-accounts/secrets/retrieve', null, {
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
- console.log(chalk.yellow('\n⚠️ Set secret not yet implemented\n'));
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
- console.log(chalk.yellow('\n⚠️ Delete secret not yet implemented\n'));
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
- console.log(chalk.yellow('\n⚠️ API keys management not yet implemented\n'));
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
- console.log(chalk.yellow('\n⚠️ Audit logs not yet implemented\n'));
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raqeb-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Raqeb CLI - Command-line tool for Database PAM and Developer Secrets Management",
5
5
  "main": "index.js",
6
6
  "bin": {