raqeb-cli 1.0.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/README.md CHANGED
@@ -1,53 +1,133 @@
1
- # raqeb-cli
1
+ # Raqeb CLI
2
2
 
3
- Official Raqeb CLI - Command-line tool for Database PAM and Developer Secrets Management.
3
+ Official command-line tool for **Raqeb Privileged Access Management (PAM)** platform.
4
4
 
5
- ## Installation
5
+ ## šŸš€ Features
6
6
 
7
- ### Via npm (Recommended)
7
+ - šŸ” **Database PAM**: Get temporary database credentials with auto-expiration
8
+ - šŸ”‘ **Secrets Management**: Securely retrieve application secrets
9
+ - šŸ‘¤ **Service Accounts**: Programmatic API access for applications
10
+ - šŸ“Š **API Keys**: Personal access tokens for developers
11
+ - šŸ”’ **Zero Trust**: Dynamic credentials that automatically expire
12
+ - šŸ“ **Audit Logging**: Track all access activities
13
+
14
+ ## šŸ“¦ Installation
15
+
16
+ ### npm (Node.js)
8
17
 
9
18
  ```bash
10
19
  npm install -g raqeb-cli
11
20
  ```
12
21
 
13
- ### Via yarn
22
+ ### PyPI (Python)
14
23
 
15
24
  ```bash
16
- yarn global add raqeb-cli
25
+ pip install raqeb-cli
17
26
  ```
18
27
 
19
- ## Quick Start
28
+ ### Homebrew (macOS/Linux)
29
+
30
+ ```bash
31
+ brew tap Tzamun-Arabia-IT-Co/raqeb
32
+ brew install raqeb-cli
33
+ ```
34
+
35
+ ### Chocolatey (Windows)
36
+
37
+ ```powershell
38
+ choco install raqeb-cli
39
+ ```
20
40
 
21
- ### 1. Login
41
+ ### RPM (RedHat/Fedora/CentOS)
22
42
 
23
43
  ```bash
24
- raqeb login --api-key sa_your_api_key_here
44
+ # Download from GitHub Releases
45
+ curl -LO https://github.com/Tzamun-Arabia-IT-Co/raqeb-cli/releases/latest/download/raqeb-cli.rpm
46
+ sudo rpm -ivh raqeb-cli.rpm
25
47
  ```
26
48
 
27
- ### 2. Get Secret
49
+ ## šŸŽÆ Quick Start
50
+
51
+ ### 1. Login with Service Account
28
52
 
29
53
  ```bash
30
- raqeb secrets get <secret-id>
54
+ # Using service account API key (for applications/CI/CD)
55
+ raqeb login --api-key sa_your_service_account_key
56
+
57
+ # Or using personal API key (for developers)
58
+ raqeb login --api-key ak_your_personal_key
31
59
  ```
32
60
 
33
- ### 3. Get Database Credentials
61
+ ### 2. Get Temporary Database Credentials
34
62
 
35
63
  ```bash
36
- raqeb db connect <database-id> --ttl 4 --access-level read-only
64
+ # Get read-only access for 2 hours
65
+ raqeb db connect prod-mysql --ttl 2 --access-level read-only
66
+
67
+ # Returns:
68
+ # Username: temp_user_abc123
69
+ # Password: random_secure_password
70
+ # Host: mysql.example.com
71
+ # Port: 3306
72
+ # Expires: 2026-02-14 20:00:00
37
73
  ```
38
74
 
39
- ### 4. Revoke Credentials
75
+ ### 3. Retrieve Application Secrets
40
76
 
41
77
  ```bash
42
- raqeb db revoke <lease-id>
78
+ # Get secret value
79
+ raqeb secrets get stripe-api-key
80
+
81
+ # Returns the decrypted secret value
43
82
  ```
44
83
 
45
- ## Commands
84
+ ### 4. Manage API Keys
85
+
86
+ ```bash
87
+ # List your API keys
88
+ raqeb keys list
89
+
90
+ # Create new API key
91
+ raqeb keys create "My Dev Key" --scopes secrets:read,databases:read
92
+
93
+ # Delete API key
94
+ raqeb keys delete <key-id>
95
+ ```
96
+
97
+ ## šŸ“– Commands Reference
46
98
 
47
99
  ### Authentication
48
100
 
