@getparallel/parx 0.0.0-stage → 0.4.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/dist/cli.js ADDED
@@ -0,0 +1,700 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from 'node:readline/promises';
3
+ import { existsSync, statSync } from 'node:fs';
4
+ import { homedir, hostname } from 'node:os';
5
+ import { basename, resolve } from 'node:path';
6
+ import { parseArgs } from 'node:util';
7
+ import { randomUUID } from 'node:crypto';
8
+ import { ALIAS_COMANDO, ALIAS_SI_NO, COMANDOS, t, USO } from "./textos.js";
9
+ import { c, fijarCapacidades, sep, signo } from "./marca.js";
10
+ import { guardarAjustes, leerAjustes, migrarCarpetaVieja, VERSION } from "./config.js";
11
+ import { MODELO_POR_DEFECTO, MODELOS, modeloDe, nombreDeModelo } from "./modelos.js";
12
+ import { correoVisible, crearMision, ErrorDeParallel, leerCuenta, quienEs, verMision } from "./api.js";
13
+ import { comoHasEntrado, credencialesActivas, entrar, formaDeLaOpcion, salir } from "./cuenta.js";
14
+ import { hacerTarea, mensajeDeError, nuevaSesion } from "./agente.js";
15
+ import { deshacerUltimo, permitirCarpeta } from "./herramientas.js";
16
+ import { Registro } from "./registro.js";
17
+ import { Pantalla } from "./ui.js";
18
+ import { acciones, bloqueDeBienvenida, bloqueDeLaPersona, duracion, Superficie } from "./tui.js";
19
+ import { CONSEJOS, cambiarConsejos, consejo, elegirConsejos, ordenDeConsejos } from "./consejos.js";
20
+ import { anchoDe, recortarMedio } from "./texto.js";
21
+ import { cargarImagen, ErrorDeImagen, extraerImagenes } from "./imagenes.js";
22
+ import { avisarEnElEscritorio, debeAvisar } from "./notificar.js";
23
+ import { copiarAlPortapapeles, escribirInforme } from "./informe.js";
24
+ import { objetivoDeMision } from "./mision.js";
25
+ import { ejecutarCarriles, MAX_CARRILES } from "./carriles.js";
26
+ import { spawnSync } from 'node:child_process';
27
+ import { comprobarDeFondo, esMasNueva, ordenDeActualizar, rutaDelCli, ultimaPublicada, versionNuevaConocida } from "./actualizar.js";
28
+ function opciones() {
29
+ return parseArgs({
30
+ allowPositionals: true,
31
+ strict: false,
32
+ options: {
33
+ mode: { type: 'string' },
34
+ model: { type: 'string' },
35
+ modo: { type: 'string' },
36
+ modelo: { type: 'string' },
37
+ bypass: { type: 'boolean' },
38
+ sandbox: { type: 'boolean' },
39
+ 'add-folder': { type: 'string', multiple: true },
40
+ lanes: { type: 'boolean' },
41
+ dir: { type: 'string', short: 'C' },
42
+ ascii: { type: 'boolean' },
43
+ version: { type: 'boolean', short: 'v' },
44
+ help: { type: 'boolean', short: 'h' },
45
+ browser: { type: 'boolean' },
46
+ web: { type: 'boolean' },
47
+ 'api-key': { type: 'boolean' },
48
+ 'with-api-key': { type: 'boolean' },
49
+ 'no-browser': { type: 'boolean' },
50
+ 'device-auth': { type: 'boolean' },
51
+ all: { type: 'boolean' },
52
+ },
53
+ });
54
+ }
55
+ function formaPedida(v) {
56
+ for (const k of ['browser', 'web', 'api-key', 'with-api-key', 'no-browser', 'device-auth']) {
57
+ if (v[k])
58
+ return formaDeLaOpcion(k);
59
+ }
60
+ return null;
61
+ }
62
+ function rutaCorta(ruta, max = 48) {
63
+ const casa = homedir();
64
+ const corta = ruta.startsWith(casa) ? `~${ruta.slice(casa.length)}` : ruta;
65
+ return recortarMedio(corta, max);
66
+ }
67
+ function cabeceraCorta(lineas = []) {
68
+ process.stdout.write(`\n ${c.violeta(signo())} ${c.negrita('parx')} ${c.gris(VERSION)}\n`);
69
+ const nueva = versionNuevaConocida();
70
+ if (nueva)
71
+ lineas = [c.gris(t('update.disponible', { version: nueva })), ...lineas];
72
+ for (const l of lineas)
73
+ process.stdout.write(` ${l}\n`);
74
+ process.stdout.write('\n');
75
+ }
76
+ export function nombreDeModo(m) {
77
+ return t(`modo.${m}`);
78
+ }
79
+ export function modoDe(texto) {
80
+ const v = (texto ?? '').trim().toLowerCase();
81
+ const tabla = { ask: 'ask', preguntar: 'ask', auto: 'auto', read: 'read', 'read-only': 'read', readonly: 'read', leer: 'read', bypass: 'bypass' };
82
+ return tabla[v] ?? null;
83
+ }
84
+ export const SIGUIENTE_MODO = { ask: 'auto', auto: 'read', read: 'ask', bypass: 'ask' };
85
+ function ayudaDeComandos() {
86
+ const ancho = Math.max(...COMANDOS.map(([k]) => anchoDe(k))) + 4;
87
+ const filas = COMANDOS.map(([k, d]) => ` ${c.marca(`/${k}`.padEnd(ancho))}${d}`);
88
+ return [...filas, '', c.gris(t('tui.atajos'))].join('\n');
89
+ }
90
+ async function cuentaOError(cred, pantalla) {
91
+ try {
92
+ return await leerCuenta(cred.servidor, cred.token);
93
+ }
94
+ catch (e) {
95
+ if (e instanceof ErrorDeParallel) {
96
+ pantalla.error(mensajeDeError(e));
97
+ return null;
98
+ }
99
+ throw e;
100
+ }
101
+ }
102
+ const dormir = (ms) => new Promise((r) => setTimeout(r, ms));
103
+ async function actualizar() {
104
+ const { gestor, orden } = ordenDeActualizar(rutaDelCli());
105
+ if (gestor === 'npx') {
106
+ process.stdout.write(`${t('update.npx')}\n`);
107
+ return 0;
108
+ }
109
+ if (gestor === 'desarrollo' || !orden) {
110
+ process.stdout.write(`${t('update.desarrollo')}\n`);
111
+ return 0;
112
+ }
113
+ const ultima = await ultimaPublicada({ plazoMs: 8000 });
114
+ if (!ultima) {
115
+ process.stderr.write(`${t('update.sin_red')}\n`);
116
+ return 1;
117
+ }
118
+ if (!esMasNueva(ultima, VERSION)) {
119
+ process.stdout.write(`${t('update.al_dia', { version: VERSION })}\n`);
120
+ return 0;
121
+ }
122
+ const texto = orden.map((p) => (/\s/.test(p) ? `"${p}"` : p)).join(' ');
123
+ process.stdout.write(`${c.gris(t('update.lanzando', { orden: texto }))}\n`);
124
+ const r = spawnSync(orden[0], orden.slice(1), { stdio: 'inherit', shell: process.platform === 'win32' });
125
+ if (r.status !== 0) {
126
+ process.stderr.write(`${c.error('✗')} ${t('update.fallo', { orden: texto })}\n`);
127
+ return 1;
128
+ }
129
+ process.stdout.write(`${c.violeta('✓')} ${t('update.hecho')} (${VERSION} → ${ultima})\n`);
130
+ return 0;
131
+ }
132
+ async function principal() {
133
+ migrarCarpetaVieja();
134
+ const { values: v, positionals } = opciones();
135
+ const ajustes = leerAjustes();
136
+ if (v.ascii)
137
+ fijarCapacidades({ unicode: false });
138
+ if (/^(parallel-code|pcode)(\.js|\.cmd|\.ps1)?$/i.test(basename(process.argv[1] ?? '')))
139
+ process.stderr.write(`${c.gris(t('nombre.nuevo'))}\n`);
140
+ const claveDelEntorno = (process.env.PARALLEL_API_KEY ?? process.env.PARX_API_KEY ?? '').trim();
141
+ if (claveDelEntorno && !/^par_key_[A-Za-z0-9_-]{40}$/.test(claveDelEntorno))
142
+ process.stderr.write(`${c.aviso('!')} ${t('entrar.entorno_mal')}\n`);
143
+ if (v.version) {
144
+ process.stdout.write(`parx ${VERSION}\n`);
145
+ return 0;
146
+ }
147
+ if (v.help) {
148
+ cabeceraCorta();
149
+ process.stdout.write(`${USO}\n`);
150
+ return 0;
151
+ }
152
+ const pantalla = new Pantalla(null);
153
+ const primero = (positionals[0] ?? '').toLowerCase();
154
+ const solo = positionals.length === 1 && !v.lanes;
155
+ if (solo && ['update', 'upgrade'].includes(primero))
156
+ return actualizar();
157
+ void comprobarDeFondo();
158
+ if (solo && ['login', 'entrar', 'switch'].includes(primero)) {
159
+ cabeceraCorta();
160
+ await entrar((l) => pantalla.escribir(l), { forma: formaPedida(v) });
161
+ return 0;
162
+ }
163
+ if (solo && ['logout', 'salir'].includes(primero)) {
164
+ await salir((l) => pantalla.escribir(l), Boolean(v.all));
165
+ return 0;
166
+ }
167
+ if (solo && ['account', 'cuenta', 'whoami', 'status', 'balance'].includes(primero)) {
168
+ const cred = credencialesActivas();
169
+ if (!cred) {
170
+ pantalla.escribir(t('cuenta.sin_sesion'));
171
+ return 1;
172
+ }
173
+ const cu = await cuentaOError(cred, pantalla);
174
+ if (!cu)
175
+ return 1;
176
+ const filas = [
177
+ [t('cuenta.cuenta'), [cu.persona, correoVisible(cu.correo)].filter(Boolean).join(sep()) || '-'],
178
+ ...(cu.plan ? [[t('cuenta.plan'), cu.plan]] : []),
179
+ [t('cuenta.acciones'), t('cuenta.saldo', { n: (cu.disponibles ?? 0).toLocaleString('en-US') })],
180
+ [t('cuenta.entrada'), comoHasEntrado(cred, cu, hostname())],
181
+ ];
182
+ const ancho = Math.max(...filas.map(([a]) => a.length)) + 2;
183
+ pantalla.escribir();
184
+ for (const [a, b] of filas)
185
+ pantalla.escribir(` ${c.gris(a.padEnd(ancho))}${b}`);
186
+ pantalla.escribir();
187
+ return 0;
188
+ }
189
+ const modoPedido = (v.mode ?? v.modo);
190
+ const pedido = modoDe(modoPedido);
191
+ if (pedido === 'bypass') {
192
+ process.stderr.write(`${c.aviso('!')} ${t('bypass.no_flag')}\n`);
193
+ return 2;
194
+ }
195
+ const quiereBypass = Boolean(v.bypass);
196
+ const modo = pedido ?? ajustes.modo ?? 'ask';
197
+ let modelo = modeloDe((v.model ?? v.modelo)) ?? ajustes.modelo ?? MODELO_POR_DEFECTO;
198
+ const raiz = resolve(v.dir ?? process.cwd());
199
+ if (!existsSync(raiz) || !statSync(raiz).isDirectory()) {
200
+ pantalla.error(`${raiz}?`);
201
+ return 1;
202
+ }
203
+ let cred = credencialesActivas();
204
+ if (!cred) {
205
+ if (!pantalla.tty) {
206
+ pantalla.escribir(t('cuenta.sin_sesion'));
207
+ return 1;
208
+ }
209
+ cabeceraCorta();
210
+ cred = await entrar((l) => pantalla.escribir(l));
211
+ }
212
+ const cuenta = await cuentaOError(cred, pantalla);
213
+ if (!cuenta)
214
+ return 1;
215
+ const registro = new Registro(randomUUID());
216
+ const conCarriles = Boolean(v.lanes);
217
+ const tarea = conCarriles ? '' : positionals.join(' ').trim();
218
+ const tareasDeCarriles = conCarriles ? positionals.map((p) => p.trim()).filter(Boolean) : [];
219
+ if (tareasDeCarriles.length > MAX_CARRILES) {
220
+ pantalla.error(t('carriles.max'));
221
+ return 2;
222
+ }
223
+ const interactiva = pantalla.tty && !tarea && !(conCarriles && !pantalla.tty);
224
+ const rl = pantalla.tty && !interactiva ? createInterface({ input: process.stdin, output: process.stdout, terminal: true, historySize: 200 }) : null;
225
+ pantalla.fijarLectora(rl);
226
+ const sesion = nuevaSesion(cred.servidor, cred.token, modelo, {
227
+ raiz,
228
+ modo,
229
+ pantalla,
230
+ registro,
231
+ permitidos: new Set(),
232
+ deshacer: [],
233
+ carpetas: [],
234
+ lecturas: [],
235
+ jaula: Boolean(v.sandbox),
236
+ });
237
+ for (const f of v['add-folder'] ?? []) {
238
+ const ok = permitirCarpeta(sesion.ctx, f);
239
+ if (!ok)
240
+ pantalla.error(t('carpeta.no_existe', { ruta: f }));
241
+ }
242
+ registro.apuntar('inicio', { version: VERSION, raiz, modo, modelo, carpetas: sesion.ctx.carpetas?.length ?? 0, jaula: Boolean(v.sandbox), plataforma: process.platform, interactiva: !tarea });
243
+ const confirmarBypass = async () => {
244
+ const r = await pantalla.preguntar(t('bypass.confirmar'), { tipo: 'bypass' });
245
+ if (r !== 'si') {
246
+ pantalla.escribir(c.gris(t('bypass.cancelado', { modo: nombreDeModo(sesion.ctx.modo) })));
247
+ return false;
248
+ }
249
+ sesion.ctx.modo = 'bypass';
250
+ sesion.ctx.permitidos.clear();
251
+ registro.apuntar('modo', { modo: 'bypass' });
252
+ return true;
253
+ };
254
+ let disponibles = cuenta.disponibles;
255
+ const saldo = () => (disponibles == null ? '' : t('cuenta.saldo', { n: disponibles.toLocaleString('en-US') }));
256
+ const saldoCorto = () => (disponibles == null ? '' : disponibles.toLocaleString('en-US'));
257
+ const quien = quienEs(cuenta);
258
+ let avisos = ajustes.avisos !== false;
259
+ let control = new AbortController();
260
+ let ocupado = false;
261
+ let superficie = null;
262
+ let ctrlC = 0;
263
+ const totalDeLaSesion = () => sesion.total.tareas ? c.gris(t('estado.total', { acciones: acciones(sesion.total.acciones), tiempo: duracion(sesion.total.ms) })) : '';
264
+ const despedida = () => [totalDeLaSesion(), c.gris(t('repl.adios'))].filter(Boolean).join('\n ');
265
+ const adios = () => {
266
+ if (superficie)
267
+ superficie.terminar(despedida());
268
+ else
269
+ pantalla.escribir(despedida());
270
+ process.exit(0);
271
+ };
272
+ const alCtrlC = () => {
273
+ ctrlC++;
274
+ if (ctrlC >= 2)
275
+ adios();
276
+ };
277
+ process.on('SIGINT', () => {
278
+ if (ocupado)
279
+ control.abort();
280
+ else
281
+ alCtrlC();
282
+ });
283
+ rl?.on('SIGINT', () => (ocupado ? control.abort() : alCtrlC()));
284
+ const quizaAvisar = (texto, ms) => {
285
+ if (!avisos)
286
+ return;
287
+ if (debeAvisar(superficie ? superficie.enfocada : null, ms))
288
+ avisarEnElEscritorio({ titulo: 'parx', texto });
289
+ };
290
+ const imagenesDe = (linea) => {
291
+ const { texto, rutas } = extraerImagenes(linea, raiz);
292
+ const imagenes = [];
293
+ for (const r of rutas) {
294
+ try {
295
+ imagenes.push(cargarImagen(r));
296
+ }
297
+ catch (e) {
298
+ const m = e instanceof ErrorDeImagen ? e.message : '';
299
+ pantalla.error(m.startsWith('grande:') ? t('imagen.grande', { mb: m.slice(7), max: '2.5' }) : t('imagen.no_se_lee', { ruta: rutaCorta(r) }));
300
+ }
301
+ }
302
+ return { texto, imagenes };
303
+ };
304
+ const hacer = async (linea) => {
305
+ const { texto, imagenes } = imagenesDe(linea);
306
+ ocupado = true;
307
+ control = new AbortController();
308
+ superficie?.trabajar(() => control.abort());
309
+ const inicio = Date.now();
310
+ let seguir = true;
311
+ try {
312
+ seguir = await hacerTarea(sesion, texto, control.signal, imagenes);
313
+ if (sesion.disponibles !== undefined && sesion.disponibles !== null)
314
+ disponibles = sesion.disponibles;
315
+ }
316
+ finally {
317
+ ocupado = false;
318
+ superficie?.descansar();
319
+ }
320
+ quizaAvisar(t('aviso.termino', { tarea: texto.replace(/\s+/g, ' ').slice(0, 80) }), Date.now() - inicio);
321
+ return seguir;
322
+ };
323
+ const carriles = async (tareas) => {
324
+ if (tareas.length < 2) {
325
+ pantalla.escribir(t('carriles.min'));
326
+ return true;
327
+ }
328
+ ocupado = true;
329
+ control = new AbortController();
330
+ pantalla.escribir(c.gris(t('carriles.empieza', { n: tareas.length })));
331
+ const inicio = Date.now();
332
+ if (superficie)
333
+ superficie.trabajar(() => control.abort());
334
+ let r;
335
+ try {
336
+ r = await ejecutarCarriles({
337
+ tareas,
338
+ servidor: cred.servidor,
339
+ token: cred.token,
340
+ modelo: sesion.modelo,
341
+ base: sesion.ctx,
342
+ superficie,
343
+ lectora: rl,
344
+ tty: pantalla.tty,
345
+ ensenar: sesion.ensenar,
346
+ señal: control.signal,
347
+ });
348
+ }
349
+ finally {
350
+ ocupado = false;
351
+ superficie?.descansar();
352
+ }
353
+ superficie?.fijarCarriles([]);
354
+ let gastadas = 0;
355
+ r.sesiones.forEach((s, i) => {
356
+ for (const d of s.diario)
357
+ sesion.diario.push({ ...d, pedido: tareas[i] ?? d.pedido });
358
+ sesion.total.acciones += s.total.acciones;
359
+ sesion.total.tareas += s.total.tareas;
360
+ gastadas += s.total.acciones;
361
+ if (s.disponibles != null)
362
+ disponibles = Math.min(disponibles ?? Infinity, s.disponibles);
363
+ });
364
+ const ms = Date.now() - inicio;
365
+ sesion.total.ms += ms;
366
+ pantalla.cierre(`${t('carriles.hecho', { n: tareas.length })}${c.gris(sep())}${c.gris(acciones(gastadas))}${c.gris(sep())}${c.gris(duracion(ms))}`);
367
+ quizaAvisar(t('carriles.hecho', { n: tareas.length }), ms);
368
+ return r.ok;
369
+ };
370
+ if (tarea) {
371
+ cabeceraCorta([c.gris([nombreDeModo(modo), nombreDeModelo(modelo), cuenta.plan, rutaCorta(raiz)].filter(Boolean).join(sep()))]);
372
+ if (quiereBypass && (await confirmarBypass()))
373
+ pantalla.aviso(t('bypass.flag'));
374
+ pantalla.bloque(bloqueDeLaPersona(tarea));
375
+ const ok = await hacer(tarea);
376
+ rl?.close();
377
+ return ok ? 0 : 1;
378
+ }
379
+ if (conCarriles && !interactiva) {
380
+ cabeceraCorta([c.gris([nombreDeModo(modo), nombreDeModelo(modelo), rutaCorta(raiz)].join(sep()))]);
381
+ if (quiereBypass && (await confirmarBypass()))
382
+ pantalla.aviso(t('bypass.flag'));
383
+ const ok = await carriles(tareasDeCarriles);
384
+ rl?.close();
385
+ return ok ? 0 : 1;
386
+ }
387
+ const estado = () => ({
388
+ modo: nombreDeModo(sesion.ctx.modo),
389
+ modelo: nombreDeModelo(sesion.modelo),
390
+ saldo: saldo(),
391
+ saldoCorto: saldoCorto(),
392
+ peligro: sesion.ctx.modo === 'bypass',
393
+ extra: sesion.ensenar ? 'Teach' : undefined,
394
+ });
395
+ if (interactiva) {
396
+ superficie = new Superficie({
397
+ entrada: process.stdin,
398
+ salida: process.stdout,
399
+ estado,
400
+ alCambiarModo: () => {
401
+ sesion.ctx.modo = SIGUIENTE_MODO[sesion.ctx.modo];
402
+ sesion.ctx.permitidos.clear();
403
+ if (sesion.ctx.modo !== 'bypass')
404
+ guardarAjustes({ modo: sesion.ctx.modo });
405
+ },
406
+ alSalir: adios,
407
+ alPreguntar: (texto) => {
408
+ if (avisos && superficie?.enfocada === false)
409
+ avisarEnElEscritorio({ titulo: 'parx', texto: t('aviso.te_necesita', { pregunta: texto }) });
410
+ },
411
+ });
412
+ const s = superficie;
413
+ process.on('exit', () => s.restaurarDeUrgencia());
414
+ pantalla.fijarSuperficie(superficie);
415
+ const elegidos = elegirConsejos(leerAjustes().consejos);
416
+ if (elegidos.ids.length)
417
+ guardarAjustes({ consejos: elegidos.estado });
418
+ superficie.iniciar(bloqueDeBienvenida({
419
+ version: VERSION,
420
+ quien: quien.includes('@') ? quien : (quien.split(/\s+/)[0] ?? ''),
421
+ carpeta: rutaCorta(raiz, 60),
422
+ plan: cuenta.plan,
423
+ novedad: (() => {
424
+ const nueva = versionNuevaConocida();
425
+ return nueva ? t('update.disponible', { version: nueva }) : undefined;
426
+ })(),
427
+ peligro: () => sesion.ctx.modo === 'bypass',
428
+ }), conCarriles || quiereBypass ? [] : elegidos.ids);
429
+ if (quiereBypass)
430
+ await confirmarBypass();
431
+ }
432
+ const leerLinea = async (ejemplo = null) => {
433
+ if (superficie)
434
+ return superficie.leer(ejemplo);
435
+ try {
436
+ return await rl.question(`${c.claro(signo())} `);
437
+ }
438
+ catch {
439
+ return null;
440
+ }
441
+ };
442
+ if (conCarriles) {
443
+ const ok = await carriles(tareasDeCarriles.length >= 2 ? tareasDeCarriles : await pedirCarriles());
444
+ if (!ok) {
445
+ superficie?.terminar(despedida());
446
+ return 1;
447
+ }
448
+ }
449
+ async function pedirCarriles() {
450
+ pantalla.escribir(c.gris(t('carriles.cuantas')));
451
+ const tareas = [];
452
+ while (tareas.length < MAX_CARRILES) {
453
+ const l = await leerLinea(t('tui.escribe_carril', { n: tareas.length + 1 }));
454
+ if (l === null || !l.trim())
455
+ break;
456
+ tareas.push(l.trim());
457
+ }
458
+ return tareas;
459
+ }
460
+ for (;;) {
461
+ const r = await leerLinea();
462
+ if (r === null)
463
+ break;
464
+ const linea = r.trim();
465
+ ctrlC = 0;
466
+ if (!linea)
467
+ continue;
468
+ if (linea.startsWith('/')) {
469
+ const [nombre = '', ...args] = linea.slice(1).split(/\s+/);
470
+ const cmd = ALIAS_COMANDO[nombre.toLowerCase()];
471
+ const resto = linea.slice(1 + nombre.length).trim();
472
+ const arg = (args[0] ?? '').toLowerCase();
473
+ switch (cmd) {
474
+ case 'exit':
475
+ if (superficie)
476
+ superficie.terminar(despedida());
477
+ else
478
+ pantalla.escribir(despedida());
479
+ rl?.close();
480
+ return 0;
481
+ case 'help':
482
+ pantalla.escribir(ayudaDeComandos());
483
+ break;
484
+ case 'mode': {
485
+ const m = modoDe(arg);
486
+ if (m === 'bypass') {
487
+ if (sesion.ctx.modo === 'bypass')
488
+ break;
489
+ if ((await confirmarBypass()) && !superficie)
490
+ pantalla.aviso(t('bypass.on'));
491
+ superficie?.pintar();
492
+ break;
493
+ }
494
+ if (m) {
495
+ sesion.ctx.modo = m;
496
+ sesion.ctx.permitidos.clear();
497
+ guardarAjustes({ modo: m });
498
+ registro.apuntar('modo', { modo: m });
499
+ }
500
+ pantalla.escribir(t('modo.ahora', { modo: nombreDeModo(sesion.ctx.modo), desc: t(`modo.${sesion.ctx.modo}.desc`) }));
501
+ superficie?.pintar();
502
+ break;
503
+ }
504
+ case 'model': {
505
+ const m = modeloDe(resto);
506
+ if (m) {
507
+ modelo = m;
508
+ sesion.modelo = m;
509
+ guardarAjustes({ modelo: m });
510
+ }
511
+ pantalla.escribir(t('modelo.ahora', { modelo: nombreDeModelo(sesion.modelo), desc: MODELOS[sesion.modelo].descripcion }));
512
+ superficie?.pintar();
513
+ break;
514
+ }
515
+ case 'folder': {
516
+ if (!resto) {
517
+ const lista = sesion.ctx.carpetas ?? [];
518
+ pantalla.escribir(lista.length ? t('carpeta.lista', { lista: lista.map((x) => rutaCorta(x, 60)).join(', ') }) : t('carpeta.ninguna'));
519
+ break;
520
+ }
521
+ const ruta = resto.replace(/^['"]|['"]$/g, '').replace(/\\(.)/g, '$1');
522
+ const ok = permitirCarpeta(sesion.ctx, ruta);
523
+ registro.apuntar('carpeta', { ok: Boolean(ok) });
524
+ pantalla.escribir(ok ? t('carpeta.ok', { ruta: rutaCorta(ok, 60) }) : t('carpeta.no_existe', { ruta }));
525
+ break;
526
+ }
527
+ case 'lanes': {
528
+ const seguir = await carriles(await pedirCarriles());
529
+ if (!seguir) {
530
+ superficie?.terminar(despedida());
531
+ rl?.close();
532
+ return 1;
533
+ }
534
+ break;
535
+ }
536
+ case 'teach': {
537
+ const si = ALIAS_SI_NO[arg] ?? (sesion.ensenar ? 'no' : 'si');
538
+ sesion.ensenar = si === 'si';
539
+ pantalla.escribir(sesion.ensenar ? t('repl.ensenar_on') : t('repl.ensenar_off'));
540
+ superficie?.pintar();
541
+ break;
542
+ }
543
+ case 'report': {
544
+ if (!sesion.diario.length) {
545
+ pantalla.escribir(t('informe.nada'));
546
+ break;
547
+ }
548
+ ocupado = true;
549
+ control = new AbortController();
550
+ superficie?.trabajar(() => control.abort());
551
+ pantalla.espera(t('hace.informe'), 'tendido', true);
552
+ try {
553
+ const inf = await escribirInforme(cred.servidor, cred.token, sesion.id, sesion.diario, basename(raiz), control.signal);
554
+ pantalla.pararEspera();
555
+ if (inf.disponibles != null)
556
+ disponibles = inf.disponibles;
557
+ sesion.total.acciones += inf.acciones;
558
+ pantalla.escribir();
559
+ pantalla.texto(`**${t('informe.titulo')}**\n\n${inf.texto}`);
560
+ pantalla.escribir();
561
+ pantalla.cierre(`${copiarAlPortapapeles(inf.texto) ? t('informe.copiado') : t('informe.titulo')}${c.gris(sep())}${c.gris(acciones(inf.acciones))}`);
562
+ }
563
+ catch (e) {
564
+ pantalla.pararEspera();
565
+ if (e instanceof ErrorDeParallel) {
566
+ if (e.codigo !== 'interrumpido')
567
+ pantalla.error(mensajeDeError(e));
568
+ }
569
+ else
570
+ throw e;
571
+ }
572
+ finally {
573
+ ocupado = false;
574
+ superficie?.descansar();
575
+ }
576
+ break;
577
+ }
578
+ case 'mission': {
579
+ const objetivo = objetivoDeMision(resto, sesion.diario);
580
+ if (!objetivo) {
581
+ pantalla.escribir(t('mision.nada'));
582
+ break;
583
+ }
584
+ pantalla.escribir(c.gris(t('mision.explica')));
585
+ ocupado = true;
586
+ control = new AbortController();
587
+ superficie?.trabajar(() => control.abort());
588
+ pantalla.espera(t('hace.mision'), 'ruta', true);
589
+ try {
590
+ const m = await crearMision(cred.servidor, cred.token, objetivo, control.signal);
591
+ registro.apuntar('mision', { generacion: m.generacion });
592
+ pantalla.pararEspera();
593
+ pantalla.escribir(t('mision.creada', { url: c.marca(m.url) }));
594
+ pantalla.escribir(c.gris(t('mision.plan')));
595
+ let vista = m;
596
+ for (let i = 0; i < 20 && !control.signal.aborted && vista.estado !== 'launched' && vista.estado !== 'failed'; i++) {
597
+ await dormir(3000);
598
+ try {
599
+ vista = await verMision(cred.servidor, cred.token, m.generacion, control.signal);
600
+ }
601
+ catch {
602
+ break;
603
+ }
604
+ }
605
+ if (vista.estado === 'launched')
606
+ pantalla.cierre(t('mision.lanzada'));
607
+ else if (vista.estado === 'failed')
608
+ pantalla.error(t('mision.no_disponible'));
609
+ }
610
+ catch (e) {
611
+ pantalla.pararEspera();
612
+ if (e instanceof ErrorDeParallel) {
613
+ if (e.codigo === 'sin_saldo')
614
+ pantalla.error(t('mision.limite'));
615
+ else if (e.detalle === 'mantenimiento')
616
+ pantalla.error(t('mision.cerradas'));
617
+ else if (e.codigo !== 'interrumpido')
618
+ pantalla.error(mensajeDeError(e));
619
+ }
620
+ else
621
+ throw e;
622
+ }
623
+ finally {
624
+ ocupado = false;
625
+ superficie?.descansar();
626
+ }
627
+ break;
628
+ }
629
+ case 'new':
630
+ sesion.conversacion = [];
631
+ pantalla.escribir(t('repl.nueva'));
632
+ break;
633
+ case 'undo': {
634
+ const r2 = deshacerUltimo(sesion.ctx);
635
+ pantalla.escribir(r2 ? t('repl.deshecho', { ruta: r2 }) : t('repl.nada_que_deshacer'));
636
+ if (r2)
637
+ sesion.conversacion.push({ role: 'user', content: `(The user undid your last change to ${r2}. It is back to how it was before.)` });
638
+ break;
639
+ }
640
+ case 'balance': {
641
+ const cu = await cuentaOError(cred, pantalla);
642
+ if (cu) {
643
+ disponibles = cu.disponibles;
644
+ const yo = quienEs(cu);
645
+ const n = (cu.disponibles ?? 0).toLocaleString('en-US');
646
+ pantalla.escribir(yo ? t('cuenta.quien', { correo: yo, n }) : t('cuenta.saldo', { n }));
647
+ superficie?.pintar();
648
+ }
649
+ break;
650
+ }
651
+ case 'notify': {
652
+ const si = ALIAS_SI_NO[arg] ?? (avisos ? 'no' : 'si');
653
+ avisos = si === 'si';
654
+ guardarAjustes({ avisos });
655
+ pantalla.escribir(avisos ? t('repl.avisos_on') : t('repl.avisos_off'));
656
+ break;
657
+ }
658
+ case 'log':
659
+ pantalla.escribir(t('repl.registro', { ruta: registro.ruta }));
660
+ break;
661
+ case 'tips': {
662
+ const si = ALIAS_SI_NO[arg];
663
+ if (si) {
664
+ guardarAjustes({ consejos: cambiarConsejos(leerAjustes().consejos, si === 'si') });
665
+ pantalla.escribir(si === 'si' ? t('consejos.encendidos') : t('consejos.apagados', { orden: ordenDeConsejos(true) }));
666
+ }
667
+ else {
668
+ const todos = CONSEJOS.map((id) => consejo(id));
669
+ const ancho = Math.max(...todos.map((k) => anchoDe(k.tecla))) + 3;
670
+ const filas = todos.map((k) => ` ${c.marca(k.tecla + ' '.repeat(Math.max(0, ancho - anchoDe(k.tecla))))}${k.texto}`);
671
+ const apagados = leerAjustes().consejos?.apagados;
672
+ const pie = apagados ? t('consejos.apagados', { orden: ordenDeConsejos(true) }) : t('consejos.quitar', { orden: ordenDeConsejos(false) });
673
+ pantalla.escribir([c.negrita(t('consejos.titulo')), ...filas, '', c.gris(pie)].join('\n'));
674
+ }
675
+ break;
676
+ }
677
+ default:
678
+ pantalla.escribir(t('repl.desconocido'));
679
+ }
680
+ continue;
681
+ }
682
+ const seguir = await hacer(linea);
683
+ if (!seguir) {
684
+ superficie?.terminar(despedida());
685
+ rl?.close();
686
+ return 1;
687
+ }
688
+ }
689
+ superficie?.terminar(despedida());
690
+ rl?.close();
691
+ return 0;
692
+ }
693
+ if (!process.env.PARX_PRUEBAS) {
694
+ principal().then((codigo) => process.exit(codigo), (e) => {
695
+ if (e instanceof ErrorDeParallel && e.codigo === 'interrumpido')
696
+ process.exit(130);
697
+ process.stderr.write(`${c.error('✗')} ${e instanceof ErrorDeParallel ? mensajeDeError(e) : e instanceof Error ? e.message : String(e)}\n`);
698
+ process.exit(1);
699
+ });
700
+ }