api-response-manager 2.5.1 → 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,29 +133,46 @@ 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 {
112
150
  const localUrl = `http://localhost:${localPort}${message.path}`;
151
+
152
+ // Remove problematic headers that cause issues with local server
153
+ const forwardHeaders = { ...message.headers };
154
+ delete forwardHeaders['host'];
155
+ delete forwardHeaders['connection'];
156
+ delete forwardHeaders['accept-encoding']; // Prevent compression issues
157
+
113
158
  const response = await axios({
114
159
  method: message.method.toLowerCase(),
115
160
  url: localUrl,
116
- headers: message.headers || {},
161
+ headers: forwardHeaders,
117
162
  data: message.body,
118
- validateStatus: () => true // Accept any status code
163
+ validateStatus: () => true, // Accept any status code
164
+ responseType: 'arraybuffer', // Handle binary data properly
165
+ maxRedirects: 0, // Don't follow redirects, let the client handle them
166
+ timeout: 25000 // 25 second timeout
119
167
  });
120
168
 
121
169
  // Clean up headers to avoid conflicts
122
170
  const cleanHeaders = { ...response.headers };
123
171
  delete cleanHeaders['transfer-encoding'];
124
- delete cleanHeaders['content-length'];
172
+ delete cleanHeaders['connection'];
173
+
174
+ // Convert binary data to base64 for JSON transport
175
+ const bodyBase64 = Buffer.from(response.data).toString('base64');
125
176
 
126
177
  // Send response back to tunnel server
127
178
  ws.send(JSON.stringify({
@@ -129,7 +180,8 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
129
180
  requestId: message.requestId,
130
181
  statusCode: response.status,
131
182
  headers: cleanHeaders,
132
- body: response.data
183
+ body: bodyBase64,
184
+ encoding: 'base64'
133
185
  }));
134
186
  } catch (error) {
135
187
  console.error(chalk.red(`Error forwarding request: ${error.message}`));
@@ -245,6 +297,38 @@ async function stop(tunnelId) {
245
297
  }
246
298
  }
247
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
+
248
332
  // View tunnel logs
249
333
  async function logs(tunnelId, options) {
250
334
  const spinner = ora('Fetching logs...').start();
@@ -423,6 +507,7 @@ module.exports = {
423
507
  start,
424
508
  list,
425
509
  stop,
510
+ stopAll,
426
511
  logs,
427
512
  setDomain,
428
513
  uploadSSL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-response-manager",
3
- "version": "2.5.1",
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 });