entexto-cli 1.4.9 → 2.0.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.
@@ -0,0 +1,222 @@
1
+ 'use strict';
2
+
3
+ const inquirer = require('inquirer');
4
+ const chalk = require('chalk');
5
+ const ora = require('ora');
6
+ const { getToken } = require('../utils/config');
7
+ const {
8
+ listDomains, addDomain, addSubdomain, verifyDomain,
9
+ getProjects
10
+ } = require('../utils/api');
11
+
12
+ module.exports = async function domain(action, domainArg, options) {
13
+ if (!getToken()) {
14
+ console.log(chalk.red('\n No has iniciado sesión. Ejecuta: entexto login\n'));
15
+ process.exit(1);
16
+ }
17
+
18
+ // Si no se pasó acción, mostrar menú
19
+ if (!action) {
20
+ const { choice } = await inquirer.prompt([{
21
+ type: 'list',
22
+ name: 'choice',
23
+ message: '¿Qué quieres hacer?',
24
+ choices: [
25
+ { name: 'Listar mis dominios', value: 'list' },
26
+ { name: 'Agregar dominio personalizado', value: 'add' },
27
+ { name: 'Crear subdominio', value: 'subdomain' },
28
+ { name: 'Verificar dominio', value: 'verify' },
29
+ ],
30
+ }]);
31
+ action = choice;
32
+ }
33
+
34
+ switch (action) {
35
+ case 'list': return await doList();
36
+ case 'ls': return await doList();
37
+ case 'add': return await doAdd(domainArg, options);
38
+ case 'sub': return await doSubdomain(domainArg, options);
39
+ case 'subdomain': return await doSubdomain(domainArg, options);
40
+ case 'verify': return await doVerify(domainArg);
41
+ default:
42
+ console.log(chalk.red('Accion no reconocida: ' + action));
43
+ console.log(chalk.gray(' Acciones: list, add, subdomain, verify'));
44
+ }
45
+ };
46
+
47
+ // ─── LISTAR ──────────────────────────────────────────────
48
+ async function doList() {
49
+ const spinner = ora('Cargando dominios...').start();
50
+ try {
51
+ const domains = await listDomains();
52
+ spinner.stop();
53
+ if (!domains || domains.length === 0) {
54
+ console.log(chalk.yellow('\n No tienes dominios configurados.\n'));
55
+ return;
56
+ }
57
+ console.log(chalk.bold.cyan('\n Tus dominios:\n'));
58
+ console.log(chalk.gray(' ' + 'Dominio'.padEnd(35) + 'Proyecto'.padEnd(25) + 'Estado'));
59
+ console.log(chalk.gray(' ' + '─'.repeat(75)));
60
+ for (const d of domains) {
61
+ const estado = d.verified
62
+ ? chalk.green('✓ Verificado')
63
+ : chalk.yellow('⏳ Pendiente');
64
+ const proj = (d.project_name || '?').slice(0, 23).padEnd(25);
65
+ console.log(' ' + chalk.white(d.domain.padEnd(35)) + proj + estado);
66
+ }
67
+ console.log('');
68
+ } catch (err) {
69
+ spinner.fail(chalk.red(err.response?.data?.error || err.message));
70
+ }
71
+ }
72
+
73
+ // ─── AGREGAR DOMINIO ─────────────────────────────────────
74
+ async function doAdd(domainArg, options) {
75
+ // Seleccionar proyecto
76
+ const projects = await getProjects();
77
+ if (!projects || projects.length === 0) {
78
+ console.log(chalk.yellow(' No tienes proyectos. Crea uno primero: entexto create'));
79
+ return;
80
+ }
81
+
82
+ let projectId;
83
+ if (options && options.project) {
84
+ const found = projects.find(p => p.project_uuid === options.project || String(p.id) === options.project);
85
+ if (!found) { console.log(chalk.red(' Proyecto no encontrado.')); return; }
86
+ projectId = found.id;
87
+ } else {
88
+ const { pid } = await inquirer.prompt([{
89
+ type: 'list',
90
+ name: 'pid',
91
+ message: 'Proyecto a vincular:',
92
+ choices: projects.map(p => ({
93
+ name: `${p.name} (${p.project_uuid})`,
94
+ value: p.id,
95
+ })),
96
+ pageSize: 15,
97
+ }]);
98
+ projectId = pid;
99
+ }
100
+
101
+ // Dominio
102
+ let dom = domainArg;
103
+ if (!dom) {
104
+ const { d } = await inquirer.prompt([{
105
+ type: 'input',
106
+ name: 'd',
107
+ message: 'Dominio (ej: miapp.com):',
108
+ validate: v => /^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/.test(v.trim()) ? true : 'Formato inválido',
109
+ }]);
110
+ dom = d.trim();
111
+ }
112
+
113
+ const spinner = ora('Registrando dominio...').start();
114
+ try {
115
+ const result = await addDomain(projectId, dom);
116
+ spinner.succeed(chalk.green('Dominio registrado: ' + dom));
117
+ console.log(chalk.yellow('\n Configura tu DNS con una de estas opciones:\n'));
118
+ if (result.dns_instructions) {
119
+ const cname = result.dns_instructions.option_a_cname;
120
+ if (cname) {
121
+ console.log(chalk.bold(' Opción A (recomendada): CNAME'));
122
+ cname.steps.forEach(s => console.log(' ' + s));
123
+ }
124
+ const ns = result.dns_instructions.option_b_nameservers;
125
+ if (ns) {
126
+ console.log(chalk.bold('\n Opción B: Nameservers'));
127
+ ns.steps.forEach(s => console.log(' ' + s));
128
+ }
129
+ }
130
+ console.log(chalk.gray('\n Cuando hayas configurado el DNS, ejecuta:'));
131
+ console.log(chalk.cyan(' entexto domain verify ' + dom + '\n'));
132
+ } catch (err) {
133
+ spinner.fail(chalk.red(err.response?.data?.error || err.message));
134
+ }
135
+ }
136
+
137
+ // ─── CREAR SUBDOMINIO ────────────────────────────────────
138
+ async function doSubdomain(subArg, options) {
139
+ // Listar dominios verificados
140
+ let domains;
141
+ try { domains = await listDomains(); } catch { domains = []; }
142
+ const verified = (domains || []).filter(d => d.verified);
143
+
144
+ if (verified.length === 0) {
145
+ console.log(chalk.yellow(' No tienes dominios verificados. Primero agrega un dominio: entexto domain add'));
146
+ return;
147
+ }
148
+
149
+ const { parent } = await inquirer.prompt([{
150
+ type: 'list',
151
+ name: 'parent',
152
+ message: 'Dominio padre:',
153
+ choices: verified.map(d => ({ name: d.domain, value: d.domain })),
154
+ }]);
155
+
156
+ let sub = subArg;
157
+ if (!sub) {
158
+ const { s } = await inquirer.prompt([{
159
+ type: 'input',
160
+ name: 's',
161
+ message: `Subdominio (se creará: ___.${parent}):`,
162
+ validate: v => /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i.test(v.trim()) ? true : 'Solo letras, números y guiones',
163
+ }]);
164
+ sub = s.trim();
165
+ }
166
+
167
+ // Seleccionar proyecto
168
+ const projects = await getProjects();
169
+ let projectId;
170
+ if (options && options.project) {
171
+ const found = projects.find(p => p.project_uuid === options.project || String(p.id) === options.project);
172
+ if (!found) { console.log(chalk.red(' Proyecto no encontrado.')); return; }
173
+ projectId = found.id;
174
+ } else {
175
+ const { pid } = await inquirer.prompt([{
176
+ type: 'list',
177
+ name: 'pid',
178
+ message: 'Proyecto a vincular:',
179
+ choices: projects.map(p => ({
180
+ name: `${p.name} (${p.project_uuid})`,
181
+ value: p.id,
182
+ })),
183
+ }]);
184
+ projectId = pid;
185
+ }
186
+
187
+ const spinner = ora('Creando subdominio...').start();
188
+ try {
189
+ const result = await addSubdomain(parent, sub, projectId);
190
+ spinner.succeed(chalk.green(
191
+ `${result.domain} → Activo` + (result.dns_created ? ' (DNS automático)' : '')
192
+ ));
193
+ } catch (err) {
194
+ spinner.fail(chalk.red(err.response?.data?.error || err.message));
195
+ }
196
+ }
197
+
198
+ // ─── VERIFICAR ───────────────────────────────────────────
199
+ async function doVerify(domainArg) {
200
+ let dom = domainArg;
201
+ if (!dom) {
202
+ const { d } = await inquirer.prompt([{
203
+ type: 'input',
204
+ name: 'd',
205
+ message: 'Dominio a verificar:',
206
+ }]);
207
+ dom = d.trim();
208
+ }
209
+
210
+ const spinner = ora('Verificando DNS...').start();
211
+ try {
212
+ const result = await verifyDomain(dom);
213
+ if (result.verified) {
214
+ spinner.succeed(chalk.green(`${dom} verificado (método: ${result.method})`));
215
+ } else {
216
+ spinner.fail(chalk.red('No verificado'));
217
+ console.log(chalk.yellow(' ' + result.message));
218
+ }
219
+ } catch (err) {
220
+ spinner.fail(chalk.red(err.response?.data?.error || err.message));
221
+ }
222
+ }
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const { getToken } = require('../utils/config');
5
+
6
+ module.exports = async function live() {
7
+ if (!getToken()) {
8
+ console.log(chalk.red('\n No has iniciado sesión. Ejecuta: entexto login\n'));
9
+ process.exit(1);
10
+ }
11
+
12
+ console.log(chalk.bold.cyan(`
13
+ ╔═══════════════════════════════════════════════════════════╗
14
+ ║ EntExto Live SDK — Guía ║
15
+ ╚═══════════════════════════════════════════════════════════╝
16
+ `));
17
+
18
+ console.log(chalk.bold(' ¿Qué es Live SDK?\n'));
19
+ console.log(chalk.white(' El SDK de Live de EntExto permite agregar comunicación en'));
20
+ console.log(chalk.white(' tiempo real a cualquier proyecto: chat, videollamadas,'));
21
+ console.log(chalk.white(' audio, pantalla compartida y eventos en vivo.\n'));
22
+ console.log(chalk.white(' Funciona con WebRTC + Socket.IO + TURN server integrado.\n'));
23
+
24
+ console.log(chalk.bold(' ── Uso rápido ─────────────────────────────────────────\n'));
25
+
26
+ console.log(chalk.yellow(' // En cualquier proyecto publicado de EntExto:\n'));
27
+ console.log(chalk.green(' const sala = window.__entexto.live.sala("mi‑sala", {'));
28
+ console.log(chalk.green(' usuario: "Pedro"'));
29
+ console.log(chalk.green(' });\n'));
30
+
31
+ console.log(chalk.gray(' // Escuchar eventos:'));
32
+ console.log(chalk.green(' sala.escuchar("mensaje", (data, remitente) => {'));
33
+ console.log(chalk.green(' console.log(remitente, "dice:", data);'));
34
+ console.log(chalk.green(' });\n'));
35
+
36
+ console.log(chalk.gray(' // Enviar a todos:'));
37
+ console.log(chalk.green(' sala.emitir("mensaje", { texto: "Hola a todos" });\n'));
38
+
39
+ console.log(chalk.gray(' // Enviar a un usuario específico:'));
40
+ console.log(chalk.green(' sala.emitirA(socketId, "mensaje", data);\n'));
41
+
42
+ console.log(chalk.gray(' // Relay WebRTC (señalización):'));
43
+ console.log(chalk.green(' sala.emitir("relay", { target: peerId, sdp: offer });'));
44
+ console.log(chalk.green(' sala.onRelay((data, fromId) => { /* handle SDP/ICE */ });\n'));
45
+
46
+ console.log(chalk.gray(' // Presencia:'));
47
+ console.log(chalk.green(' sala.onEntra((usuario, socketId) => { });'));
48
+ console.log(chalk.green(' sala.onSale((usuario, socketId) => { });\n'));
49
+
50
+ console.log(chalk.bold(' ── Videollamadas WebRTC ───────────────────────────────\n'));
51
+
52
+ console.log(chalk.white(' Para videollamadas, usa la arquitectura del Proyecto 34:\n'));
53
+
54
+ console.log(chalk.green(' // 1. Obtener media local'));
55
+ console.log(chalk.green(' const stream = await navigator.mediaDevices.getUserMedia({'));
56
+ console.log(chalk.green(' video: true, audio: true'));
57
+ console.log(chalk.green(' });\n'));
58
+
59
+ console.log(chalk.green(' // 2. Obtener config TURN (automático)'));
60
+ console.log(chalk.green(' const resp = await fetch("/api/turn-config");'));
61
+ console.log(chalk.green(' const { iceServers } = await resp.json();\n'));
62
+
63
+ console.log(chalk.green(' // 3. Crear RTCPeerConnection'));
64
+ console.log(chalk.green(' const pc = new RTCPeerConnection({ iceServers });\n'));
65
+
66
+ console.log(chalk.green(' // 4. Agregar tracks y negociar via sala.emitir("relay")'));
67
+ console.log(chalk.green(' stream.getTracks().forEach(t => pc.addTrack(t, stream));\n'));
68
+
69
+ console.log(chalk.bold(' ── ICE Servers ────────────────────────────────────────\n'));
70
+
71
+ console.log(chalk.white(' STUN: stun:entexto.com:3478'));
72
+ console.log(chalk.white(' TURN: Las credenciales se obtienen de /api/turn-config'));
73
+ console.log(chalk.white(' Se generan con HMAC-SHA1, TTL de 24 horas.\n'));
74
+
75
+ console.log(chalk.bold(' ── Métodos disponibles ────────────────────────────────\n'));
76
+
77
+ const methods = [
78
+ ['sala.escuchar(evt, cb)', 'Escuchar evento de la sala'],
79
+ ['sala.emitir(evt, data)', 'Emitir a todos en la sala'],
80
+ ['sala.emitirA(id, evt, d)', 'Emitir a un socket específico'],
81
+ ['sala.onRelay(cb)', 'Recibir señalización WebRTC'],
82
+ ['sala.onEntra(cb)', 'Evento: usuario entró'],
83
+ ['sala.onSale(cb)', 'Evento: usuario salió'],
84
+ ['sala.salir()', 'Salir de la sala'],
85
+ ];
86
+ for (const [m, desc] of methods) {
87
+ console.log(' ' + chalk.cyan(m.padEnd(30)) + chalk.gray(desc));
88
+ }
89
+
90
+ console.log(chalk.bold('\n ── Plantilla con Live SDK ─────────────────────────────\n'));
91
+ console.log(chalk.gray(' Crea un proyecto con Live preconfigurado:'));
92
+ console.log(chalk.white(' entexto create → selecciona plantilla "fullstack"\n'));
93
+ console.log(chalk.gray(' O agrega manualmente a tu index.html:'));
94
+ console.log(chalk.green(' <script src="/_entexto/live.js"><\/script>\n'));
95
+
96
+ console.log(chalk.bold(' ── Referencia: Proyecto 34 ────────────────────────────\n'));
97
+ console.log(chalk.gray(' El proyecto 34 es una app tipo Zoom completa con:'));
98
+ console.log(chalk.white(' • Videollamadas con grid/PiP layout'));
99
+ console.log(chalk.white(' • Compartir pantalla'));
100
+ console.log(chalk.white(' • Chat en tiempo real'));
101
+ console.log(chalk.white(' • Controles de admin (silenciar, expulsar)'));
102
+ console.log(chalk.white(' • Selector de cámara/micrófono'));
103
+ console.log(chalk.white(' • ICE config automática con TURN fallback\n'));
104
+ console.log(chalk.gray(' Revísalo como ejemplo: entexto pull --id 34\n'));
105
+ };
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const ora = require('ora');
5
+ const inquirer = require('inquirer');
6
+ const { getToken } = require('../utils/config');
7
+ const { getProjects, publishProject } = require('../utils/api');
8
+
9
+ module.exports = async function publish(options) {
10
+ if (!getToken()) {
11
+ console.log(chalk.red('\n No has iniciado sesión. Ejecuta: entexto login\n'));
12
+ process.exit(1);
13
+ }
14
+
15
+ let uuid = options?.uuid;
16
+
17
+ if (!uuid) {
18
+ const spin = ora('Cargando proyectos...').start();
19
+ try {
20
+ const projects = await getProjects();
21
+ spin.stop();
22
+ if (!projects || projects.length === 0) {
23
+ console.log(chalk.yellow(' No tienes proyectos.'));
24
+ return;
25
+ }
26
+ const { selected } = await inquirer.prompt([{
27
+ type: 'list',
28
+ name: 'selected',
29
+ message: 'Selecciona proyecto a publicar:',
30
+ choices: projects.map(p => ({
31
+ name: `[${p.id}] ${p.name}${p.is_published ? chalk.green(' ●') : chalk.gray(' ○')}`,
32
+ value: p.uuid,
33
+ })),
34
+ }]);
35
+ uuid = selected;
36
+ } catch (err) {
37
+ spin.fail(chalk.red(err.response?.data?.error || err.message));
38
+ return;
39
+ }
40
+ }
41
+
42
+ const spinner = ora('Publicando proyecto...').start();
43
+ try {
44
+ await publishProject(uuid);
45
+ spinner.succeed(chalk.green('Proyecto publicado exitosamente'));
46
+ console.log(chalk.gray(' URL: https://entexto.com/p/' + uuid));
47
+ console.log('');
48
+ } catch (err) {
49
+ spinner.fail(chalk.red(err.response?.data?.error || err.message));
50
+ }
51
+ };
package/lib/utils/api.js CHANGED
@@ -101,4 +101,84 @@ async function deleteFileRemote(uuid, ruta) {
101
101
  return res.data;
102
102
  }
