entexto-cli 2.1.2 → 2.2.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/bin/entexto.js +9 -2
- package/lib/commands/tunnel.js +10 -0
- package/lib/commands/tunnels.js +114 -0
- package/package.json +1 -1
package/bin/entexto.js
CHANGED
|
@@ -81,10 +81,17 @@ program
|
|
|
81
81
|
program
|
|
82
82
|
.command('tunnel [target]')
|
|
83
83
|
.description('Exponer un servidor local en una URL pública de entexto.com')
|
|
84
|
-
.
|
|
85
|
-
|
|
84
|
+
.option('-l, --link <name>', 'Link personalizado: /t/<name> (requiere login)')
|
|
85
|
+
.action((target, opts) => {
|
|
86
|
+
require('../lib/commands/tunnel')({ target: target || '3000', link: opts.link });
|
|
86
87
|
});
|
|
87
88
|
|
|
89
|
+
// ─── entexto tunnels ─────────────────────────────────────────
|
|
90
|
+
program
|
|
91
|
+
.command('tunnels')
|
|
92
|
+
.description('Ver y gestionar tus túneles abiertos')
|
|
93
|
+
.action(require('../lib/commands/tunnels'));
|
|
94
|
+
|
|
88
95
|
// ─── entexto create ──────────────────────────────────────────
|
|
89
96
|
program
|
|
90
97
|
.command('create')
|
package/lib/commands/tunnel.js
CHANGED
|
@@ -30,6 +30,14 @@ module.exports = async function tunnel(opts) {
|
|
|
30
30
|
const { getConfig } = require('../utils/config');
|
|
31
31
|
const cfg = getConfig();
|
|
32
32
|
const baseUrl = (cfg.baseUrl || 'https://entexto.com').replace(/\/$/, '');
|
|
33
|
+
const token = cfg.token || null;
|
|
34
|
+
|
|
35
|
+
// Validar link personalizado
|
|
36
|
+
const customLink = opts.link || null;
|
|
37
|
+
if (customLink && !token) {
|
|
38
|
+
console.error(chalk.red('Los links personalizados requieren iniciar sesión. Ejecuta: entexto login'));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
33
41
|
|
|
34
42
|
// Calcular URL del WebSocket
|
|
35
43
|
const wsUrl = baseUrl.replace(/^http/, 'ws') + '/tunnel';
|
|
@@ -73,6 +81,8 @@ module.exports = async function tunnel(opts) {
|
|
|
73
81
|
socket.on('connect', () => {
|
|
74
82
|
const meta = { target: targetUrl };
|
|
75
83
|
if (currentTunnelId) meta.reclaimId = currentTunnelId; // intentar recuperar el mismo ID
|
|
84
|
+
if (customLink) meta.link = customLink;
|
|
85
|
+
if (token) meta.token = token;
|
|
76
86
|
|
|
77
87
|
socket.emit('open-tunnel', meta, (res) => {
|
|
78
88
|
if (!res || !res.ok) {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const ora = require('ora');
|
|
5
|
+
|
|
6
|
+
module.exports = async function tunnels() {
|
|
7
|
+
const { getConfig } = require('../utils/config');
|
|
8
|
+
const cfg = getConfig();
|
|
9
|
+
const token = cfg.token;
|
|
10
|
+
|
|
11
|
+
if (!token) {
|
|
12
|
+
console.error(chalk.red('Debes iniciar sesión para ver tus túneles. Ejecuta: entexto login'));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const baseUrl = (cfg.baseUrl || 'https://entexto.com').replace(/\/$/, '');
|
|
17
|
+
const wsUrl = baseUrl.replace(/^http/, 'ws') + '/tunnel';
|
|
18
|
+
|
|
19
|
+
const spinner = ora('Consultando túneles abiertos...').start();
|
|
20
|
+
|
|
21
|
+
let ioClient;
|
|
22
|
+
try {
|
|
23
|
+
ioClient = require('socket.io-client');
|
|
24
|
+
} catch {
|
|
25
|
+
spinner.fail('Falta socket.io-client. Ejecuta: npm install socket.io-client');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const socket = ioClient(wsUrl, {
|
|
30
|
+
transports: ['websocket'],
|
|
31
|
+
upgrade: false,
|
|
32
|
+
reconnection: false,
|
|
33
|
+
timeout: 10000,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
socket.on('connect', () => {
|
|
37
|
+
socket.emit('list-tunnels', { token }, (res) => {
|
|
38
|
+
if (!res || !res.ok) {
|
|
39
|
+
spinner.fail('Error: ' + (res?.error || 'sin respuesta'));
|
|
40
|
+
socket.disconnect();
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const list = res.tunnels || [];
|
|
45
|
+
spinner.stop();
|
|
46
|
+
|
|
47
|
+
if (list.length === 0) {
|
|
48
|
+
console.log(chalk.yellow('\n No tienes túneles abiertos.\n'));
|
|
49
|
+
socket.disconnect();
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(chalk.bold(`\n 🚇 Tus túneles abiertos (${list.length}):\n`));
|
|
54
|
+
list.forEach((t, i) => {
|
|
55
|
+
const status = t.connected
|
|
56
|
+
? chalk.green('● conectado')
|
|
57
|
+
: chalk.yellow('○ reconectando');
|
|
58
|
+
const url = chalk.cyan.underline(`${baseUrl}/t/${t.id}`);
|
|
59
|
+
console.log(` ${i + 1}. ${url}`);
|
|
60
|
+
console.log(` → ${t.target} ${status}${t.queued ? chalk.gray(` (${t.queued} en cola)`) : ''}`);
|
|
61
|
+
});
|
|
62
|
+
console.log('');
|
|
63
|
+
|
|
64
|
+
// Preguntar si quiere cerrar alguno
|
|
65
|
+
askClose(socket, token, list, baseUrl);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
socket.on('connect_error', (err) => {
|
|
70
|
+
spinner.fail('No se pudo conectar: ' + err.message);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
async function askClose(socket, token, list, baseUrl) {
|
|
76
|
+
let inquirer;
|
|
77
|
+
try {
|
|
78
|
+
inquirer = require('inquirer');
|
|
79
|
+
} catch {
|
|
80
|
+
socket.disconnect();
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const { action } = await inquirer.prompt([{
|
|
85
|
+
type: 'list',
|
|
86
|
+
name: 'action',
|
|
87
|
+
message: '¿Qué deseas hacer?',
|
|
88
|
+
choices: [
|
|
89
|
+
...list.map((t, i) => ({
|
|
90
|
+
name: `Cerrar /t/${t.id} (→ ${t.target})`,
|
|
91
|
+
value: t.id,
|
|
92
|
+
})),
|
|
93
|
+
{ name: 'Salir', value: null },
|
|
94
|
+
],
|
|
95
|
+
}]);
|
|
96
|
+
|
|
97
|
+
if (!action) {
|
|
98
|
+
socket.disconnect();
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const chalk = require('chalk');
|
|
103
|
+
const spinner = require('ora')('Cerrando túnel...').start();
|
|
104
|
+
|
|
105
|
+
socket.emit('close-tunnel', { token, tunnelId: action }, (res) => {
|
|
106
|
+
if (res?.ok) {
|
|
107
|
+
spinner.succeed(`Túnel /t/${action} cerrado`);
|
|
108
|
+
} else {
|
|
109
|
+
spinner.fail('Error: ' + (res?.error || 'sin respuesta'));
|
|
110
|
+
}
|
|
111
|
+
socket.disconnect();
|
|
112
|
+
process.exit(0);
|
|
113
|
+
});
|
|
114
|
+
}
|