49
101
  ```bash
102
+ # Login with API key
50
103
  raqeb login --api-key <key>
104
+
105
+ # Login with interactive prompt
106
+ raqeb login
107
+
108
+ # Logout
109
+ raqeb logout
110
+
111
+ # Show current user
112
+ raqeb whoami
113
+ ```
114
+
115
+ ### Database PAM
116
+
117
+ ```bash
118
+ # Get temporary database credentials
119
+ raqeb db connect <database-id> [options]
120
+ --ttl <hours> Time to live in hours (default: 4, max: 24)
121
+ --access-level <level> Access level: read-only, read-write, admin (default: read-only)
122
+
123
+ # List active database leases
124
+ raqeb db leases
125
+
126
+ # Revoke database credentials
127
+ raqeb db revoke <lease-id>
128
+
129
+ # List available databases
130
+ raqeb db list
51
131
  ```
52
132
 
53
133
  ### Secrets Management
@@ -55,80 +135,254 @@ raqeb login --api-key <key>
55
135
  ```bash
56
136
  # Get secret value
57
137
  raqeb secrets get <secret-id>
58
- ```
59
138
 
60
- ### Database Access
139
+ # List all secrets (names only, not values)
140
+ raqeb secrets list
61
141
 
62
- ```bash
63
- # Get temporary credentials
64
- raqeb db connect <database-id> [options]
65
- --ttl <hours> Time to live (default: 4)
66
- --access-level <level> read-only, read-write, or admin (default: read-only)
142
+ # Create new secret
143
+ raqeb secrets create <name> <value> [--description <desc>]
67
144
 
68
- # Revoke credentials
69
- raqeb db revoke <lease-id>
145
+ # Update secret
146
+ raqeb secrets update <secret-id> <new-value>
147
+
148
+ # Delete secret
149
+ raqeb secrets delete <secret-id>
70
150
  ```
71
151
 
72
- ### API Keys
152
+ ### API Keys (Personal)
73
153
 
74
154
  ```bash
75
- # List API keys
155
+ # List your API keys
76
156
  raqeb keys list
77
157
 
78
158
  # Create new API key
79
- raqeb keys create <name> [--scopes <scopes>]
159
+ raqeb keys create <name> [options]
160
+ --scopes <scopes> Comma-separated scopes (default: secrets:read)
161
+ --expires-in <days> Expiration in days (default: 90, max: 365)
80
162
 
81
163
  # Delete API key
82
164
  raqeb keys delete <key-id>
165
+
166
+ # Rotate API key
167
+ raqeb keys rotate <key-id>
83
168
  ```
84
169
 
85
- ## Configuration
170
+ ### Service Accounts (Admin Only)
171
+
172
+ ```bash
173
+ # List service accounts
174
+ raqeb sa list
175
+
176
+ # Create service account
177
+ raqeb sa create <name> [options]
178
+ --scopes <scopes> Comma-separated scopes
179
+ --allowed-databases <ids> Comma-separated database IDs
180
+ --allowed-secrets <ids> Comma-separated secret IDs
181
+
182
+ # Delete service account
183
+ raqeb sa delete <account-id>
184
+
185
+ # View service account details
186
+ raqeb sa get <account-id>
187
+ ```
188
+
189
+ ### Audit Logs
190
+
191
+ ```bash
192
+ # View audit logs
193
+ raqeb audit list [options]
194
+ --limit <n> Number of records (default: 50)
195
+ --user <user-id> Filter by user
196
+ --action <action> Filter by action type
197
+ --from <date> Start date (YYYY-MM-DD)
198
+ --to <date> End date (YYYY-MM-DD)
199
+
200
+ # Export audit logs
201
+ raqeb audit export --format csv --output audit.csv
202
+ ```
203
+
204
+ ## āš™ļø Configuration
86
205
 
87
206
  Configuration is stored in `~/.raqeb/config.json`
88
207
 
89
208
  ```json
90
209
  {
91
210
  "base_url": "https://app.raqeb.cloud/api/v1",
92
- "api_key": "sa_your_api_key"
211
+ "api_key": "sa_your_service_account_key",
212
+ "tenant": "your-tenant-subdomain"
93
213
  }
94
214
  ```
95
215
 
96
- ## Examples
216
+ ### Environment Variables
97
217
 
98
- ### CI/CD Integration
218
+ You can also use environment variables:
219
+
220
+ ```bash
221
+ export RAQEB_API_KEY="sa_your_api_key"
222
+ export RAQEB_BASE_URL="https://app.raqeb.cloud/api/v1"
223
+ export RAQEB_TENANT="your-tenant"
224
+ ```
225
+
226
+ ### Multi-Tenant Support
227
+
228
+ If you work with multiple Raqeb tenants:
229
+
230
+ ```bash
231
+ # Switch tenant
232
+ raqeb config set-tenant production-tenant
233
+
234
+ # Or specify tenant per command
235
+ raqeb --tenant staging-tenant db connect staging-db
236
+ ```
237
+
238
+ ## šŸ’” Use Cases & Examples
239
+
240
+ ### 1. CI/CD Pipeline Integration
99
241
 