103
103
 
104
- module.exports = { login, getProjects, deployFiles, deployWithPublish, publishProject, pullProject, getManifest, streamEvents, deleteFileRemote };
104
+ // ─── Projects CRUD ─────────────────────────────────────────
105
+ async function createProject(data) {
106
+ const res = await client().post('/api/projects', data);
107
+ return res.data;
108
+ }
109
+
110
+ // ─── Dominios ──────────────────────────────────────────────
111
+ async function listDomains() {
112
+ const res = await client().get('/api/domains/list');
113
+ return res.data;
114
+ }
115
+
116
+ async function addDomain(domain, projectId) {
117
+ const res = await client().post('/api/domains/add', { domain, project_id: projectId });
118
+ return res.data;
119
+ }
120
+
121
+ async function addSubdomain(parentDomain, subdomain, projectId) {
122
+ const res = await client().post('/api/domains/add-subdomain', {
123
+ parent_domain: parentDomain,
124
+ subdomain,
125
+ project_id: projectId,
126
+ });
127
+ return res.data;
128
+ }
129
+
130
+ async function verifyDomain(domain) {
131
+ const res = await client().post('/api/domains/verify', { domain });
132
+ return res.data;
133
+ }
134
+
135
+ // ─── API Platform ──────────────────────────────────────────
136
+ async function apiCreateProject(data) {
137
+ const res = await client().post('/v1/api/projects', data);
138
+ return res.data;
139
+ }
140
+
141
+ async function apiListProjects() {
142
+ const res = await client().get('/v1/api/projects');
143
+ return res.data.projects || res.data;
144
+ }
145
+
146
+ async function apiGetProject(slug) {
147
+ const res = await client().get(`/v1/api/projects/${encodeURIComponent(slug)}`);
148
+ return res.data;
149
+ }
150
+
151
+ async function apiCreateCollection(slug, name) {
152
+ const res = await client().post(
153
+ `/v1/api/projects/${encodeURIComponent(slug)}/collections`,
154
+ { name }
155
+ );
156
+ return res.data;
157
+ }
158
+
159
+ async function apiInsertDoc(slug, collection, data) {
160
+ const res = await client().post(
161
+ `/v1/api/projects/${encodeURIComponent(slug)}/collections/${encodeURIComponent(collection)}/documents`,
162
+ { data }
163
+ );
164
+ return res.data;
165
+ }
166
+
167
+ async function apiQueryDocs(slug, collection) {
168
+ const res = await client().get(
169
+ `/v1/api/projects/${encodeURIComponent(slug)}/collections/${encodeURIComponent(collection)}/documents`
170
+ );
171
+ return res.data;
172
+ }
173
+
174
+ module.exports = {
175
+ login, getProjects, deployFiles, deployWithPublish, publishProject,
176
+ pullProject, getManifest, streamEvents, deleteFileRemote,
177
+ // Projects
178
+ createProject,
179
+ // Dominios
180
+ listDomains, addDomain, addSubdomain, verifyDomain,
181
+ // API Platform
182
+ apiCreateProject, apiListProjects, apiGetProject,
183
+ apiCreateCollection, apiInsertDoc, apiQueryDocs,
184
+ };
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "entexto-cli",
3
- "version": "1.4.9",
4
- "description": "CLI oficial de Entexto — Deploy y gestión de proyectos desde tu terminal",
3
+ "version": "2.0.0",
4
+ "description": "CLI oficial de Entexto — Crea, deploya y gestiona proyectos, dominios, APIs y Live SDK desde tu terminal",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
7
7
  "entexto": "bin/entexto.js"
8
8
  },
9
9
  "files": [
10
10
  "bin/",
11
- "lib/"
11
+ "lib/",
12
+ "docs/"
12
13
  ],
13
14
  "scripts": {
14
15
  "test": "node bin/entexto.js --version"
@@ -18,7 +19,12 @@
18
19
  "cli",
19
20
  "deploy",
20
21
  "hosting",
21
- "vfs"
22
+ "vfs",
23
+ "api",
24
+ "domains",
25
+ "live",
26
+ "webrtc",
27
+ "baas"
22
28
  ],
23
29
  "author": "reyjosias",
24
30
  "license": "MIT",