api-response-manager 2.5.2 → 2.5.3

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
@@ -6,7 +6,7 @@
6
6
 
7
7
  Command-line interface for API Response Manager. Manage tunnels, webhooks, and projects from your terminal.
8
8
 
9
- **Version:** 2.5.0 | **Live Service:** https://tunnelapi.in
9
+ **Version:** 2.5.3 | **Live Service:** https://tunnelapi.in
10
10
 
11
11
  ## Installation
12
12
 
package/bin/arm.js CHANGED
@@ -38,6 +38,7 @@ program
38
38
  .option('-e, --email <email>', 'Email address')
39
39
  .option('-p, --password <password>', 'Password')
40
40
  .option('--provider <provider>', 'OAuth provider (google, github, microsoft)')
41
+ .option('-t, --token <token>', 'API token for CI/CD automation')
41
42
  .action(loginCommand);
42
43
 
43
44
  // Logout command
@@ -58,6 +59,7 @@ program
58
59
  .option('-p, --protocol <protocol>', 'Protocol (http, https, tcp, ws, wss)', 'http')
59
60
  .option('--ssl', 'Enable SSL/HTTPS')
60
61
  .option('-d, --domain <domain>', 'Custom domain')
62
+ .option('--json', 'Output in JSON format (for CI/CD automation)')
61
63
  .action(tunnelCommand.start);
62
64
 
63
65
  program
@@ -71,6 +73,11 @@ program
71
73
  .argument('<tunnelId>', 'Tunnel ID to stop')
72
74
  .action(tunnelCommand.stop);
73
75
 
76
+ program
77
+ .command('tunnel:stop-all')
78
+ .description('Stop all active tunnels')
79
+ .action(tunnelCommand.stopAll);
80
+
74
81
  program
75
82
  .command('tunnel:logs')
76
83
  .description('View tunnel request logs')
@@ -1,7 +1,7 @@
1
1
  const chalk = require('chalk');
2
2
  const ora = require('ora');
3
3
  const Table = require('cli-table3');
4
- const { getApiClient, requireAuth } = require('../utils/api');
4
+ const api = require('../utils/api');
5
5
 