100
242
  ```bash
101
243
  #!/bin/bash
102
- # Get database credentials for deployment
244
+ # GitHub Actions / Jenkins / GitLab CI
245
+
246
+ # Login with service account (stored in CI secrets)
247
+ raqeb login --api-key $RAQEB_SERVICE_ACCOUNT_KEY
103
248
 
104
- # Login (use environment variable)
249
+ # Get temporary database credentials for migration
250
+ CREDS=$(raqeb db connect prod-mysql --ttl 1 --access-level read-write)
251
+ DB_USER=$(echo "$CREDS" | grep "Username:" | awk '{print $2}')
252
+ DB_PASS=$(echo "$CREDS" | grep "Password:" | awk '{print $2}')
253
+
254
+ # Run database migration
255
+ mysql -h prod-mysql.example.com -u $DB_USER -p$DB_PASS < migration.sql
256
+
257
+ # Credentials automatically expire after 1 hour
258
+ ```
259
+
260
+ ### 2. Application Secrets Retrieval
261
+
262
+ ```bash
263
+ #!/bin/bash
264
+ # Retrieve secrets for application startup
265
+
266
+ # Login
105
267
  raqeb login --api-key $RAQEB_API_KEY
106
268
 
107
- # Get temporary credentials
108
- raqeb db connect prod-mysql-db --ttl 1 --access-level read-write
269
+ # Get all required secrets
270
+ export STRIPE_API_KEY=$(raqeb secrets get stripe-api-key)
271
+ export AWS_ACCESS_KEY=$(raqeb secrets get aws-access-key)
272
+ export DATABASE_URL=$(raqeb secrets get database-url)
273
+
274
+ # Start application
275
+ npm start
276
+ ```
277
+
278
+ ### 3. Developer Local Access
279
+
280
+ ```bash
281
+ # Developer needs temporary access to staging database
282
+
283
+ # Login with personal API key
284
+ raqeb login --api-key ak_developer_key
285
+
286
+ # Get 4-hour read-only access
287
+ raqeb db connect staging-postgres --ttl 4 --access-level read-only
288
+
289
+ # Connect with provided credentials
290
+ psql -h staging-db.example.com -U temp_user_xyz789 -d staging
109
291
 
110
- # ... your deployment script ...
292
+ # Credentials auto-expire after 4 hours
293
+ ```
294
+
295
+ ### 4. Automated Testing
296
+
297
+ ```bash
298
+ #!/bin/bash
299
+ # Integration tests with temporary database
300
+
301
+ # Login
302
+ raqeb login --api-key $RAQEB_TEST_KEY
303
+
304
+ # Get test database credentials
305
+ raqeb db connect test-mysql --ttl 1 --access-level read-write
111
306
 
112
- # Revoke when done
307
+ # Run tests
308
+ npm test
309
+
310
+ # Cleanup (optional - auto-expires anyway)
113
311
  raqeb db revoke $LEASE_ID
114
312
  ```
115
313
 
116
- ### Retrieve Application Secrets
314
+ ### 5. Service Account Management (Admin)
117
315
 
118
316
  ```bash
119
- # Get API key from secrets vault
120
- raqeb secrets get api-key-prod
317
+ # Admin creates service account for production app
318
+
319
+ raqeb sa create "Production Backend" \
320
+ --scopes "databases:read,databases:write,secrets:read" \
321
+ --allowed-databases "prod-mysql,prod-postgres" \
322
+ --allowed-secrets "stripe-key,aws-key"
121
323
 
122
- # Use in your application
123
- export EXTERNAL_API_KEY=$(raqeb secrets get api-key-prod | grep "Value:" | cut -d' ' -f2)
324
+ # Returns service account API key (sa_xxx)
325
+ # Store in production environment variables
124
326
  ```
125
327
 
