@getparallel/parx 0.5.0 → 0.6.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/CHANGELOG.md +8 -0
- package/README.md +11 -5
- package/dist/actualizar.js +2 -1
- package/dist/agente.js +82 -2
- package/dist/api.js +14 -0
- package/dist/cli.js +193 -34
- package/dist/config.js +4 -0
- package/dist/estimacion.js +118 -0
- package/dist/firmas.js +55 -0
- package/dist/marca.js +56 -19
- package/dist/par.js +355 -0
- package/dist/remoto.js +284 -0
- package/dist/tar.js +99 -0
- package/dist/textos.js +44 -7
- package/dist/tui.js +56 -10
- package/dist/ui.js +35 -2
- package/dist/versiones.js +341 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ changes). Entries are generated by `npm run release` from the git history of `ap
|
|
|
6
6
|
|
|
7
7
|
<!-- Notes written under «## Unreleased» are used as the next entry instead of the git log. -->
|
|
8
8
|
|
|
9
|
+
## 0.6.0 (2026-10-18)
|
|
10
|
+
|
|
11
|
+
- parx updates itself. Every few hours it checks npm in the background; a new version is downloaded, verified (npm's registry signature against npm's public key pinned in parx, plus the package's SHA-512) and start-tested, then kept ready in `~/.config/parx/versiones`. It starts with your next session, never mid-session, with one line: «parx 0.5.1 updated · what's new». If a new version fails to start, parx goes back to the previous one on its own. `/autoupdate off` (or `PARX_NO_AUTO_UPDATE=1`) keeps the old behaviour: a line when there is a new version, and `parx update` installs it.
|
|
12
|
+
- `parx update` now installs with the same verification, right away, and tells you the version is ready for your next session. Through `npx` or from a development copy, it just says so.
|
|
13
|
+
- `/undo` survives `/new`: the files you changed are still on disk when the conversation is gone, so the last change can still be undone.
|
|
14
|
+
- Before each task, one grey line says what it will roughly cost, «~150 Actions (estimate)», and, when a cheaper model would clearly do (a question, a small change), «with Swift: ~12». The estimate learns from your own tasks: after each one it compares estimate and real cost and corrects itself, per kind of task and model (`~/.config/parx/estimaciones.json`).
|
|
15
|
+
- parx spends less: what the last turns are using stays whole, but older tool outputs are trimmed and files already written are replaced by a short note (they are on disk), so each turn re-sends far less; reasoning from earlier tasks is dropped; and the instructions ask for several tools per turn, no re-reading of files just written, one verification at the end and no fetching of external resources unless asked. A simple one-page site that cost 436 Actions with Stride now costs a fraction (measured below).
|
|
16
|
+
|
|
9
17
|
## 0.5.0 (2026-10-18)
|
|
10
18
|
|
|
11
19
|
- `/missions`: your Parallel missions in a list; pick one and open it, pause or resume it (whichever fits its state), share a read-only link (copied), see its result or what the agents did, without leaving parx. `/missions bakery` filters by title.
|
package/README.md
CHANGED
|
@@ -33,11 +33,17 @@ You need a [Parallel](https://getparallel.net) account: each task is charged in
|
|
|
33
33
|
|
|
34
34
|
## Updates
|
|
35
35
|
|
|
36
|
-
parx
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
parx keeps itself up to date. Every few hours it checks npm in the background (short timeout, never
|
|
37
|
+
slows anything down); when there is a new version it downloads it, verifies it is the one Parallel
|
|
38
|
+
published (npm's registry signature, checked against npm's public key pinned in parx, plus the
|
|
39
|
+
package's SHA-512) and that it starts, and keeps it ready in its own folder (`~/.config/parx/versiones`).
|
|
40
|
+
Nothing changes mid-session: the new version starts with your next session, with one discreet line,
|
|
41
|
+
«parx 0.5.1 updated · …». If a new version fails to start, parx goes back to the previous one by itself.
|
|
42
|
+
|
|
43
|
+
`parx update` fetches the latest version right now, with the same checks. `/autoupdate off` (or
|
|
44
|
+
`PARX_NO_AUTO_UPDATE=1`) makes parx only tell you, «parx 0.5.1 available · run `parx update`».
|
|
45
|
+
`PARX_NO_UPDATE_CHECK=1` (or `NO_UPDATE_NOTIFIER=1`) turns the check off entirely; it is also off in
|
|
46
|
+
CI, through `npx` (which already fetches the latest) and in a development copy of the repo.
|
|
41
47
|
|
|
42
48
|
Every change ships as a new version ([semver](https://semver.org)); see the
|
|
43
49
|
[changelog](https://www.npmjs.com/package/@getparallel/parx?activeTab=code) (`CHANGELOG.md` in the package).
|
package/dist/actualizar.js
CHANGED
|
@@ -4,6 +4,7 @@ import { carpetaDeConfiguracion, VERSION } from "./config.js";
|
|
|
4
4
|
export const PAQUETE = '@getparallel/parx';
|
|
5
5
|
export const PAQUETE_EN_URL = PAQUETE.replace('/', '%2f');
|
|
6
6
|
export const UN_DIA_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
export const ENTRE_COMPROBACIONES_MS = 6 * 60 * 60 * 1000;
|
|
7
8
|
const PLAZO_MS = 2500;
|
|
8
9
|
const FICHERO = 'actualizacion.json';
|
|
9
10
|
export function comprobacionApagada(env = process.env) {
|
|
@@ -72,7 +73,7 @@ export async function comprobarDeFondo(opts = {}) {
|
|
|
72
73
|
const ahora = opts.ahora ?? Date.now();
|
|
73
74
|
const e = leerEstado(dir);
|
|
74
75
|
const hace = e ? ahora - Date.parse(e.comprobada) : Infinity;
|
|
75
|
-
if (e && hace >= 0 && hace <
|
|
76
|
+
if (e && hace >= 0 && hace < ENTRE_COMPROBACIONES_MS)
|
|
76
77
|
return esMasNueva(e.ultima, opts.actual ?? VERSION) ? e.ultima : null;
|
|
77
78
|
const ultima = await ultimaPublicada(opts);
|
|
78
79
|
guardarEstado(dir, { comprobada: new Date(ahora).toISOString(), ultima: ultima ?? e?.ultima ?? opts.actual ?? VERSION });
|
package/dist/agente.js
CHANGED
|
@@ -8,8 +8,12 @@ import { c, sep as separador } from "./marca.js";
|
|
|
8
8
|
import { descripcionDelSistema, esRepoGit } from "./shell.js";
|
|
9
9
|
import { MODELOS } from "./modelos.js";
|
|
10
10
|
import { acciones as textoDeAcciones, duracion } from "./tui.js";
|
|
11
|
+
import { aprenderDeLaTarea, estimarTarea, leerCalibracion, lineaDeEstimacion } from "./estimacion.js";
|
|
11
12
|
const MAX_VUELTAS = 80;
|
|
12
|
-
const MAX_CARACTERES_DE_CONTEXTO =
|
|
13
|
+
const MAX_CARACTERES_DE_CONTEXTO = 400_000;
|
|
14
|
+
const SALIDA_VIEJA_MAX = 1_500;
|
|
15
|
+
const SALIDA_VIEJA_CABEZA = 500;
|
|
16
|
+
const CONTENIDO_VIEJO_MAX = 600;
|
|
13
17
|
function notasDelProyecto(raiz) {
|
|
14
18
|
for (const nombre of ['PARALLEL.md', 'AGENTS.md']) {
|
|
15
19
|
const ruta = join(raiz, nombre);
|
|
@@ -55,6 +59,7 @@ export function instrucciones(raiz, modo, o = {}) {
|
|
|
55
59
|
- Read a file before editing it. Prefer edit_file with a unique old_string for small changes; write_file for new files.
|
|
56
60
|
- Keep changes minimal and in the style of the codebase. Do not reformat unrelated code. Do not add dependencies unless needed.
|
|
57
61
|
- Commands are non-interactive (no stdin, CI=1). Use flags that do not prompt (e.g. "npx --yes", "vitest run", "git --no-pager"). Prefer project scripts (npm test).
|
|
62
|
+
- Spend little. Every turn re-sends the whole conversation, so fewer turns and shorter tool outputs cost the user less. Call several independent tools in ONE turn (list, search and read together; write several files together). Read only the files and line ranges you need; do not re-read a file you just wrote. For a small or simple task (a page, a script, a fix) skip exploring beyond one list_files and write the files directly. Verify once, at the end, with the project's own test or build command; if there is none (a static page, a script), one syntax check at most and then finish: do not invent checks with sed, grep or ad-hoc scripts, and do not re-read files to confirm what you just wrote. Do not fetch external resources (images, URLs, packages) unless the user asked for them.
|
|
58
63
|
- Credentials (.env files, keys, tokens, ~/.ssh, ~/.aws, keychains...) always need the user's explicit confirmation, every time, and parx asks it for you: when the user asks for something that needs a credential, just call the tool and parx shows the question. Do not refuse on your own and do not ask in text first. Touch credentials only if the task needs them, never copy them anywhere, and do not repeat secret values in your answer unless the user explicitly asked to see them.
|
|
59
64
|
- Never push, publish or deploy unless the user explicitly asks in this conversation.
|
|
60
65
|
- If the user declines an action, do not retry it in the same task: adapt or ask. If they ask for it again in a later message, try again (parx will ask them again).
|
|
@@ -69,6 +74,63 @@ export function instrucciones(raiz, modo, o = {}) {
|
|
|
69
74
|
export function nuevaSesion(servidor, token, modelo, ctx) {
|
|
70
75
|
return { id: randomUUID(), servidor, token, modelo, ctx, conversacion: [], total: { acciones: 0, ms: 0, tareas: 0 }, diario: [] };
|
|
71
76
|
}
|
|
77
|
+
export function reiniciarConversacion(s) {
|
|
78
|
+
s.conversacion = [];
|
|
79
|
+
s.diario = [];
|
|
80
|
+
s.id = randomUUID();
|
|
81
|
+
s.ultimaTarea = undefined;
|
|
82
|
+
s.ctx.hechos = { ficheros: [], comandos: [] };
|
|
83
|
+
s.ctx.permitidos.clear();
|
|
84
|
+
if (s.ctx.lecturas)
|
|
85
|
+
s.ctx.lecturas.length = 0;
|
|
86
|
+
}
|
|
87
|
+
function notaDeContenido(texto) {
|
|
88
|
+
const lineas = texto.split('\n').length;
|
|
89
|
+
return `[${texto.length} characters, ${lineas} lines: written to disk as requested]`;
|
|
90
|
+
}
|
|
91
|
+
export function compactarConversacion(items, l) {
|
|
92
|
+
let ahorro = 0;
|
|
93
|
+
const desde = Math.max(0, l.desde);
|
|
94
|
+
const limite = Math.min(items.length, Math.max(desde, l.limite));
|
|
95
|
+
const limiteEscrituras = Math.min(items.length, Math.max(desde, l.limiteEscrituras ?? l.limite));
|
|
96
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
97
|
+
const it = items[i];
|
|
98
|
+
const viejo = i < limite || i < desde;
|
|
99
|
+
const escrito = i < limiteEscrituras || i < desde;
|
|
100
|
+
if (!viejo && !escrito)
|
|
101
|
+
continue;
|
|
102
|
+
if (it.type === 'reasoning' && i < desde) {
|
|
103
|
+
ahorro += JSON.stringify(it).length;
|
|
104
|
+
items.splice(i, 1);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const salida = viejo && it.type === 'function_call_output' && typeof it.output === 'string' ? it.output : null;
|
|
108
|
+
if (salida !== null && salida.length > SALIDA_VIEJA_MAX) {
|
|
109
|
+
const nueva = `${salida.slice(0, SALIDA_VIEJA_CABEZA)}\n[… ${salida.length - SALIDA_VIEJA_CABEZA} more characters removed to save space; call the tool again if needed]`;
|
|
110
|
+
it.output = nueva;
|
|
111
|
+
ahorro += salida.length - nueva.length;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (escrito && it.type === 'function_call' && (it.name === 'write_file' || it.name === 'edit_file') && typeof it.arguments === 'string' && it.arguments.length > CONTENIDO_VIEJO_MAX) {
|
|
115
|
+
try {
|
|
116
|
+
const args = JSON.parse(it.arguments);
|
|
117
|
+
const campo = it.name === 'write_file' ? 'content' : 'new_string';
|
|
118
|
+
const texto = args[campo];
|
|
119
|
+
if (typeof texto === 'string' && texto.length > CONTENIDO_VIEJO_MAX && !texto.startsWith('[')) {
|
|
120
|
+
args[campo] = notaDeContenido(texto);
|
|
121
|
+
if (it.name === 'edit_file' && typeof args.old_string === 'string' && args.old_string.length > CONTENIDO_VIEJO_MAX)
|
|
122
|
+
args.old_string = `[${args.old_string.length} characters]`;
|
|
123
|
+
const nuevo = JSON.stringify(args);
|
|
124
|
+
ahorro += it.arguments.length - nuevo.length;
|
|
125
|
+
it.arguments = nuevo;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return ahorro;
|
|
133
|
+
}
|
|
72
134
|
function recortarContexto(items) {
|
|
73
135
|
let total = JSON.stringify(items).length;
|
|
74
136
|
for (let i = 0; i < items.length - 8 && total > MAX_CARACTERES_DE_CONTEXTO; i++) {
|
|
@@ -116,6 +178,17 @@ export async function hacerTarea(s, pedido, señal, imagenes = []) {
|
|
|
116
178
|
}
|
|
117
179
|
: { role: 'user', content: texto });
|
|
118
180
|
registro.apuntar('tarea', { tarea, caracteres: pedido.length, imagenes: imagenes.length, modelo: s.modelo, modo: s.ctx.modo, ensenar: Boolean(s.ensenar) });
|
|
181
|
+
const desde = s.conversacion.length - 1;
|
|
182
|
+
compactarConversacion(s.conversacion, { desde, limite: desde });
|
|
183
|
+
const vueltas = [];
|
|
184
|
+
let estimacion = null;
|
|
185
|
+
if (s.tarifas) {
|
|
186
|
+
s.calibracion ??= leerCalibracion();
|
|
187
|
+
const contextoChars = instrucciones(s.ctx.raiz, s.ctx.modo, { carpetas: s.ctx.carpetas, ensenar: s.ensenar }).length + JSON.stringify(DEFINICIONES).length + JSON.stringify(s.conversacion).length;
|
|
188
|
+
estimacion = estimarTarea({ pedido, contextoChars, tarifas: s.tarifas, calibracion: s.calibracion });
|
|
189
|
+
pantalla.escribir(c.gris(lineaDeEstimacion(estimacion, s.modelo)));
|
|
190
|
+
registro.apuntar('estimacion', { tarea, clase: estimacion.clase, estimada: estimacion.acciones[s.modelo] });
|
|
191
|
+
}
|
|
119
192
|
let acciones = 0;
|
|
120
193
|
let disponibles = null;
|
|
121
194
|
let respuesta = '';
|
|
@@ -139,6 +212,9 @@ export async function hacerTarea(s, pedido, señal, imagenes = []) {
|
|
|
139
212
|
for (let vuelta = 0; vuelta < MAX_VUELTAS; vuelta++) {
|
|
140
213
|
if (señal.aborted)
|
|
141
214
|
throw new ErrorDeParallel('interrumpido');
|
|
215
|
+
vueltas.push(s.conversacion.length);
|
|
216
|
+
if (vuelta > 0)
|
|
217
|
+
compactarConversacion(s.conversacion, { desde, limite: vueltas[vueltas.length - 2] ?? desde });
|
|
142
218
|
recortarContexto(s.conversacion);
|
|
143
219
|
pantalla.espera(null);
|
|
144
220
|
const r = await turno(s.servidor, s.token, {
|
|
@@ -206,7 +282,9 @@ export async function hacerTarea(s, pedido, señal, imagenes = []) {
|
|
|
206
282
|
s.ultimaTarea = { acciones, ms };
|
|
207
283
|
s.disponibles = disponibles;
|
|
208
284
|
apuntarDiario();
|
|
209
|
-
registro.apuntar('tarea_hecha', { tarea, acciones, ms });
|
|
285
|
+
registro.apuntar('tarea_hecha', { tarea, acciones, ms, ...(estimacion ? { estimada: estimacion.acciones[s.modelo], clase: estimacion.clase } : {}) });
|
|
286
|
+
if (estimacion && acciones > 0)
|
|
287
|
+
s.calibracion = aprenderDeLaTarea({ clase: estimacion.clase, modelo: s.modelo, estimada: estimacion.acciones[s.modelo], real: acciones, calibracion: s.calibracion });
|
|
210
288
|
const sep = c.gris(separador());
|
|
211
289
|
pantalla.cierre(`${t('estado.hecho')}${sep}${c.gris(textoDeAcciones(acciones))}${sep}${c.gris(duracion(ms))}`);
|
|
212
290
|
return true;
|
|
@@ -253,6 +331,8 @@ export function mensajeDeError(e) {
|
|
|
253
331
|
return t('error.sin_acceso');
|
|
254
332
|
case 'demasiado':
|
|
255
333
|
return t('error.demasiado');
|
|
334
|
+
case 'ia':
|
|
335
|
+
return t('error.ia');
|
|
256
336
|
case 'red':
|
|
257
337
|
return t('error.red', { detalle: e.detalle });
|
|
258
338
|
default:
|
package/dist/api.js
CHANGED
|
@@ -59,6 +59,8 @@ async function llamar(servidor, ruta, init = {}) {
|
|
|
59
59
|
throw new ErrorDeParallel('servidor', 'mantenimiento');
|
|
60
60
|
if (r.status === 413)
|
|
61
61
|
throw new ErrorDeParallel('demasiado');
|
|
62
|
+
if ((r.status === 502 || r.status === 503 || r.status === 504) && (error === 'no_disponible' || error === 'servicio' || error === 'plazo' || error === ''))
|
|
63
|
+
throw new ErrorDeParallel('ia', `${r.status}${error ? ` ${error}` : ''}`);
|
|
62
64
|
throw new ErrorDeParallel('servidor', `${r.status}${error ? ` ${error}` : ''}`);
|
|
63
65
|
}
|
|
64
66
|
export function pedirCodigo(servidor) {
|
|
@@ -117,3 +119,15 @@ export function quienEs(cu) {
|
|
|
117
119
|
return nombre;
|
|
118
120
|
return correoVisible(cu?.correo);
|
|
119
121
|
}
|
|
122
|
+
export function abrirRemoto(servidor, token, datos) {
|
|
123
|
+
return llamar(servidor, '/api/terminal/remoto', { token, cuerpo: datos, plazoMs: 20_000 });
|
|
124
|
+
}
|
|
125
|
+
export function latidoRemoto(servidor, token, id, cuerpo, señal) {
|
|
126
|
+
return llamar(servidor, `/api/terminal/remoto/${encodeURIComponent(id)}`, { token, cuerpo, señal, plazoMs: 20_000 });
|
|
127
|
+
}
|
|
128
|
+
export function ordenesRemotas(servidor, token, id, esperaMs, señal) {
|
|
129
|
+
return llamar(servidor, `/api/terminal/remoto/${encodeURIComponent(id)}/ordenes?espera=${esperaMs}`, { token, señal, plazoMs: esperaMs + 15_000 });
|
|
130
|
+
}
|
|
131
|
+
export function cerrarRemoto(servidor, token, id, plazoMs = 3000) {
|
|
132
|
+
return llamar(servidor, `/api/terminal/remoto/${encodeURIComponent(id)}`, { token, metodo: 'DELETE', plazoMs });
|
|
133
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -6,16 +6,18 @@ import { basename, resolve } from 'node:path';
|
|
|
6
6
|
import { parseArgs } from 'node:util';
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
8
|
import { ALIAS_COMANDO, ALIAS_SI_NO, COMANDOS, t, USO } from "./textos.js";
|
|
9
|
-
import { c, colorDeModo, fijarCapacidades, sep, signo, SIMBOLOS } from "./marca.js";
|
|
9
|
+
import { c, capacidades, colorDeModo, fijarCapacidades, sep, signo, sinColor, SIMBOLOS } from "./marca.js";
|
|
10
10
|
import { guardarAjustes, leerAjustes, migrarCarpetaVieja, VERSION } from "./config.js";
|
|
11
11
|
import { MODELO_POR_DEFECTO, MODELOS, modeloDe, nombreDeModelo } from "./modelos.js";
|
|
12
|
-
import { cicloDeMision, compartirMision, correoVisible, crearMision, ErrorDeParallel, leerBandeja, leerConexiones, leerCuenta, leerMisionDeLaCuenta, leerMisiones, leerPlantillas, quienEs, responderBandeja, verMision, } from "./api.js";
|
|
12
|
+
import { cicloDeMision, compartirMision, correoVisible, crearMision, equipo, ErrorDeParallel, leerBandeja, leerConexiones, leerCuenta, leerMisionDeLaCuenta, leerMisiones, leerPlantillas, quienEs, responderBandeja, verMision, } from "./api.js";
|
|
13
13
|
import { abrirNavegador, comoHasEntrado, credencialesActivas, entrar, formaDeLaOpcion, salir } from "./cuenta.js";
|
|
14
14
|
import { accionesDeUnaMision, filtrarPorTitulo, hace } from "./comandos.js";
|
|
15
|
-
import { hacerTarea, mensajeDeError, nuevaSesion } from "./agente.js";
|
|
15
|
+
import { hacerTarea, mensajeDeError, nuevaSesion, reiniciarConversacion } from "./agente.js";
|
|
16
16
|
import { deshacerUltimo, permitirCarpeta } from "./herramientas.js";
|
|
17
17
|
import { Registro } from "./registro.js";
|
|
18
18
|
import { Pantalla } from "./ui.js";
|
|
19
|
+
import { Remoto } from "./remoto.js";
|
|
20
|
+
import { qrEnTexto } from "./qr.js";
|
|
19
21
|
import { acciones, bloqueDeBienvenida, bloqueDeLaPersona, bloqueDeTabla, duracion, Superficie } from "./tui.js";
|
|
20
22
|
import { CONSEJOS, cambiarConsejos, consejo, elegirConsejos, ordenDeConsejos } from "./consejos.js";
|
|
21
23
|
import { anchoDe, recortarMedio } from "./texto.js";
|
|
@@ -24,9 +26,9 @@ import { avisarEnElEscritorio, debeAvisar } from "./notificar.js";
|
|
|
24
26
|
import { copiarAlPortapapeles, escribirInforme, textoParaPegar } from "./informe.js";
|
|
25
27
|
import { objetivoDeMision } from "./mision.js";
|
|
26
28
|
import { ejecutarCarriles, MAX_CARRILES } from "./carriles.js";
|
|
27
|
-
import { spawnSync } from 'node:child_process';
|
|
28
29
|
import { ejecutarBash, textoParaElAgente } from "./bash.js";
|
|
29
|
-
import { comprobarDeFondo, esMasNueva, ordenDeActualizar, rutaDelCli, ultimaPublicada, versionNuevaConocida } from "./actualizar.js";
|
|
30
|
+
import { comprobarDeFondo, ENTRE_COMPROBACIONES_MS, esMasNueva, ordenDeActualizar, rutaDelCli, ultimaPublicada, versionNuevaConocida } from "./actualizar.js";
|
|
31
|
+
import { autoactualizarApagada, confirmarArranque, gestorPermiteAutoactualizar, lanzarLaMasNueva, prepararVersion } from "./versiones.js";
|
|
30
32
|
function opciones() {
|
|
31
33
|
return parseArgs({
|
|
32
34
|
allowPositionals: true,
|
|
@@ -46,6 +48,7 @@ function opciones() {
|
|
|
46
48
|
'no-mouse': { type: 'boolean' },
|
|
47
49
|
'no-animation': { type: 'boolean' },
|
|
48
50
|
animation: { type: 'boolean' },
|
|
51
|
+
remote: { type: 'boolean' },
|
|
49
52
|
version: { type: 'boolean', short: 'v' },
|
|
50
53
|
help: { type: 'boolean', short: 'h' },
|
|
51
54
|
browser: { type: 'boolean' },
|
|
@@ -70,15 +73,38 @@ function rutaCorta(ruta, max = 48) {
|
|
|
70
73
|
const corta = ruta.startsWith(casa) ? `~${ruta.slice(casa.length)}` : ruta;
|
|
71
74
|
return recortarMedio(corta, max);
|
|
72
75
|
}
|
|
76
|
+
let lineaDeVersiones = null;
|
|
77
|
+
function novedadParaLaPantalla() {
|
|
78
|
+
if (lineaDeVersiones)
|
|
79
|
+
return lineaDeVersiones;
|
|
80
|
+
if (!autoactualizarApagada())
|
|
81
|
+
return undefined;
|
|
82
|
+
const nueva = versionNuevaConocida();
|
|
83
|
+
return nueva ? t('update.disponible', { version: nueva }) : undefined;
|
|
84
|
+
}
|
|
73
85
|
function cabeceraCorta(lineas = []) {
|
|
74
86
|
process.stdout.write(`\n ${c.violeta(signo())} ${c.negrita('parx')} ${c.gris(VERSION)}\n`);
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
77
|
-
lineas = [c.gris(
|
|
87
|
+
const novedad = novedadParaLaPantalla();
|
|
88
|
+
if (novedad)
|
|
89
|
+
lineas = [c.gris(novedad), ...lineas];
|
|
78
90
|
for (const l of lineas)
|
|
79
91
|
process.stdout.write(` ${l}\n`);
|
|
80
92
|
process.stdout.write('\n');
|
|
81
93
|
}
|
|
94
|
+
async function mantenerAlDia(registro) {
|
|
95
|
+
try {
|
|
96
|
+
const nueva = await comprobarDeFondo();
|
|
97
|
+
if (!nueva || autoactualizarApagada() || !gestorPermiteAutoactualizar(ordenDeActualizar(rutaDelCli()).gestor))
|
|
98
|
+
return;
|
|
99
|
+
const r = await prepararVersion(nueva);
|
|
100
|
+
if (!r.ok)
|
|
101
|
+
registro?.apuntar('actualizacion', { version: nueva, motivo: r.motivo });
|
|
102
|
+
else if (!r.yaEstaba)
|
|
103
|
+
registro?.apuntar('actualizacion', { version: nueva, preparada: true });
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
82
108
|
export function nombreDeModo(m) {
|
|
83
109
|
return t(`modo.${m}`);
|
|
84
110
|
}
|
|
@@ -114,7 +140,7 @@ async function actualizar() {
|
|
|
114
140
|
process.stdout.write(`${t('update.npx')}\n`);
|
|
115
141
|
return 0;
|
|
116
142
|
}
|
|
117
|
-
if (gestor === 'desarrollo'
|
|
143
|
+
if (gestor === 'desarrollo') {
|
|
118
144
|
process.stdout.write(`${t('update.desarrollo')}\n`);
|
|
119
145
|
return 0;
|
|
120
146
|
}
|
|
@@ -127,14 +153,13 @@ async function actualizar() {
|
|
|
127
153
|
process.stdout.write(`${t('update.al_dia', { version: VERSION })}\n`);
|
|
128
154
|
return 0;
|
|
129
155
|
}
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
process.stderr.write(`${c.error('✗')} ${t('update.fallo', { orden: texto })}\n`);
|
|
156
|
+
const aMano = (orden ?? ['npm', 'install', '-g', '@getparallel/parx@latest']).map((p) => (/\s/.test(p) ? `"${p}"` : p)).join(' ');
|
|
157
|
+
const r = await prepararVersion(ultima);
|
|
158
|
+
if (!r.ok) {
|
|
159
|
+
process.stderr.write(`${c.error('✗')} ${t(r.motivo === 'firma' ? 'update.firma' : 'update.no_se_pudo', { version: ultima, orden: aMano })}\n`);
|
|
135
160
|
return 1;
|
|
136
161
|
}
|
|
137
|
-
process.stdout.write(`${c.violeta('✓')} ${t('update.
|
|
162
|
+
process.stdout.write(`${c.violeta('✓')} ${t(r.yaEstaba ? 'update.ya_preparada' : 'update.preparada', { version: ultima })}${r.novedad ? c.gris(`${sep()}${r.novedad}`) : ''}\n`);
|
|
138
163
|
return 0;
|
|
139
164
|
}
|
|
140
165
|
async function principal() {
|
|
@@ -152,6 +177,9 @@ async function principal() {
|
|
|
152
177
|
process.stdout.write(`parx ${VERSION}\n`);
|
|
153
178
|
return 0;
|
|
154
179
|
}
|
|
180
|
+
const estreno = confirmarArranque();
|
|
181
|
+
if (estreno)
|
|
182
|
+
lineaDeVersiones = estreno.novedad ? t('update.actualizado_con', { version: estreno.version, novedad: estreno.novedad }) : t('update.actualizado', { version: estreno.version });
|
|
155
183
|
if (v.help) {
|
|
156
184
|
cabeceraCorta();
|
|
157
185
|
process.stdout.write(`${USO}\n`);
|
|
@@ -162,7 +190,7 @@ async function principal() {
|
|
|
162
190
|
const solo = positionals.length === 1 && !v.lanes;
|
|
163
191
|
if (solo && ['update', 'upgrade'].includes(primero))
|
|
164
192
|
return actualizar();
|
|
165
|
-
void
|
|
193
|
+
void mantenerAlDia();
|
|
166
194
|
if (solo && ['login', 'entrar', 'switch'].includes(primero)) {
|
|
167
195
|
cabeceraCorta();
|
|
168
196
|
await entrar((l) => pantalla.escribir(l), { forma: formaPedida(v) });
|
|
@@ -242,6 +270,8 @@ async function principal() {
|
|
|
242
270
|
lecturas: [],
|
|
243
271
|
jaula: Boolean(v.sandbox),
|
|
244
272
|
});
|
|
273
|
+
if (cuenta.tarifa)
|
|
274
|
+
sesion.tarifas = cuenta.tarifa;
|
|
245
275
|
for (const f of v['add-folder'] ?? []) {
|
|
246
276
|
const ok = permitirCarpeta(sesion.ctx, f);
|
|
247
277
|
if (!ok)
|
|
@@ -270,6 +300,8 @@ async function principal() {
|
|
|
270
300
|
let control = new AbortController();
|
|
271
301
|
let ocupado = false;
|
|
272
302
|
let superficie = null;
|
|
303
|
+
let remoto = null;
|
|
304
|
+
let deFueraPendientes = 0;
|
|
273
305
|
let bienvenida;
|
|
274
306
|
let consejosDeEntrada = [];
|
|
275
307
|
const confirmarNuevaEnMarcha = async () => {
|
|
@@ -283,15 +315,7 @@ async function principal() {
|
|
|
283
315
|
control.abort();
|
|
284
316
|
};
|
|
285
317
|
const empezarDeNuevo = () => {
|
|
286
|
-
sesion
|
|
287
|
-
sesion.diario = [];
|
|
288
|
-
sesion.id = randomUUID();
|
|
289
|
-
sesion.ultimaTarea = undefined;
|
|
290
|
-
sesion.ctx.hechos = { ficheros: [], comandos: [] };
|
|
291
|
-
sesion.ctx.permitidos.clear();
|
|
292
|
-
sesion.ctx.deshacer.length = 0;
|
|
293
|
-
if (sesion.ctx.lecturas)
|
|
294
|
-
sesion.ctx.lecturas.length = 0;
|
|
318
|
+
reiniciarConversacion(sesion);
|
|
295
319
|
registro.apuntar('nueva', {});
|
|
296
320
|
pantalla.reiniciar();
|
|
297
321
|
if (superficie)
|
|
@@ -307,6 +331,14 @@ async function principal() {
|
|
|
307
331
|
superficie.terminar(despedida());
|
|
308
332
|
else
|
|
309
333
|
pantalla.escribir(despedida());
|
|
334
|
+
if (remoto?.encendido) {
|
|
335
|
+
const salida = setTimeout(() => process.exit(0), 1500);
|
|
336
|
+
void remoto.cerrar().finally(() => {
|
|
337
|
+
clearTimeout(salida);
|
|
338
|
+
process.exit(0);
|
|
339
|
+
});
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
310
342
|
process.exit(0);
|
|
311
343
|
};
|
|
312
344
|
const alCtrlC = () => {
|
|
@@ -348,6 +380,13 @@ async function principal() {
|
|
|
348
380
|
superficie?.trabajar(() => control.abort());
|
|
349
381
|
const inicio = Date.now();
|
|
350
382
|
let seguir = true;
|
|
383
|
+
if (remoto?.encendido) {
|
|
384
|
+
remoto.fijarModo(nombreDeModo(sesion.ctx.modo), nombreDeModelo(sesion.modelo));
|
|
385
|
+
remoto.evento('tu', { texto: texto.replace(/^\u2060/, ''), desde: deFueraPendientes > 0 ? 'movil' : 'ordenador' });
|
|
386
|
+
if (deFueraPendientes > 0)
|
|
387
|
+
deFueraPendientes--;
|
|
388
|
+
remoto.fijarEstado('trabajando', texto.replace(/^\u2060/, ''));
|
|
389
|
+
}
|
|
351
390
|
try {
|
|
352
391
|
seguir = await hacerTarea(sesion, texto, control.signal, imagenes);
|
|
353
392
|
if (sesion.disponibles !== undefined && sesion.disponibles !== null)
|
|
@@ -356,10 +395,93 @@ async function principal() {
|
|
|
356
395
|
finally {
|
|
357
396
|
ocupado = false;
|
|
358
397
|
superficie?.descansar();
|
|
398
|
+
remoto?.fijarEstado('libre', null);
|
|
359
399
|
}
|
|
360
400
|
quizaAvisar(t('aviso.termino', { tarea: texto.replace(/\s+/g, ' ').slice(0, 80) }), Date.now() - inicio);
|
|
361
401
|
return seguir;
|
|
362
402
|
};
|
|
403
|
+
const encenderRemoto = async (enseñarQR = true) => {
|
|
404
|
+
if (!superficie) {
|
|
405
|
+
pantalla.escribir(t('remoto.solo_interactiva'));
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (remoto?.encendido && remoto.url) {
|
|
409
|
+
const url = remoto.url;
|
|
410
|
+
soloAqui(() => enseñarEnlace(url, enseñarQR));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const nuevo = new Remoto({
|
|
414
|
+
servidor: cred.servidor,
|
|
415
|
+
token: cred.token,
|
|
416
|
+
alMensaje: (texto) => {
|
|
417
|
+
soloAqui(() => pantalla.escribir(c.gris(`${t('remoto.del_movil')}:`)));
|
|
418
|
+
deFueraPendientes++;
|
|
419
|
+
superficie?.mandarDesdeFuera(texto);
|
|
420
|
+
},
|
|
421
|
+
alParar: () => {
|
|
422
|
+
if (!ocupado)
|
|
423
|
+
return;
|
|
424
|
+
control.abort();
|
|
425
|
+
pantalla.aviso(t('remoto.parado'));
|
|
426
|
+
},
|
|
427
|
+
alApagarse: (motivo) => {
|
|
428
|
+
pantalla.espejo = null;
|
|
429
|
+
remoto = null;
|
|
430
|
+
pantalla.aviso(motivo === 'web' ? t('remoto.cortado_web') : motivo === 'sesion' ? t('remoto.sesion') : t('remoto.no_disponible'));
|
|
431
|
+
superficie?.pintar();
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
try {
|
|
435
|
+
const r = await nuevo.abrir({ equipo: equipo().equipo, version: equipo().version, carpeta: basename(raiz), modelo: nombreDeModelo(sesion.modelo), modo: nombreDeModo(sesion.ctx.modo) });
|
|
436
|
+
remoto = nuevo;
|
|
437
|
+
registro.apuntar('remoto', { encendido: true });
|
|
438
|
+
enseñarEnlace(r.url, enseñarQR);
|
|
439
|
+
pantalla.espejo = nuevo;
|
|
440
|
+
}
|
|
441
|
+
catch (e) {
|
|
442
|
+
if (e instanceof ErrorDeParallel) {
|
|
443
|
+
if (e.codigo === 'sin_acceso')
|
|
444
|
+
pantalla.error(t('remoto.no_disponible'));
|
|
445
|
+
else if (e.codigo === 'sesion')
|
|
446
|
+
pantalla.error(t('remoto.sesion'));
|
|
447
|
+
else if (e.detalle.includes('demasiados'))
|
|
448
|
+
pantalla.error(t('remoto.demasiados'));
|
|
449
|
+
else
|
|
450
|
+
pantalla.error(t('remoto.sin_red', { motivo: e.codigo === 'red' ? 'no connection' : e.detalle || e.codigo }));
|
|
451
|
+
}
|
|
452
|
+
else
|
|
453
|
+
throw e;
|
|
454
|
+
}
|
|
455
|
+
superficie?.pintar();
|
|
456
|
+
};
|
|
457
|
+
const soloAqui = (f) => {
|
|
458
|
+
const e = pantalla.espejo;
|
|
459
|
+
pantalla.espejo = null;
|
|
460
|
+
try {
|
|
461
|
+
f();
|
|
462
|
+
}
|
|
463
|
+
finally {
|
|
464
|
+
pantalla.espejo = e;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
const enseñarEnlace = (url, conQR) => {
|
|
468
|
+
pantalla.escribir(t('remoto.on', { url: c.marca(url) }));
|
|
469
|
+
pantalla.escribir(c.gris(t('remoto.detalle')));
|
|
470
|
+
const caps = capacidades();
|
|
471
|
+
const qr = conQR ? qrEnTexto(url, { color: caps.color > 0, unicode: caps.unicode }) : null;
|
|
472
|
+
if (qr && caps.ancho >= sinColor(qr[0]).length + 4) {
|
|
473
|
+
pantalla.escribir(c.gris(t('remoto.escanea')));
|
|
474
|
+
pantalla.bloque(() => ['', ...qr.map((l) => ` ${l}`), '']);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
const apagarRemoto = async () => {
|
|
478
|
+
const r = remoto;
|
|
479
|
+
remoto = null;
|
|
480
|
+
pantalla.espejo = null;
|
|
481
|
+
if (r)
|
|
482
|
+
await r.cerrar();
|
|
483
|
+
registro.apuntar('remoto', { encendido: false });
|
|
484
|
+
};
|
|
363
485
|
const carriles = async (tareas) => {
|
|
364
486
|
if (tareas.length < 2) {
|
|
365
487
|
pantalla.escribir(t('carriles.min'));
|
|
@@ -507,15 +629,14 @@ async function principal() {
|
|
|
507
629
|
quien: quien.includes('@') ? quien : (quien.split(/\s+/)[0] ?? ''),
|
|
508
630
|
carpeta: rutaCorta(raiz, 60),
|
|
509
631
|
plan: cuenta.plan,
|
|
510
|
-
novedad: (
|
|
511
|
-
const nueva = versionNuevaConocida();
|
|
512
|
-
return nueva ? t('update.disponible', { version: nueva }) : undefined;
|
|
513
|
-
})(),
|
|
632
|
+
novedad: novedadParaLaPantalla(),
|
|
514
633
|
peligro: () => sesion.ctx.modo === 'bypass',
|
|
515
634
|
});
|
|
516
635
|
superficie.iniciar(bienvenida, consejosDeEntrada);
|
|
517
636
|
if (quiereBypass)
|
|
518
637
|
await confirmarBypass();
|
|
638
|
+
if (v.remote || ajustes.remoto)
|
|
639
|
+
void encenderRemoto(Boolean(v.remote));
|
|
519
640
|
const mirarBandeja = async () => {
|
|
520
641
|
if (ocupado)
|
|
521
642
|
return;
|
|
@@ -536,6 +657,11 @@ async function principal() {
|
|
|
536
657
|
relojBandeja.unref();
|
|
537
658
|
void mirarBandeja();
|
|
538
659
|
void cargarConexiones();
|
|
660
|
+
const relojVersiones = setInterval(() => {
|
|
661
|
+
if (!ocupado)
|
|
662
|
+
void mantenerAlDia(registro);
|
|
663
|
+
}, ENTRE_COMPROBACIONES_MS);
|
|
664
|
+
relojVersiones.unref();
|
|
539
665
|
}
|
|
540
666
|
const leerLinea = async (ejemplo = null) => {
|
|
541
667
|
if (superficie)
|
|
@@ -1053,6 +1179,33 @@ async function principal() {
|
|
|
1053
1179
|
pantalla.escribir(pantalla.animar ? t('animacion.on') : t('animacion.off'));
|
|
1054
1180
|
break;
|
|
1055
1181
|
}
|
|
1182
|
+
case 'remote': {
|
|
1183
|
+
if (arg === 'off' || ALIAS_SI_NO[arg] === 'no' || arg === 'never') {
|
|
1184
|
+
await apagarRemoto();
|
|
1185
|
+
if (leerAjustes().remoto)
|
|
1186
|
+
guardarAjustes({ remoto: false });
|
|
1187
|
+
pantalla.escribir(t('remoto.off'));
|
|
1188
|
+
superficie?.pintar();
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
if (arg === 'always' || arg === 'siempre') {
|
|
1192
|
+
guardarAjustes({ remoto: true });
|
|
1193
|
+
await encenderRemoto(true);
|
|
1194
|
+
pantalla.escribir(c.gris(t('remoto.siempre')));
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
await encenderRemoto(true);
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
case 'autoupdate': {
|
|
1201
|
+
const si = ALIAS_SI_NO[arg] ?? (autoactualizarApagada() ? 'si' : 'no');
|
|
1202
|
+
guardarAjustes({ autoactualizar: si === 'si' });
|
|
1203
|
+
registro.apuntar('autoactualizar', { encendido: si === 'si' });
|
|
1204
|
+
pantalla.escribir(si === 'si' ? t('autoupdate.on') : t('autoupdate.off'));
|
|
1205
|
+
if (si === 'si')
|
|
1206
|
+
void mantenerAlDia(registro);
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1056
1209
|
case 'undo': {
|
|
1057
1210
|
const r2 = deshacerUltimo(sesion.ctx);
|
|
1058
1211
|
pantalla.escribir(r2 ? t('repl.deshecho', { ruta: r2 }) : t('repl.nada_que_deshacer'));
|
|
@@ -1112,10 +1265,16 @@ async function principal() {
|
|
|
1112
1265
|
return 0;
|
|
1113
1266
|
}
|
|
1114
1267
|
if (!process.env.PARX_PRUEBAS) {
|
|
1115
|
-
|
|
1116
|
-
if (
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1268
|
+
lanzarLaMasNueva().then((lanzamiento) => {
|
|
1269
|
+
if (lanzamiento.lanzada)
|
|
1270
|
+
return;
|
|
1271
|
+
if (lanzamiento.retirada)
|
|
1272
|
+
lineaDeVersiones = t('update.no_arranco', { version: lanzamiento.retirada, actual: VERSION });
|
|
1273
|
+
principal().then((codigo) => process.exit(codigo), (e) => {
|
|
1274
|
+
if (e instanceof ErrorDeParallel && e.codigo === 'interrumpido')
|
|
1275
|
+
process.exit(130);
|
|
1276
|
+
process.stderr.write(`${c.error('✗')} ${e instanceof ErrorDeParallel ? mensajeDeError(e) : e instanceof Error ? e.message : String(e)}\n`);
|
|
1277
|
+
process.exit(1);
|
|
1278
|
+
});
|
|
1120
1279
|
});
|
|
1121
1280
|
}
|
package/dist/config.js
CHANGED
|
@@ -96,6 +96,10 @@ export function leerAjustes(dir = carpetaDeConfiguracion()) {
|
|
|
96
96
|
out.raton = a.raton;
|
|
97
97
|
if (typeof a.animacion === 'boolean')
|
|
98
98
|
out.animacion = a.animacion;
|
|
99
|
+
if (typeof a.autoactualizar === 'boolean')
|
|
100
|
+
out.autoactualizar = a.autoactualizar;
|
|
101
|
+
if (typeof a.remoto === 'boolean')
|
|
102
|
+
out.remoto = a.remoto;
|
|
99
103
|
const k = a.consejos;
|
|
100
104
|
if (k && typeof k === 'object') {
|
|
101
105
|
const n = (x) => (typeof x === 'number' && Number.isFinite(x) && x >= 0 ? Math.floor(x) : 0);
|