6
6
  /**
7
7
  * Get current user info
@@ -10,10 +10,14 @@ async function info() {
10
10
  const spinner = ora('Fetching account info...').start();
11
11
 
12
12
  try {
13
- const api = await getApiClient();
14
- await requireAuth(api);
13
+ const token = api.getToken();
14
+ if (!token) {
15
+ spinner.fail('Not authenticated');
16
+ console.error(chalk.red('Please run: arm login'));
17
+ process.exit(1);
18
+ }
15
19
 
16
- const response = await api.get('/auth/me');
20
+ const response = await api.client.get('/auth/me');
17
21
  const user = response.data.user;
18
22
 
19
23
  spinner.stop();
@@ -52,10 +56,14 @@ async function features() {
52
56
  const spinner = ora('Fetching feature access...').start();
53
57
 
54
58
  try {
55
- const api = await getApiClient();
56
- await requireAuth(api);
59
+ const token = api.getToken();
60
+ if (!token) {
61
+ spinner.fail('Not authenticated');
62
+ console.error(chalk.red('Please run: arm login'));
63
+ process.exit(1);
64
+ }
57
65
 
58
- const response = await api.get('/auth/features');
66
+ const response = await api.client.get('/auth/features');
59
67
  const { tier, features } = response.data;
60
68
 
61
69
  spinner.stop();
@@ -102,12 +110,16 @@ async function usage() {
102
110
  const spinner = ora('Fetching usage stats...').start();
103
111
 
104
112
  try {
105
- const api = await getApiClient();
106
- await requireAuth(api);
113
+ const token = api.getToken();
114
+ if (!token) {
115
+ spinner.fail('Not authenticated');
116
+ console.error(chalk.red('Please run: arm login'));
117
+ process.exit(1);
118
+ }
107
119
 
108
120
  const [userRes, featuresRes] = await Promise.all([
109
- api.get('/auth/me'),
110
- api.get('/auth/features')
121
+ api.client.get('/auth/me'),
122
+ api.client.get('/auth/features')
111
123
  ]);
112
124
 
113
125
  const user = userRes.data.user;
package/commands/login.js CHANGED
@@ -6,6 +6,31 @@ const api = require('../utils/api');
6
6
  const config = require('../utils/config');
7
7
 
8
8
  async function login(options) {
9
+ // Token-based login for CI/CD automation
10
+ if (options.token) {
11
+ const spinner = ora('Authenticating with token...').start();
12
+ try {
13
+ // Validate token by making a test API call
14
+ config.set('token', options.token);
15
+ const response = await api.getUser();
16
+
17
+ if (response && response.user) {
18
+ config.set('userId', response.user._id || response.user.id);
19
+ config.set('email', response.user.email);
20
+ spinner.succeed(chalk.green('Authenticated successfully!'));
21
+ console.log(chalk.gray(`\nLogged in as: ${response.user.email}\n`));
22
+ } else {
23
+ throw new Error('Invalid token');
24
+ }
25
+ } catch (error) {
26
+ config.delete('token');
27
+ spinner.fail(chalk.red('Authentication failed'));
28
+ console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
29
+ process.exit(1);
30
+ }
31
+ return;
32
+ }
33
+
9
34
  console.log(chalk.blue.bold('\n🔐 API Response Manager - Login\n'));
10
35
 
11
36
  // Check if social login is requested
@@ -8,14 +8,20 @@ const config = require('../utils/config');
8
8
 
9
9
  // Start tunnel
10
10
  async function start(port, options) {
11
- console.log(chalk.blue.bold('\n🚇 Starting Tunnel...\n'));
11
+ const jsonOutput = options.json || false;
12
+
13
+ if (!jsonOutput) {
14
+ console.log(chalk.blue.bold('\n🚇 Starting Tunnel...\n'));
15
+ }
12
16
 
13
17
  if (!port) {
14
18
  port = config.get('defaultTunnelPort');
15
- console.log(chalk.gray(`Using default port: ${port}`));
19
+ if (!jsonOutput) {
20
+ console.log(chalk.gray(`Using default port: ${port}`));
21
+ }
16
22
  }
17
23
 
18
- const spinner = ora('Creating tunnel...').start();
24
+ const spinner = jsonOutput ? null : ora('Creating tunnel...').start();
19
25
 
20
26
  try {
21
27
  const tunnelData = {
@@ -37,7 +43,22 @@ async function start(port, options) {
37
43
  const response = await api.createTunnel(tunnelData);
38
44
  const tunnel = response.tunnel;
39
45
 
40
- spinner.succeed(chalk.green('Tunnel created successfully!'));
46
+ if (spinner) spinner.succeed(chalk.green('Tunnel created successfully!'));
47
+
48
+ // JSON output for automation/CI
49
+ if (jsonOutput) {
50
+ console.log(JSON.stringify({
51
+ success: true,
52
+ id: tunnel.id || tunnel._id,
53
+ subdomain: tunnel.subdomain,
54
+ publicUrl: tunnel.publicUrl,
55
+ localPort: tunnel.localPort,
56
+ protocol: tunnel.protocol || 'https'
57
+ }));
58
+ // For JSON mode, connect silently
59
+ await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort, true);
60
+ return;
61
+ }
41
62
 
42
63
  // Safe string padding function
43
64
  const safePadEnd = (str, length) => {
@@ -56,17 +77,30 @@ async function start(port, options) {
56
77
 
57
78
  // Connect tunnel client
58
79
  console.log(chalk.blue('Connecting tunnel client...\n'));
59
- await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort);
80
+ await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort, false);
60
81
 
61
82
  } catch (error) {
62
- spinner.fail(chalk.red('Failed to create tunnel'));
63
- console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
83
+ if (spinner) spinner.fail(chalk.red('Failed to create tunnel'));
84
+
85
+ const errorMsg = error.response?.data?.msg || error.message;
86
+
87
+ // Handle subdomain already taken error
88
+ if (error.response?.status === 409 && errorMsg.toLowerCase().includes('subdomain')) {
89
+ console.error(chalk.yellow(`\n⚠ ${errorMsg}`));
90
+ console.log(chalk.gray('\nOptions:'));
91
+ console.log(chalk.gray(' 1. Try again with a different subdomain: ') + chalk.cyan(`arm tunnel ${port} -s <new-subdomain>`));
92
+ console.log(chalk.gray(' 2. Let the system generate a unique subdomain: ') + chalk.cyan(`arm tunnel ${port}`));
93
+ console.log();
94
+ process.exit(1);
95
+ }
96
+
97
+ console.error(chalk.red(`\n✗ ${errorMsg}\n`));
64
98
  process.exit(1);
65
99
  }
66
100
  }
67
101
 
68
102
  // Connect tunnel client
69
- async function connectTunnelClient(tunnelId, subdomain, localPort) {
103
+ async function connectTunnelClient(tunnelId, subdomain, localPort, silent = false) {
70
104
  const tunnelServerUrl = config.get('tunnelServerUrl') || 'ws://localhost:8080';
71
105
  const token = api.getToken();
72
106
  const userId = config.get('userId');
@@ -76,7 +110,7 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
76
110
  let heartbeatInterval;
77
111
 
78
112
  ws.on('open', () => {
79
- console.log(chalk.green('✓ Connected to tunnel server'));
113
+ if (!silent) console.log(chalk.green('✓ Connected to tunnel server'));
80
114
 
81
115
  ws.send(JSON.stringify({
82
116
  type: 'register',
@@ -99,13 +133,17 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
99
133
  const message = JSON.parse(data.toString());
100
134
 
101
135
  if (message.type === 'registered') {
102
- console.log(chalk.green.bold('\n🎉 Tunnel Active!\n'));
103
- console.log(chalk.white('Your local server is now accessible at:'));
104
- console.log(chalk.cyan.bold(` ${message.publicUrl}\n`));
105
- console.log(chalk.gray('Press Ctrl+C to stop the tunnel\n'));
136
+ if (!silent) {
137
+ console.log(chalk.green.bold('\n🎉 Tunnel Active!\n'));
138
+ console.log(chalk.white('Your local server is now accessible at:'));
139
+ console.log(chalk.cyan.bold(` ${message.publicUrl}\n`));
140
+ console.log(chalk.gray('Press Ctrl+C to stop the tunnel\n'));
141
+ }
106
142
  } else if (message.type === 'request') {
107
- const timestamp = new Date().toLocaleTimeString();
108
- console.log(chalk.gray(`[${timestamp}]`), chalk.blue(message.method), chalk.white(message.path));
143
+ if (!silent) {
144
+ const timestamp = new Date().toLocaleTimeString();
145
+ console.log(chalk.gray(`[${timestamp}]`), chalk.blue(message.method), chalk.white(message.path));
146
+ }
109
147
 
110
148
  // Forward request to local server
111
149
  try {
@@ -259,6 +297,38 @@ async function stop(tunnelId) {
259
297
  }
260
298
  }
261
299
 
300
+ // Stop all tunnels
301
+ async function stopAll() {
302
+ const spinner = ora('Stopping all tunnels...').start();
303
+
304
+ try {
305
+ const response = await api.getTunnels();
306
+ const tunnels = response.tunnels || [];
307
+
308
+ if (tunnels.length === 0) {
309
+ spinner.info(chalk.yellow('No active tunnels to stop'));
310
+ return;
311
+ }
312
+
313
+ let stopped = 0;
314
+ for (const tunnel of tunnels) {
315
+ try {
316
+ await api.deleteTunnel(tunnel._id || tunnel.id);
317
+ stopped++;
318
+ } catch (e) {
319
+ // Continue stopping other tunnels
320
+ }
321
+ }
322
+
323
+ spinner.succeed(chalk.green(`Stopped ${stopped} tunnel(s)`));
324
+ console.log();
325
+ } catch (error) {
326
+ spinner.fail(chalk.red('Failed to stop tunnels'));
327
+ console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
328
+ process.exit(1);
329
+ }
330
+ }
331
+
262
332
  // View tunnel logs
263
333
  async function logs(tunnelId, options) {
264
334
  const spinner = ora('Fetching logs...').start();
@@ -437,6 +507,7 @@ module.exports = {
437
507
  start,
438
508
  list,
439
509
  stop,
510
+ stopAll,
440
511
  logs,
441
512
  setDomain,
442
513
  uploadSSL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-response-manager",
3
- "version": "2.5.2",
3
+ "version": "2.5.3",
4
4
  "description": "Command-line interface for API Response Manager",
5
5
  "main": "index.js",
6
6
  "bin": {
package/utils/api.js CHANGED
@@ -59,6 +59,11 @@ class APIClient {
59
59
  return response.data;
60
60
  }
61
61
 
62
+ async getUser() {
63
+ const response = await this.client.get('/auth/me');
64
+ return response.data;
65
+ }
66
+
62
67
  // OAuth Device Flow for CLI
63
68
  async requestDeviceCode(provider) {
64
69
  const response = await this.client.post('/auth/device/code', { provider });