126
- ## License
328
+ ## šŸ” Security Best Practices
329
+
330
+ ### API Key Management
331
+
332
+ - **Never commit API keys** to version control
333
+ - **Use environment variables** in CI/CD pipelines
334
+ - **Rotate keys regularly** (every 90 days recommended)
335
+ - **Use service accounts** for applications, personal keys for development
336
+ - **Limit scopes** to minimum required permissions
337
+
338
+ ### Database Access
339
+
340
+ - **Request minimum TTL** needed for your task
341
+ - **Use read-only access** when possible
342
+ - **Revoke credentials** when done (optional - auto-expires)
343
+ - **Monitor audit logs** for suspicious activity
344
+
345
+ ### Secrets Management
346
+
347
+ - **Encrypt secrets** at rest (handled by Raqeb)
348
+ - **Limit secret access** to specific service accounts
349
+ - **Rotate secrets** regularly
350
+ - **Use different secrets** for different environments
351
+
352
+ ## šŸ“š Documentation
353
+
354
+ - **Full Documentation**: https://docs.raqeb.cloud
355
+ - **API Reference**: https://docs.raqeb.cloud/api
356
+ - **Service Accounts Guide**: https://docs.raqeb.cloud/service-accounts
357
+ - **Database PAM Guide**: https://docs.raqeb.cloud/database-pam
358
+ - **Secrets Management**: https://docs.raqeb.cloud/secrets
359
+
360
+ ## šŸ†˜ Support
361
+
362
+ - **Email**: support@tzamun.sa
363
+ - **Website**: https://raqeb.cloud
364
+ - **GitHub Issues**: https://github.com/Tzamun-Arabia-IT-Co/raqeb-cli/issues
365
+ - **Documentation**: https://docs.raqeb.cloud
366
+
367
+ ## šŸ“ License
368
+
369
+ MIT License - Copyright (c) 2026 Tzamun Arabia IT Co
370
+
371
+ ## 🌟 Features Roadmap
372
+
373
+ - [ ] Multi-factor authentication support
374
+ - [ ] SSH key management
375
+ - [ ] Certificate management
376
+ - [ ] Kubernetes secrets integration
377
+ - [ ] Terraform provider
378
+ - [ ] Ansible integration
379
+
380
+ ## šŸ¤ Contributing
381
+
382
+ Contributions are welcome! Please read our contributing guidelines before submitting PRs.
127
383
 
128
- MIT
384
+ ## šŸ“Š Version
129
385
 
130
- ## Support
386
+ Current version: 1.0.0
131
387
 
132
- - Documentation: https://docs.raqeb.cloud
133
- - Email: support@raqeb.cloud
134
- - GitHub: https://github.com/raqeb/cli
388
+ See [CHANGELOG.md](CHANGELOG.md) for release notes.
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Generate shell completion scripts
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ const shell = process.argv[2] || 'bash';
9
+
10
+ const completionsDir = path.join(__dirname, '..', 'completions');
11
+
12
+ switch (shell) {
13
+ case 'bash':
14
+ const bashScript = fs.readFileSync(path.join(completionsDir, 'bash', 'raqeb-completion.sh'), 'utf8');
15
+ console.log(bashScript);
16
+ break;
17
+
18
+ case 'zsh':
19
+ const zshScript = fs.readFileSync(path.join(completionsDir, 'zsh', '_raqeb'), 'utf8');
20
+ console.log(zshScript);
21
+ break;
22
+
23
+ case 'fish':
24
+ console.log('# Fish completion not yet implemented');
25
+ console.log('# Use: raqeb completion bash > ~/.config/fish/completions/raqeb.fish');
26
+ break;
27
+
28
+ default:
29
+ console.error(`Unknown shell: ${shell}`);
30
+ console.error('Supported shells: bash, zsh, fish');
31
+ process.exit(1);
32
+ }
package/bin/raqeb.js CHANGED
@@ -6,6 +6,8 @@ const fs = require('fs');
6
6
  const path = require('path');
7
7
  const os = require('os');
8
8
  const chalk = require('chalk');
9
+ const inquirer = require('inquirer');
10
+ const Table = require('cli-table3');
9
11
 
10
12
  const CONFIG_DIR = path.join(os.homedir(), '.raqeb');
11
13
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
@@ -45,6 +47,15 @@ function getClient() {
45
47
  });
46
48
  }
47
49
 
50
+ // Format output (table or JSON)
51
+ function formatOutput(data, options = {}) {
52
+ if (options.json) {
53
+ console.log(JSON.stringify(data, null, 2));
54
+ return;
55
+ }
56
+ return data;
57
+ }
58
+
48
59
  // Login command
49
60
  program
50
61
  .command('login')
@@ -84,11 +95,97 @@ secrets
84
95
  const db = program.command('db').description('Manage database access');
85
96
 
86
97
  db
87
- .command('connect <database-id>')
98
+ .command('list')
99
+ .description('List all available databases')
100
+ .option('--json', 'Output in JSON format')
101
+ .addHelpText('after', `
102
+
103
+ Examples:
104
+ $ raqeb db list
105
+ $ raqeb db list --json
106
+ `)
107
+ .action(async (options) => {
108
+ try {
109
+ const client = getClient();
110
+ const response = await client.get('/databases');
111
+ const databases = response.data;
112
+
113
+ if (options.json) {
114
+ console.log(JSON.stringify(databases, null, 2));
115
+ return;
116
+ }
117
+
118
+ if (databases.length === 0) {
119
+ console.log(chalk.yellow('No databases available'));
120
+ return;
121
+ }
122
+
123
+ const table = new Table({
124
+ head: ['ID', 'Name', 'Type', 'Host', 'Status'],
125
+ style: { head: ['cyan'] }
126
+ });
127
+
128
+ databases.forEach(db => {
129
+ table.push([
130
+ db.id,
131
+ db.name,
132
+ db.type,
133
+ db.host || 'N/A',
134
+ db.is_active ? chalk.green('Active') : chalk.red('Inactive')
135
+ ]);
136
+ });
137
+
138
+ console.log('\n' + table.toString());
139
+ console.log(chalk.gray(`\nTotal: ${databases.length} database(s)`));
140
+ } catch (error) {
141
+ console.error(chalk.red(`āŒ Error: ${error.response?.data?.detail || error.message}`));
142
+ process.exit(1);
143
+ }
144
+ });
145
+
146
+ db
147
+ .command('connect [database-id]')
88
148
  .description('Get temporary database credentials')
89
149
  .option('--ttl <hours>', 'Time to live in hours', '4')
90
150
  .option('--access-level <level>', 'Access level (read-only, read-write, admin)', 'read-only')
151
+ .option('--reason <text>', 'Reason for access (for audit trail)')
152
+ .option('--json', 'Output in JSON format')
153
+ .addHelpText('after', `
154
+
155
+ Examples:
156
+ $ raqeb db connect prod-mysql --ttl 2
157
+ $ raqeb db connect staging-postgres --access-level read-write --reason "Migration"
158
+ $ raqeb db connect dev-mongodb --json
159
+ $ raqeb db connect # Interactive mode - prompts for database
160
+ `)
91
161
  .action(async (databaseId, options) => {
162
+ // Interactive prompt if database-id not provided
163
+ if (!databaseId) {
164
+ try {
165
+ const client = getClient();
166
+ const dbResponse = await client.get('/databases');
167
+ const databases = dbResponse.data;
168
+
169
+ if (databases.length === 0) {
170
+ console.log(chalk.yellow('No databases available'));
171
+ return;
172
+ }
173
+
174
+ const answer = await inquirer.prompt([{
175
+ type: 'list',
176
+ name: 'database',
177
+ message: 'Which database?',
178
+ choices: databases.map(db => ({
179
+ name: `${db.name} (${db.type})`,
180
+ value: db.id
181
+ }))
182
+ }]);
183
+ databaseId = answer.database;
184
+ } catch (error) {
185
+ console.error(chalk.red(`āŒ Error fetching databases: ${error.message}`));
186
+ process.exit(1);
187
+ }
188
+ }
92
189
  try {
93
190
  const client = getClient();
94
191
  const response = await client.post('/service-accounts/databases/dynamic-credentials', {
@@ -98,6 +195,11 @@ db
98
195
  });
99
196
  const data = response.data;
100
197
 
198
+ if (options.json) {
199
+ console.log(JSON.stringify(data, null, 2));
200
+ return;
201
+ }
202
+
101
203
  console.log(chalk.cyan('\nšŸ”‘ Temporary Database Credentials'));
102
204
  console.log(chalk.gray('━'.repeat(40)));
103
205
  console.log(chalk.white(`Username: ${data.username}`));
@@ -204,6 +306,57 @@ keys
204
306
  }
205
307
  });
206
308
 
309
+ // Interactive command
310
+ program
311
+ .command('interactive')
312
+ .alias('i')
313
+ .alias('shell')
314
+ .description('Launch interactive mode')
315
+ .addHelpText('after', `
316
+
317
+ Interactive mode provides a guided experience with:
318
+ - Command discovery with /
319
+ - Tab completion
320
+ - Command history
321
+ - Interactive prompts
322
+
323
+ Examples:
324
+ $ raqeb interactive
325
+ $ raqeb -i
326
+ $ raqeb shell
327
+ `)
328
+ .action(() => {
329
+ const InteractiveShell = require('../lib/interactive');
330
+ const client = getClient();
331
+ const shell = new InteractiveShell(client);
332
+ shell.start();
333
+ });
334
+
335
+ // Completion command
336
+ program
337
+ .command('completion <shell>')
338
+ .description('Generate shell completion script')
339
+ .addHelpText('after', `
340
+
341
+ Supported shells: bash, zsh, fish
342
+
343
+ Installation:
344
+ Bash:
345
+ $ raqeb completion bash > /etc/bash_completion.d/raqeb
346
+ $ source /etc/bash_completion.d/raqeb
347
+
348
+ Zsh:
349
+ $ raqeb completion zsh > ~/.zsh/completion/_raqeb
350
+ $ fpath=(~/.zsh/completion $fpath)
351
+
352
+ Fish:
353
+ $ raqeb completion fish > ~/.config/fish/completions/raqeb.fish
354
+ `)
355
+ .action((shell) => {
356
+ const completionScript = require('./raqeb-completion.js');
357
+ // Script will output to stdout
358
+ });
359
+
207
360
  program
208
361
  .name('raqeb')
209
362
  .description('Raqeb CLI - Database PAM and Secrets Management')
@@ -0,0 +1,668 @@
1
+ const readline = require('readline');
2
+ const chalk = require('chalk');
3
+ const inquirer = require('inquirer');
4
+ const Table = require('cli-table3');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const path = require('path');
8
+
9
+ class InteractiveShell {
10
+ constructor(apiClient) {
11
+ this.client = apiClient;
12
+ this.history = [];
13
+ this.rl = null;
14
+ this.config = this.loadConfig();
15
+ this.userInfo = null;
16
+ }
17
+
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();
41
+ this.showWelcome();
42
+ this.setupReadline();
43
+ this.rl.prompt();
44
+ }
45
+
46
+ setupReadline() {
47
+ this.rl = readline.createInterface({
48
+ input: process.stdin,
49
+ output: process.stdout,
50
+ prompt: chalk.cyan('raqeb> '),
51
+ completer: this.completer.bind(this)
52
+ });
53
+
54
+ this.rl.on('line', async (line) => {
55
+ const input = line.trim();
56
+
57
+ if (input) {
58
+ this.history.push(input);
59
+ await this.handleCommand(input);
60
+ }
61
+
62
+ this.rl.prompt();
63
+ });
64
+
65
+ this.rl.on('close', () => {
66
+ console.log(chalk.gray('\nGoodbye! šŸ‘‹'));
67
+ process.exit(0);
68
+ });
69
+ }
70
+
71
+ completer(line) {
72
+ const commands = [
73
+ '/', '/help', '/exit', '/clear',
74
+ '/db', '/db list', '/db connect', '/db sessions', '/db revoke',
75
+ '/secrets', '/secrets list', '/secrets get', '/secrets set', '/secrets delete',
76
+ '/keys', '/keys list', '/keys create', '/keys delete',
77
+ '/audit', '/audit logs'
78
+ ];
79
+
80
+ const hits = commands.filter((c) => c.startsWith(line));
81
+ return [hits.length ? hits : commands, line];
82
+ }
83
+
84
+ showWelcome() {
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'));
110
+ }
111
+
112
+ async handleCommand(input) {
113
+ try {
114
+ if (input === '/') {
115
+ this.showAllCommands();
116
+ } else if (input === '/help') {
117
+ this.showHelp();
118
+ } else if (input === '/exit' || input === '/quit') {
119
+ this.rl.close();
120
+ } else if (input === '/clear') {
121
+ console.clear();
122
+ this.showWelcome();
123
+ } else if (input.startsWith('/db')) {
124
+ await this.handleDatabase(input);
125
+ } else if (input.startsWith('/secrets')) {
126
+ await this.handleSecrets(input);
127
+ } else if (input.startsWith('/keys')) {
128
+ await this.handleKeys(input);
129
+ } else if (input.startsWith('/audit')) {
130
+ await this.handleAudit(input);
131
+ } else {
132
+ console.log(chalk.red(`Unknown command: ${input}`));
133
+ console.log(chalk.gray('Type / to see all commands'));
134
+ }
135
+ } catch (error) {
136
+ console.error(chalk.red(`āŒ Error: ${error.message}`));
137
+ }
138
+ }
139
+
140
+ showAllCommands() {
141
+ console.log(chalk.bold('\nšŸ“‹ Available Commands:\n'));
142
+ console.log(chalk.cyan(' /db') + ' Database operations');
143
+ console.log(chalk.cyan(' /secrets') + ' Secrets management');
144
+ console.log(chalk.cyan(' /keys') + ' API keys management');
145
+ console.log(chalk.cyan(' /audit') + ' Audit logs');
146
+ console.log(chalk.cyan(' /help') + ' Show this help');
147
+ console.log(chalk.cyan(' /clear') + ' Clear screen');
148
+ console.log(chalk.cyan(' /exit') + ' Exit interactive mode');
149
+ console.log();
150
+ }
151
+
152
+ showHelp() {
153
+ console.log(chalk.bold('\nšŸ“– Interactive Mode Help\n'));
154
+ console.log(chalk.white('Commands:'));
155
+ console.log(' / Show all available commands');
156
+ console.log(' /db Database operations');
157
+ console.log(' /db list List all databases');
158
+ console.log(' /db connect Connect to a database');
159
+ console.log(' /secrets Secrets management');
160
+ console.log(' /secrets list List all secrets');
161
+ console.log(' /secrets get Get a secret value');
162
+ console.log(' /keys API keys management');
163
+ console.log(' /audit View audit logs');
164
+ console.log(' /help Show this help');
165
+ console.log(' /clear Clear the screen');
166
+ console.log(' /exit Exit interactive mode');
167
+ console.log();
168
+ console.log(chalk.gray('Tips:'));
169
+ console.log(chalk.gray(' - Use TAB for autocomplete'));
170
+ console.log(chalk.gray(' - Use UP/DOWN arrows for command history'));
171
+ console.log(chalk.gray(' - Press Ctrl+C or type /exit to quit'));
172
+ console.log();
173
+ }
174
+
175
+ async handleDatabase(input) {
176
+ const parts = input.split(' ');
177
+ const subcommand = parts[1];
178
+
179
+ if (!subcommand || subcommand === 'help') {
180
+ console.log(chalk.bold('\nšŸ—„ļø Database Commands:\n'));
181
+ console.log(' /db list List all databases');
182
+ console.log(' /db connect Connect to a database');
183
+ console.log(' /db sessions View active sessions');
184
+ console.log(' /db revoke <id> Revoke credentials');
185
+ console.log();
186
+ return;
187
+ }
188
+
189
+ switch (subcommand) {
190
+ case 'list':
191
+ await this.listDatabases();
192
+ break;
193
+ case 'connect':
194
+ await this.connectDatabase(parts[2]);
195
+ break;
196
+ case 'sessions':
197
+ await this.showSessions();
198
+ break;
199
+ case 'revoke':
200
+ await this.revokeCredentials(parts[2]);
201
+ break;
202
+ default:
203
+ console.log(chalk.red(`Unknown database command: ${subcommand}`));
204
+ console.log(chalk.gray('Type /db for available commands'));
205
+ }
206
+ }
207
+
208
+ async listDatabases() {
209
+ try {
210
+ const response = await this.client.get('/databases');
211
+ const databases = response.data;
212
+
213
+ if (databases.length === 0) {
214
+ console.log(chalk.yellow('\nNo databases available'));
215
+ return;
216
+ }
217
+
218
+ const table = new Table({
219
+ head: ['ID', 'Name', 'Type', 'Host', 'Status'],
220
+ style: { head: ['cyan'] }
221
+ });
222
+
223
+ databases.forEach(db => {
224
+ table.push([
225
+ db.id,
226
+ db.name,
227
+ db.type,
228
+ db.host || 'N/A',
229
+ db.is_active ? chalk.green('ā—') + ' Active' : chalk.red('ā—') + ' Inactive'
230
+ ]);
231
+ });
232
+
233
+ console.log('\n' + table.toString());
234
+ console.log(chalk.gray(`\nTotal: ${databases.length} database(s)\n`));
235
+ } catch (error) {
236
+ throw new Error(error.response?.data?.detail || error.message);
237
+ }
238
+ }
239
+
240
+ async connectDatabase(databaseId) {
241
+ try {
242
+ // If no database ID provided, prompt for it
243
+ if (!databaseId) {
244
+ const response = await this.client.get('/databases');
245
+ const databases = response.data;
246
+
247
+ if (databases.length === 0) {
248
+ console.log(chalk.yellow('\nNo databases available'));
249
+ return;
250
+ }
251
+
252
+ const answer = await inquirer.prompt([{
253
+ type: 'list',
254
+ name: 'database',
255
+ message: 'Which database?',
256
+ choices: databases.map(db => ({
257
+ name: `${db.name} (${db.type})`,
258
+ value: db.id
259
+ }))
260
+ }]);
261
+ databaseId = answer.database;
262
+ }
263
+
264
+ // Prompt for connection options
265
+ const options = await inquirer.prompt([
266
+ {
267
+ type: 'list',
268
+ name: 'accessLevel',
269
+ message: 'Access level?',
270
+ choices: ['read-only', 'read-write', 'admin'],
271
+ default: 'read-only'
272
+ },
273
+ {
274
+ type: 'number',
275
+ name: 'ttl',
276
+ message: 'TTL (hours)?',
277
+ default: 2,
278
+ validate: (value) => value > 0 && value <= 24 || 'TTL must be between 1 and 24 hours'
279
+ },
280
+ {
281
+ type: 'input',
282
+ name: 'reason',
283
+ message: 'Reason for access? (optional)',
284
+ default: ''
285
+ }
286
+ ]);
287
+
288
+ // Request credentials
289
+ const response = await this.client.post('/service-accounts/databases/dynamic-credentials', {
290
+ database_id: databaseId,
291
+ ttl_hours: options.ttl,
292
+ access_level: options.accessLevel,
293
+ reason: options.reason || undefined
294
+ });
295
+
296
+ const data = response.data;
297
+
298
+ console.log(chalk.green('\nāœ“ Credentials Generated!\n'));
299
+ const table = new Table({
300
+ style: { head: ['cyan'] }
301
+ });
302
+ table.push(
303
+ ['Username', data.username],
304
+ ['Password', data.password],
305
+ ['Access Level', data.access_level],
306
+ ['Expires', data.expires_at],
307
+ ['Lease ID', data.lease_id]
308
+ );
309
+ console.log(table.toString());
310
+ console.log(chalk.yellow(`\nāš ļø These credentials will expire in ${options.ttl} hours`));
311
+ console.log(chalk.gray(`To revoke early: /db revoke ${data.lease_id}\n`));
312
+ } catch (error) {
313
+ throw new Error(error.response?.data?.detail || error.message);
314
+ }
315
+ }
316
+
317
+ async showSessions() {
318
+ console.log(chalk.yellow('\nāš ļø Sessions view not yet implemented\n'));
319
+ }
320
+
321
+ async revokeCredentials(leaseId) {
322
+ if (!leaseId) {
323
+ console.log(chalk.red('\nāŒ Lease ID required'));
324
+ console.log(chalk.gray('Usage: /db revoke <lease-id>\n'));
325
+ return;
326
+ }
327
+
328
+ try {
329
+ await this.client.post(`/service-accounts/leases/${leaseId}/revoke`);
330
+ console.log(chalk.green(`\nāœ“ Lease ${leaseId} revoked successfully\n`));
331
+ } catch (error) {
332
+ throw new Error(error.response?.data?.detail || error.message);
333
+ }
334
+ }
335
+
336
+ async handleSecrets(input) {
337
+ const parts = input.split(' ');
338
+ const subcommand = parts[1];
339
+
340
+ if (!subcommand || subcommand === 'help') {
341
+ console.log(chalk.bold('\nšŸ” Secrets Commands:\n'));
342
+ console.log(' /secrets list List all secrets');
343
+ console.log(' /secrets get <name> Get secret value');
344
+ console.log(' /secrets set Create/update secret');
345
+ console.log(' /secrets delete <name> Delete secret');
346
+ console.log();
347
+ return;
348
+ }
349
+
350
+ switch (subcommand) {
351
+ case 'list':
352
+ await this.listSecrets();
353
+ break;
354
+ case 'get':
355
+ await this.getSecret(parts[2]);
356
+ break;
357
+ case 'set':
358
+ await this.setSecret();
359
+ break;
360
+ case 'delete':
361
+ await this.deleteSecret(parts[2]);
362
+ break;
363
+ default:
364
+ console.log(chalk.red(`Unknown secrets command: ${subcommand}`));
365
+ console.log(chalk.gray('Type /secrets for available commands'));
366
+ }
367
+ }
368
+
369
+ async listSecrets() {
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
+ }
398
+ }
399
+
400
+ async getSecret(secretId) {
401
+ if (!secretId) {
402
+ console.log(chalk.red('\nāŒ Secret name required'));
403
+ console.log(chalk.gray('Usage: /secrets get <name>\n'));
404
+ return;
405
+ }
406
+
407
+ try {
408
+ const response = await this.client.post('/service-accounts/secrets/retrieve', null, {
409
+ params: { secret_id: secretId }
410
+ });
411
+ const data = response.data;
412
+
413
+ console.log(chalk.cyan(`\nšŸ” Secret: ${data.name}`));
414
+ console.log(chalk.white(`Value: ${data.value}`));
415
+ console.log(chalk.gray(`Retrieved at: ${data.retrieved_at}\n`));
416
+ } catch (error) {
417
+ throw new Error(error.response?.data?.detail || error.message);
418
+ }
419
+ }
420
+
421
+ async setSecret() {
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
+ }
455
+ }
456
+
457
+ async deleteSecret(secretName) {
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
+ }
482
+ }
483
+
484
+ async handleKeys(input) {
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
+ }
608
+ }
609
+
610
+ async handleAudit(input) {
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
+ }
665
+ }
666
+ }
667
+
668
+ module.exports = InteractiveShell;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raqeb-cli",
3
- "version": "1.0.0",
3
+ "version": "1.3.0",
4
4
  "description": "Raqeb CLI - Command-line tool for Database PAM and Developer Secrets Management",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -29,7 +29,8 @@
29
29
  "commander": "^11.0.0",
30
30
  "chalk": "^4.1.2",
31
31
  "ora": "^5.4.1",
32
- "inquirer": "^8.2.5"
32
+ "inquirer": "^8.2.5",
33
+ "cli-table3": "^0.6.3"
33
34
  },
34
35
  "engines": {
35
36
  "node": ">=14.0.0"