@getparallel/parx 0.4.4 → 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 +16 -0
- package/README.md +21 -6
- package/dist/actualizar.js +2 -1
- package/dist/agente.js +82 -2
- package/dist/api.js +32 -0
- package/dist/cli.js +385 -36
- package/dist/comandos.js +71 -0
- package/dist/config.js +4 -0
- package/dist/consejos.js +2 -1
- package/dist/estimacion.js +118 -0
- package/dist/firmas.js +55 -0
- package/dist/marca.js +56 -19
- package/dist/menciones.js +85 -0
- package/dist/par.js +355 -0
- package/dist/remoto.js +284 -0
- package/dist/tar.js +99 -0
- package/dist/textos.js +95 -6
- package/dist/tui.js +132 -15
- package/dist/ui.js +35 -2
- package/dist/versiones.js +341 -0
- package/package.json +1 -1
package/dist/tar.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { gunzipSync } from 'node:zlib';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, normalize, sep } from 'node:path';
|
|
4
|
+
const BLOQUE = 512;
|
|
5
|
+
function campo(b, desde, largo) {
|
|
6
|
+
const fin = b.indexOf(0, desde);
|
|
7
|
+
return b.subarray(desde, fin === -1 || fin > desde + largo ? desde + largo : fin).toString('utf8');
|
|
8
|
+
}
|
|
9
|
+
function numero(b, desde, largo) {
|
|
10
|
+
const s = campo(b, desde, largo).trim();
|
|
11
|
+
if (!s)
|
|
12
|
+
return 0;
|
|
13
|
+
if (b[desde] & 0x80) {
|
|
14
|
+
let n = 0;
|
|
15
|
+
for (let i = 1; i < largo; i++)
|
|
16
|
+
n = n * 256 + b[desde + i];
|
|
17
|
+
return n;
|
|
18
|
+
}
|
|
19
|
+
const n = parseInt(s, 8);
|
|
20
|
+
return Number.isFinite(n) ? n : 0;
|
|
21
|
+
}
|
|
22
|
+
export function leerTar(tar, limites = {}) {
|
|
23
|
+
const maxFicheros = limites.maxFicheros ?? 400;
|
|
24
|
+
const maxBytes = limites.maxBytes ?? 20 * 1024 * 1024;
|
|
25
|
+
const prefijo = limites.prefijo ?? 'package/';
|
|
26
|
+
const salida = [];
|
|
27
|
+
let bytes = 0;
|
|
28
|
+
let pos = 0;
|
|
29
|
+
let nombreLargo = null;
|
|
30
|
+
let rutaPax = null;
|
|
31
|
+
while (pos + BLOQUE <= tar.length) {
|
|
32
|
+
const cab = tar.subarray(pos, pos + BLOQUE);
|
|
33
|
+
if (cab.every((x) => x === 0))
|
|
34
|
+
break;
|
|
35
|
+
const tipo = String.fromCharCode(cab[156]);
|
|
36
|
+
const tamano = numero(cab, 124, 12);
|
|
37
|
+
const datos = tar.subarray(pos + BLOQUE, pos + BLOQUE + tamano);
|
|
38
|
+
pos += BLOQUE + Math.ceil(tamano / BLOQUE) * BLOQUE;
|
|
39
|
+
if (tipo === 'L') {
|
|
40
|
+
nombreLargo = datos.toString('utf8').replace(/\0+$/, '');
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (tipo === 'x') {
|
|
44
|
+
for (const linea of datos.toString('utf8').split('\n')) {
|
|
45
|
+
const m = /^\d+ path=(.*)$/.exec(linea);
|
|
46
|
+
if (m)
|
|
47
|
+
rutaPax = m[1];
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (tipo === 'g')
|
|
52
|
+
continue;
|
|
53
|
+
let nombre = rutaPax ?? nombreLargo ?? campo(cab, 0, 100);
|
|
54
|
+
if (!rutaPax && !nombreLargo && campo(cab, 257, 6) === 'ustar') {
|
|
55
|
+
const pre = campo(cab, 345, 155);
|
|
56
|
+
if (pre)
|
|
57
|
+
nombre = `${pre}/${nombre}`;
|
|
58
|
+
}
|
|
59
|
+
rutaPax = null;
|
|
60
|
+
nombreLargo = null;
|
|
61
|
+
if (tipo === '5' || nombre.endsWith('/'))
|
|
62
|
+
continue;
|
|
63
|
+
if (tipo !== '0' && tipo !== '\0' && tipo !== '7')
|
|
64
|
+
throw new Error(`unsupported entry type "${tipo}" for ${nombre}`);
|
|
65
|
+
if (!nombre.startsWith(prefijo))
|
|
66
|
+
throw new Error(`entry outside the package: ${nombre}`);
|
|
67
|
+
const ruta = nombre.slice(prefijo.length);
|
|
68
|
+
if (!rutaSegura(ruta))
|
|
69
|
+
throw new Error(`unsafe path: ${nombre}`);
|
|
70
|
+
bytes += datos.length;
|
|
71
|
+
if (salida.length >= maxFicheros || bytes > maxBytes)
|
|
72
|
+
throw new Error('package too large');
|
|
73
|
+
salida.push({ ruta, contenido: Buffer.from(datos) });
|
|
74
|
+
}
|
|
75
|
+
return salida;
|
|
76
|
+
}
|
|
77
|
+
export function rutaSegura(ruta) {
|
|
78
|
+
if (!ruta || ruta.includes('\\') || ruta.includes('\0') || ruta.startsWith('/'))
|
|
79
|
+
return false;
|
|
80
|
+
if (/^[A-Za-z]:/.test(ruta))
|
|
81
|
+
return false;
|
|
82
|
+
const partes = ruta.split('/');
|
|
83
|
+
return partes.every((p) => p && p !== '.' && p !== '..');
|
|
84
|
+
}
|
|
85
|
+
export function leerTgz(tgz, limites = {}) {
|
|
86
|
+
const maxBytes = limites.maxBytes ?? 20 * 1024 * 1024;
|
|
87
|
+
const tar = gunzipSync(tgz, { maxOutputLength: maxBytes + 2 * 1024 * 1024 });
|
|
88
|
+
return leerTar(tar, limites);
|
|
89
|
+
}
|
|
90
|
+
export function escribirFicheros(ficheros, destino) {
|
|
91
|
+
const base = normalize(destino + sep);
|
|
92
|
+
for (const f of ficheros) {
|
|
93
|
+
const ruta = normalize(join(destino, ...f.ruta.split('/')));
|
|
94
|
+
if (!ruta.startsWith(base))
|
|
95
|
+
throw new Error(`unsafe path: ${f.ruta}`);
|
|
96
|
+
mkdirSync(dirname(ruta), { recursive: true });
|
|
97
|
+
writeFileSync(ruta, f.contenido, { mode: f.ruta.endsWith('.js') ? 0o755 : 0o644 });
|
|
98
|
+
}
|
|
99
|
+
}
|
package/dist/textos.js
CHANGED
|
@@ -21,6 +21,7 @@ export const TEXTOS = {
|
|
|
21
21
|
'tui.una_vez': 'Yes, this once',
|
|
22
22
|
'tui.atajos': 'Shift+Tab mode · ↑ ↓ history · ! runs a shell command · Esc stops a task · any key shows the whole answer · Ctrl+C twice exits · Ctrl+L redraws',
|
|
23
23
|
'tui.atajos_raton': 'Click any option or button · wheel or PgUp/PgDn scrolls · End jumps to the bottom · Shift+drag selects text (Option in iTerm2, Fn in Terminal) · /mouse off gives the mouse back to the terminal',
|
|
24
|
+
'tui.atajos_arroba': '@ names one of your Parallel apps · one that is not connected opens its Connect page',
|
|
24
25
|
'tui.al_fondo': 'Jump to bottom',
|
|
25
26
|
'tui.nuevas': '{n} new',
|
|
26
27
|
'bash.ejemplo': 'A shell command, run in the project folder',
|
|
@@ -53,6 +54,46 @@ export const TEXTOS = {
|
|
|
53
54
|
'bandeja.despacio': 'Too many answers at once. Try again in a minute.',
|
|
54
55
|
'bandeja.no_disponible': 'Your missions are not reachable from parx yet.',
|
|
55
56
|
'bandeja.abrir': 'Open in the browser: {url}',
|
|
57
|
+
'arroba.conectada': 'Connected',
|
|
58
|
+
'arroba.conectar': 'Connect',
|
|
59
|
+
'arroba.abriendo': 'Connect {nombre} in your browser: {url}',
|
|
60
|
+
'arroba.despues': 'Then type @ again: it shows up as connected.',
|
|
61
|
+
'misiones.titulo': 'Pick a mission',
|
|
62
|
+
'misiones.mirando': 'Looking at your missions',
|
|
63
|
+
'misiones.nada': 'You have no missions yet. Start one with /mission <goal>.',
|
|
64
|
+
'misiones.sin_coincidencias': 'No mission matches “{q}”.',
|
|
65
|
+
'misiones.volver': 'Back',
|
|
66
|
+
'misiones.progreso': '{estado} · {n}%',
|
|
67
|
+
'misiones.esperan': '{n} waiting',
|
|
68
|
+
'misiones.abierta': 'Opened in your browser: {url}',
|
|
69
|
+
'misiones.pausada': '“{titulo}” paused',
|
|
70
|
+
'misiones.reanudada': '“{titulo}” is running again',
|
|
71
|
+
'misiones.no_se_puede': 'That mission cannot do that right now.',
|
|
72
|
+
'misiones.no_existe': 'That mission no longer exists.',
|
|
73
|
+
'misiones.compartida': 'Read-only link, copied to the clipboard: {url}',
|
|
74
|
+
'misiones.compartida_sin_copiar': 'Read-only link: {url}',
|
|
75
|
+
'misiones.sin_compartir': 'Sharing is not available right now.',
|
|
76
|
+
'misiones.resultado': 'Result',
|
|
77
|
+
'misiones.meta': 'Goal',
|
|
78
|
+
'misiones.objetivo': '{etiqueta}: {valor} of {meta}',
|
|
79
|
+
'misiones.sin_objetivo': 'Not measured yet',
|
|
80
|
+
'misiones.el_plan': 'Plan',
|
|
81
|
+
'misiones.plan': '{estado} · {n}% done',
|
|
82
|
+
'misiones.ver_resultados': 'Results: {url}',
|
|
83
|
+
'misiones.actividad': 'Activity',
|
|
84
|
+
'misiones.sin_actividad': 'Nothing yet: the agents have not started.',
|
|
85
|
+
'misiones.ver_actividad': 'All of it: {url}',
|
|
86
|
+
'misiones.no_disponible': 'Your missions are not reachable from parx yet.',
|
|
87
|
+
'misiones.despacio': 'Too many at once. Try again in a minute.',
|
|
88
|
+
'plantillas.titulo': 'Start from a template',
|
|
89
|
+
'plantillas.mirando': 'Looking at the templates',
|
|
90
|
+
'plantillas.nada': 'No templates right now.',
|
|
91
|
+
'plantillas.sin_coincidencias': 'No template matches “{q}”.',
|
|
92
|
+
'plantillas.propia': 'Yours',
|
|
93
|
+
'plantillas.lista': 'Written below as a mission: review it and press Enter.',
|
|
94
|
+
'ir.abriendo': 'Opening {que} in your browser: {url}',
|
|
95
|
+
'ir.conexiones': 'your connections',
|
|
96
|
+
'ir.ajustes': 'your settings',
|
|
56
97
|
'modo.ask': 'Ask',
|
|
57
98
|
'modo.auto': 'Auto',
|
|
58
99
|
'modo.read': 'Read only',
|
|
@@ -165,19 +206,43 @@ export const TEXTOS = {
|
|
|
165
206
|
'salir.entorno': 'You are signed in with PARALLEL_API_KEY: remove it from your environment to sign out.',
|
|
166
207
|
'salir.todas': 'Signed out on {n} terminals. API keys stay active.',
|
|
167
208
|
'nombre.nuevo': 'parallel-code is now called parx. Type “parx”.',
|
|
209
|
+
'estimacion.linea': '~{n} Actions (estimate)',
|
|
210
|
+
'estimacion.con': '~{n} Actions (estimate) · with {modelo}: ~{otro}',
|
|
168
211
|
'update.disponible': 'parx {version} available · run `parx update`',
|
|
169
212
|
'update.al_dia': 'parx {version} is the latest version.',
|
|
170
213
|
'update.sin_red': 'Could not reach npm to check for a new version. Try again later.',
|
|
171
|
-
'update.lanzando': 'Updating with: {orden}',
|
|
172
|
-
'update.hecho': 'Done. parx is now up to date.',
|
|
173
|
-
'update.fallo': 'The update did not finish. Run it yourself: {orden}',
|
|
174
214
|
'update.npx': 'You are running parx through npx. Use `npx @getparallel/parx@latest` to always get the latest version.',
|
|
175
215
|
'update.desarrollo': 'This is a development copy of parx (not installed from npm). Update it with git pull.',
|
|
216
|
+
'update.actualizado': 'parx updated to {version}',
|
|
217
|
+
'update.actualizado_con': 'parx updated to {version} · {novedad}',
|
|
218
|
+
'update.preparada': 'parx {version} is ready · it runs the next time you open parx',
|
|
219
|
+
'update.ya_preparada': 'parx {version} is already downloaded · it runs the next time you open parx',
|
|
220
|
+
'update.no_arranco': 'parx {version} did not start · back on {actual}',
|
|
221
|
+
'update.firma': 'parx {version} is on npm but its signature could not be verified, so it was not installed. Update by hand: {orden}',
|
|
222
|
+
'update.no_se_pudo': 'parx {version} could not be downloaded or checked. Update by hand: {orden}',
|
|
223
|
+
'autoupdate.on': 'parx keeps itself up to date: new versions are downloaded in the background and start with the next session.',
|
|
224
|
+
'autoupdate.off': 'parx will not update itself. You will see a line when there is a new version; `parx update` installs it. /autoupdate on to turn it back on.',
|
|
225
|
+
'remoto.abriendo': 'Turning on remote control…',
|
|
226
|
+
'remoto.on': 'Remote control is on. Open this on your phone or any browser: {url}',
|
|
227
|
+
'remoto.escanea': 'Or scan it with your phone:',
|
|
228
|
+
'remoto.detalle': 'Everything still runs on this computer. Credentials and Bypass are only answered here.',
|
|
229
|
+
'remoto.siempre': 'Remote control will turn on every time parx opens on this computer. /remote off to stop.',
|
|
230
|
+
'remoto.off': 'Remote control is off on this computer.',
|
|
231
|
+
'remoto.no_disponible': 'Remote control is not available for your account yet.',
|
|
232
|
+
'remoto.sin_red': 'Could not turn on remote control: {motivo}. Try /remote again.',
|
|
233
|
+
'remoto.cortado_web': 'Remote control was turned off from the web.',
|
|
234
|
+
'remoto.sesion': 'Remote control stopped: sign in again with parx login.',
|
|
235
|
+
'remoto.demasiados': 'You already have 12 parx sessions with remote control open. Close one and try again.',
|
|
236
|
+
'remoto.del_movil': 'From your phone',
|
|
237
|
+
'remoto.desde_el_movil': 'from your phone',
|
|
238
|
+
'remoto.parado': 'Stopped from your phone.',
|
|
239
|
+
'remoto.solo_interactiva': 'Remote control works in an interactive session: run parx and then /remote.',
|
|
176
240
|
'error.sin_saldo': 'You are out of Actions. Top up or upgrade at getparallel.net/settings/billing',
|
|
177
241
|
'error.sin_acceso': 'Your account does not have parx yet: it is in testing.',
|
|
178
242
|
'error.sesion': 'Your session expired. Run: parx login',
|
|
179
243
|
'error.red': 'Could not reach Parallel: {detalle}',
|
|
180
244
|
'error.servidor': 'Parallel could not answer right now ({detalle}). Try again.',
|
|
245
|
+
'error.ia': 'Parallel’s AI is temporarily unavailable. Your Actions weren’t charged. Try again in a minute.',
|
|
181
246
|
'error.demasiado': 'This conversation is too long. Type /new to start another one.',
|
|
182
247
|
'repl.adios': 'See you.',
|
|
183
248
|
'repl.registro': 'Log of this session: {ruta}',
|
|
@@ -242,6 +307,7 @@ export const TEXTOS = {
|
|
|
242
307
|
'tip.bash': 'Runs a shell command right here; parx sees the output',
|
|
243
308
|
'tip.bandeja': 'Answer what your missions are waiting for',
|
|
244
309
|
'tip.raton': 'Click options; Shift+drag selects text',
|
|
310
|
+
'tip.arroba': 'Names one of your apps; a new one opens Connect',
|
|
245
311
|
};
|
|
246
312
|
export function cuantos(n, uno, varios, ninguno) {
|
|
247
313
|
if (n === 0 && ninguno)
|
|
@@ -258,17 +324,23 @@ export const COMANDOS = [
|
|
|
258
324
|
['model', 'Swift, Stride or Summit'],
|
|
259
325
|
['folder', 'Let parx work in another folder'],
|
|
260
326
|
['lanes', 'Run 2 or 3 tasks at once'],
|
|
327
|
+
['remote', 'Control this parx from your phone'],
|
|
261
328
|
['mission', 'Keep it running on Parallel, phone alert when done'],
|
|
329
|
+
['missions', 'Pick a mission and control it from here'],
|
|
262
330
|
['inbox', 'Answer what your missions are waiting for'],
|
|
263
331
|
['report', 'Plain summary to paste in email or WhatsApp'],
|
|
264
332
|
['teach', 'Explain each step, like a teacher'],
|
|
333
|
+
['templates', 'Start a mission from a template'],
|
|
265
334
|
['undo', 'Undo the last change to a file'],
|
|
266
335
|
['new', 'Start a new conversation'],
|
|
267
336
|
['balance', 'Your account and your Actions'],
|
|
337
|
+
['connections', 'Connect your apps (opens the browser)'],
|
|
338
|
+
['settings', 'Your account and preferences (browser)'],
|
|
268
339
|
['notify', 'Desktop notifications: on or off'],
|
|
269
340
|
['tips', 'Tips when it opens: on or off'],
|
|
270
341
|
['mouse', 'Clicks and wheel in parx: on or off'],
|
|
271
342
|
['animation', 'Answers written out as they arrive: on or off'],
|
|
343
|
+
['autoupdate', 'New versions install themselves: on or off'],
|
|
272
344
|
['log', 'Where the log of what it does is kept'],
|
|
273
345
|
['exit', 'Leave parx'],
|
|
274
346
|
];
|
|
@@ -276,12 +348,19 @@ export const ALIAS_COMANDO = {
|
|
|
276
348
|
help: 'help', '?': 'help', ayuda: 'help',
|
|
277
349
|
mode: 'mode', modo: 'mode',
|
|
278
350
|
model: 'model', modelo: 'model',
|
|
351
|
+
strength: 'model',
|
|
279
352
|
folder: 'folder', 'add-folder': 'folder', carpeta: 'folder', dir: 'folder',
|
|
280
353
|
lanes: 'lanes', carriles: 'lanes',
|
|
354
|
+
remote: 'remote', 'remote-control': 'remote', rc: 'remote', remoto: 'remote', phone: 'remote',
|
|
281
355
|
mission: 'mission', mision: 'mission', misión: 'mission',
|
|
356
|
+
missions: 'missions', misiones: 'missions',
|
|
282
357
|
inbox: 'inbox', bandeja: 'inbox', approvals: 'inbox', waiting: 'inbox',
|
|
358
|
+
templates: 'templates', template: 'templates', plantillas: 'templates',
|
|
359
|
+
connections: 'connections', conexiones: 'connections', apps: 'connections',
|
|
360
|
+
settings: 'settings', ajustes: 'settings', preferences: 'settings',
|
|
283
361
|
mouse: 'mouse', raton: 'mouse', ratón: 'mouse',
|
|
284
362
|
animation: 'animation', animations: 'animation', animacion: 'animation', animación: 'animation',
|
|
363
|
+
autoupdate: 'autoupdate', 'auto-update': 'autoupdate', autoupdates: 'autoupdate', updates: 'autoupdate', autoactualizar: 'autoupdate',
|
|
285
364
|
report: 'report', informe: 'report',
|
|
286
365
|
teach: 'teach', class: 'teach', clase: 'teach',
|
|
287
366
|
undo: 'undo', deshacer: 'undo',
|
|
@@ -292,6 +371,7 @@ export const ALIAS_COMANDO = {
|
|
|
292
371
|
log: 'log', registro: 'log',
|
|
293
372
|
exit: 'exit', quit: 'exit', salir: 'exit',
|
|
294
373
|
};
|
|
374
|
+
export const ALIAS_DESDE = { strength: 2, rc: 2, phone: 3 };
|
|
295
375
|
export const ALIAS_SI_NO = {
|
|
296
376
|
on: 'si', yes: 'si', y: 'si', '1': 'si', si: 'si', 'sí': 'si',
|
|
297
377
|
off: 'no', no: 'no', n: 'no', '0': 'no',
|
|
@@ -302,7 +382,7 @@ export const USO = [
|
|
|
302
382
|
' parx "task" do the task and exit',
|
|
303
383
|
' parx --lanes "task" "task" ... 2 or 3 tasks at once, each in its own lane',
|
|
304
384
|
' parx login | logout | account',
|
|
305
|
-
' parx update
|
|
385
|
+
' parx update get the latest version now (parx also updates itself; /autoupdate off stops that)',
|
|
306
386
|
'',
|
|
307
387
|
'Options:',
|
|
308
388
|
' --mode ask|auto|read Ask (default), Auto inside the project, Read only',
|
|
@@ -314,6 +394,7 @@ export const USO = [
|
|
|
314
394
|
' --ascii plain characters only (also NO_COLOR for no color)',
|
|
315
395
|
' --no-mouse leave the mouse to the terminal (no clicks or wheel in parx)',
|
|
316
396
|
' --no-animation show answers whole, without writing them out',
|
|
397
|
+
' --remote control this session from your phone (same as /remote)',
|
|
317
398
|
' --version, --help',
|
|
318
399
|
'',
|
|
319
400
|
'Sign in:',
|
|
@@ -325,7 +406,15 @@ export const USO = [
|
|
|
325
406
|
' parx logout [--all] sign out here, or on every terminal',
|
|
326
407
|
' PARALLEL_API_KEY=par_key_… sign in with that key without saving anything (CI)',
|
|
327
408
|
'',
|
|
409
|
+
'Remote control:',
|
|
410
|
+
' /remote link and QR to control this parx from your phone or a browser',
|
|
411
|
+
' /remote always turn it on every time parx opens on this computer',
|
|
412
|
+
' /remote off turn it off here',
|
|
413
|
+
'',
|
|
328
414
|
'Updates:',
|
|
329
|
-
' parx checks npm
|
|
330
|
-
'
|
|
415
|
+
' parx checks npm every few hours, without slowing anything down, and installs new versions',
|
|
416
|
+
' itself: they are verified against npm’s signature and start with your next session.',
|
|
417
|
+
' /autoupdate off only tell me when there is one (then: parx update)',
|
|
418
|
+
' PARX_NO_AUTO_UPDATE=1 the same, from the environment',
|
|
419
|
+
' PARX_NO_UPDATE_CHECK=1 never even check',
|
|
331
420
|
].join('\n');
|
package/dist/tui.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { animacion, c, capacidades, colorDeModo, sep, cabezaDePar, caraDePar, carril, fijarCapacidades, signo, ANCHO_DE_PAR, SIMBOLOS } from "./marca.js";
|
|
2
2
|
import { anchoDe, envolver, puntos, recortar, rellenar } from "./texto.js";
|
|
3
|
-
import { ALIAS_COMANDO, COMANDOS, t as tt } from "./textos.js";
|
|
3
|
+
import { ALIAS_COMANDO, ALIAS_DESDE, COMANDOS, t as tt } from "./textos.js";
|
|
4
4
|
import { MODELOS, ORDEN_DE_MODELOS } from "./modelos.js";
|
|
5
5
|
import { consejo, ordenDeConsejos } from "./consejos.js";
|
|
6
|
+
import { conexionesQueSalen, insertarMencion, mencionEnLaCaja, mencionTerminada } from "./menciones.js";
|
|
6
7
|
export { anchoDe } from "./texto.js";
|
|
7
8
|
import { conElipsis, fraseDeEspera } from "./actividad.js";
|
|
8
9
|
export const MARGEN = 2;
|
|
@@ -288,9 +289,9 @@ function teclaDeRaton(b, x, y, soltar, botonX10) {
|
|
|
288
289
|
}
|
|
289
290
|
export const RATON_SI = '\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h';
|
|
290
291
|
export const RATON_NO = '\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1006l';
|
|
291
|
-
const CON_VALOR = new Set(['mode', 'model', 'tips', 'notify', 'teach', 'mouse', 'animation']);
|
|
292
|
+
const CON_VALOR = new Set(['mode', 'model', 'tips', 'notify', 'teach', 'mouse', 'animation', 'autoupdate', 'remote']);
|
|
292
293
|
const CON_TEXTO = new Set(['folder']);
|
|
293
|
-
const SE_MANDAN_SOLOS = new Set(['teach', 'notify', 'tips', 'folder', 'mouse', 'animation']);
|
|
294
|
+
const SE_MANDAN_SOLOS = new Set(['teach', 'notify', 'tips', 'folder', 'mouse', 'animation', 'autoupdate', 'remote']);
|
|
294
295
|
const MODOS_DEL_MENU = ['ask', 'auto', 'read', 'bypass'];
|
|
295
296
|
export function opcionesDelMenu(valor) {
|
|
296
297
|
if (!valor.startsWith('/') || valor.includes('\n'))
|
|
@@ -301,7 +302,7 @@ export function opcionesDelMenu(valor) {
|
|
|
301
302
|
const q = sinBarra.toLocaleLowerCase();
|
|
302
303
|
const out = [];
|
|
303
304
|
for (const [cmd, descripcion] of COMANDOS) {
|
|
304
|
-
const casa = cmd.startsWith(q) || (q.length > 0 && Object.entries(ALIAS_COMANDO).some(([alias, destino]) => destino === cmd && alias.startsWith(q)));
|
|
305
|
+
const casa = cmd.startsWith(q) || (q.length > 0 && Object.entries(ALIAS_COMANDO).some(([alias, destino]) => destino === cmd && alias.startsWith(q) && q.length >= (ALIAS_DESDE[alias] ?? 1)));
|
|
305
306
|
if (!casa)
|
|
306
307
|
continue;
|
|
307
308
|
const conAlgo = CON_VALOR.has(cmd) || CON_TEXTO.has(cmd);
|
|
@@ -321,14 +322,37 @@ export function opcionesDelMenu(valor) {
|
|
|
321
322
|
? MODOS_DEL_MENU.map((k) => [k, tt(`modo.${k}.desc`)])
|
|
322
323
|
: cmd === 'model'
|
|
323
324
|
? ORDEN_DE_MODELOS.map((k) => [k, `${MODELOS[k].nombre} ${MODELOS[k].version}: ${MODELOS[k].descripcion}`])
|
|
324
|
-
:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
325
|
+
: cmd === 'remote'
|
|
326
|
+
? [
|
|
327
|
+
['on', 'Link and QR for your phone, for this session'],
|
|
328
|
+
['always', 'Every time parx opens on this computer'],
|
|
329
|
+
['off', 'Stop remote control here'],
|
|
330
|
+
]
|
|
331
|
+
: [
|
|
332
|
+
['on', cmd === 'teach' ? 'Explain each step as it codes' : cmd === 'notify' ? 'Notify me when it finishes or needs me' : cmd === 'mouse' ? 'Click options, scroll with the wheel' : cmd === 'animation' ? 'Write answers out as they arrive' : cmd === 'autoupdate' ? 'New versions install themselves, for the next session' : 'Show them when parx opens'],
|
|
333
|
+
['off', cmd === 'teach' ? 'Back to normal' : cmd === 'notify' ? 'No desktop notifications' : cmd === 'mouse' ? 'The terminal selects and scrolls as usual' : cmd === 'animation' ? 'Show answers whole, at once' : cmd === 'autoupdate' ? 'Only tell me; I run parx update myself' : 'Don’t show them again'],
|
|
334
|
+
];
|
|
328
335
|
return valores
|
|
329
336
|
.filter(([v]) => v.startsWith(q))
|
|
330
337
|
.map(([v, d]) => ({ nombre: v, descripcion: d, completar: `${base}${v}`, enviar: true, ...(cmd === 'mode' ? { modo: v, ...(v === 'bypass' ? { peligro: true } : {}) } : {}) }));
|
|
331
338
|
}
|
|
339
|
+
export function opcionesDeArroba(unidades, cursor, conexiones) {
|
|
340
|
+
if (!conexiones || !conexiones.length)
|
|
341
|
+
return null;
|
|
342
|
+
const m = mencionEnLaCaja(unidades, cursor);
|
|
343
|
+
if (!m || mencionTerminada(m.consulta, conexiones))
|
|
344
|
+
return null;
|
|
345
|
+
const salen = conexionesQueSalen(conexiones, m.consulta);
|
|
346
|
+
if (!salen.length)
|
|
347
|
+
return null;
|
|
348
|
+
return salen.map((x) => ({
|
|
349
|
+
nombre: `@${x.nombre}`,
|
|
350
|
+
descripcion: x.conectada ? tt('arroba.conectada') : tt('arroba.conectar'),
|
|
351
|
+
completar: '',
|
|
352
|
+
enviar: false,
|
|
353
|
+
mencion: { inicio: m.inicio, fin: cursor, conexion: x },
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
332
356
|
export function componerBarra(e, util) {
|
|
333
357
|
return componerBarraConZonas(e, util).texto;
|
|
334
358
|
}
|
|
@@ -529,7 +553,7 @@ export function componerCarril(d, ancho, alto) {
|
|
|
529
553
|
cursor = null;
|
|
530
554
|
}
|
|
531
555
|
if (conPar)
|
|
532
|
-
filaEscribir += ` ${cabezaDePar()} `;
|
|
556
|
+
filaEscribir += ` ${cabezaDePar(d.gesto)} `;
|
|
533
557
|
const tinta = (t) => (bash ? c.bash(t) : c.violeta(t));
|
|
534
558
|
const linea = conPar ? `${tinta(carril(util - ANCHO_DE_PAR - 2))}${caraDePar(d.gesto)}${tinta(carril(2))}` : tinta(carril(util));
|
|
535
559
|
let barra;
|
|
@@ -648,7 +672,8 @@ export function componerCarril(d, ancho, alto) {
|
|
|
648
672
|
const nombre = rellenar(recortar(o.nombre, anchoNombre - 1), anchoNombre);
|
|
649
673
|
if (o.modo)
|
|
650
674
|
return `${m}${rellenar(`${marca} ${sel || sobre ? c.negrita(tinta(nombre)) : tinta(nombre)}${sel || sobre ? desc : c.tenue(c.gris(desc))}`, util)}`;
|
|
651
|
-
|
|
675
|
+
const nota = o.mencion && !o.mencion.conexion.conectada ? c.marca(desc) : sel || sobre ? desc : c.tenue(c.gris(desc));
|
|
676
|
+
return `${m}${rellenar(`${marca} ${sel ? c.negrita(c.marca(nombre)) : sobre ? c.marca(nombre) : c.gris(nombre)}${nota}`, util)}`;
|
|
652
677
|
});
|
|
653
678
|
}
|
|
654
679
|
}
|
|
@@ -768,6 +793,8 @@ export class Superficie {
|
|
|
768
793
|
borrador = [];
|
|
769
794
|
menuCerradoPara = null;
|
|
770
795
|
menuElegida = 0;
|
|
796
|
+
conexiones = null;
|
|
797
|
+
avisadoArroba = false;
|
|
771
798
|
cola = [];
|
|
772
799
|
ctrlC = 0;
|
|
773
800
|
ejemplo = null;
|
|
@@ -802,9 +829,51 @@ export class Superficie {
|
|
|
802
829
|
escribir(s) {
|
|
803
830
|
this.o.salida.write(s);
|
|
804
831
|
}
|
|
832
|
+
menuActual(valor = this.valorCrudo()) {
|
|
833
|
+
if (this.menuCerradoPara === valor)
|
|
834
|
+
return null;
|
|
835
|
+
const barra = opcionesDelMenu(valor);
|
|
836
|
+
if (barra && barra.length)
|
|
837
|
+
return barra;
|
|
838
|
+
const arroba = mencionEnLaCaja(this.unidades, this.cursor);
|
|
839
|
+
if (!arroba) {
|
|
840
|
+
this.avisadoArroba = false;
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
if (!this.avisadoArroba) {
|
|
844
|
+
this.avisadoArroba = true;
|
|
845
|
+
this.o.alArroba?.();
|
|
846
|
+
}
|
|
847
|
+
return opcionesDeArroba(this.unidades, this.cursor, this.conexiones);
|
|
848
|
+
}
|
|
849
|
+
fijarConexiones(lista) {
|
|
850
|
+
this.conexiones = lista ? [...lista] : null;
|
|
851
|
+
this.pintar();
|
|
852
|
+
}
|
|
853
|
+
ponerEnLaCaja(texto) {
|
|
854
|
+
this.ponerValor(texto);
|
|
855
|
+
this.menuCerradoPara = this.valorCrudo();
|
|
856
|
+
this.menuElegida = 0;
|
|
857
|
+
this.pintar();
|
|
858
|
+
}
|
|
859
|
+
elegirMencion(o) {
|
|
860
|
+
const m = o.mencion;
|
|
861
|
+
if (m.conexion.conectada) {
|
|
862
|
+
const r = insertarMencion(this.unidades, m.inicio, m.fin, m.conexion.nombre);
|
|
863
|
+
this.unidades = r.unidades;
|
|
864
|
+
this.cursor = r.cursor;
|
|
865
|
+
this.menuElegida = 0;
|
|
866
|
+
this.menuCerradoPara = null;
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
this.menuCerradoPara = this.valorCrudo();
|
|
870
|
+
this.menuElegida = 0;
|
|
871
|
+
this.avisadoArroba = false;
|
|
872
|
+
this.o.alConectar?.(m.conexion);
|
|
873
|
+
}
|
|
805
874
|
datos() {
|
|
806
875
|
const valor = this.valorCrudo();
|
|
807
|
-
const ops = this.
|
|
876
|
+
const ops = this.menuActual(valor);
|
|
808
877
|
if (ops && this.menuElegida >= ops.length)
|
|
809
878
|
this.menuElegida = Math.max(0, ops.length - 1);
|
|
810
879
|
const pendientes = this.lineas.length - this.volcadas;
|
|
@@ -1422,6 +1491,19 @@ export class Superficie {
|
|
|
1422
1491
|
this.ponerValor(texto);
|
|
1423
1492
|
this.enviar();
|
|
1424
1493
|
}
|
|
1494
|
+
mandarDesdeFuera(texto) {
|
|
1495
|
+
const t = /^[/!]/.test(texto.trimStart()) ? `\u2060${texto.trimStart()}` : texto;
|
|
1496
|
+
if (this.resolverLectura && !this.trabajo && !this.pregunta) {
|
|
1497
|
+
const r = this.resolverLectura;
|
|
1498
|
+
this.resolverLectura = null;
|
|
1499
|
+
this.irAlFondo();
|
|
1500
|
+
this.anadir(this.bloqueDeEnvio(t));
|
|
1501
|
+
r(t);
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
this.cola.push(t);
|
|
1505
|
+
this.mostrarAviso(this.cola.length > 1 ? tt('tui.en_cola_n', { n: this.cola.length }) : tt('tui.en_cola'));
|
|
1506
|
+
}
|
|
1425
1507
|
get libre() {
|
|
1426
1508
|
return Boolean(this.resolverLectura) && !this.trabajo && !this.pregunta;
|
|
1427
1509
|
}
|
|
@@ -1457,11 +1539,18 @@ export class Superficie {
|
|
|
1457
1539
|
actualizarCarriles() {
|
|
1458
1540
|
this.pintar();
|
|
1459
1541
|
}
|
|
1460
|
-
preguntar(texto, tipo = 'normal') {
|
|
1542
|
+
preguntar(texto, tipo = 'normal', externo) {
|
|
1461
1543
|
const o = opcionesDePregunta(tipo);
|
|
1462
1544
|
const peligro = tipo === 'bypass';
|
|
1463
1545
|
return new Promise((resolver) => {
|
|
1464
|
-
|
|
1546
|
+
let hecha = false;
|
|
1547
|
+
const contestar = (r) => {
|
|
1548
|
+
if (hecha)
|
|
1549
|
+
return;
|
|
1550
|
+
hecha = true;
|
|
1551
|
+
resolver(r);
|
|
1552
|
+
};
|
|
1553
|
+
const quitar = this.mostrarPregunta({
|
|
1465
1554
|
texto,
|
|
1466
1555
|
opciones: o.opciones,
|
|
1467
1556
|
cortas: o.cortas,
|
|
@@ -1469,7 +1558,13 @@ export class Superficie {
|
|
|
1469
1558
|
...(peligro ? { peligro: true } : {}),
|
|
1470
1559
|
indiceNo: o.valores.indexOf('no'),
|
|
1471
1560
|
siConY: o.valores[o.elegida] !== 'no' || peligro,
|
|
1472
|
-
}, (i) =>
|
|
1561
|
+
}, (i) => contestar(i === null ? 'no' : (o.valores[i] ?? 'no')));
|
|
1562
|
+
externo?.then((r) => {
|
|
1563
|
+
if (hecha || !o.valores.includes(r))
|
|
1564
|
+
return;
|
|
1565
|
+
contestar(r);
|
|
1566
|
+
quitar();
|
|
1567
|
+
});
|
|
1473
1568
|
});
|
|
1474
1569
|
}
|
|
1475
1570
|
elegir(texto, opciones, o = {}) {
|
|
@@ -1497,6 +1592,20 @@ export class Superficie {
|
|
|
1497
1592
|
this.esperando.push(mostrar);
|
|
1498
1593
|
else
|
|
1499
1594
|
mostrar();
|
|
1595
|
+
return () => {
|
|
1596
|
+
const i = this.esperando.indexOf(mostrar);
|
|
1597
|
+
if (i >= 0) {
|
|
1598
|
+
this.esperando.splice(i, 1);
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
if (this.pregunta?.resolver !== resolver)
|
|
1602
|
+
return;
|
|
1603
|
+
this.pregunta = null;
|
|
1604
|
+
this.sobre = null;
|
|
1605
|
+
this.pulsado = null;
|
|
1606
|
+
this.pintar();
|
|
1607
|
+
this.esperando.shift()?.();
|
|
1608
|
+
};
|
|
1500
1609
|
}
|
|
1501
1610
|
responder(i) {
|
|
1502
1611
|
const p = this.pregunta;
|
|
@@ -1613,7 +1722,7 @@ export class Superficie {
|
|
|
1613
1722
|
this.aviso = null;
|
|
1614
1723
|
}
|
|
1615
1724
|
const valor = this.valorCrudo();
|
|
1616
|
-
const ops = this.
|
|
1725
|
+
const ops = this.menuActual(valor);
|
|
1617
1726
|
const menu = ops && ops.length ? ops : null;
|
|
1618
1727
|
switch (k.t) {
|
|
1619
1728
|
case 'texto':
|
|
@@ -1640,6 +1749,10 @@ export class Superficie {
|
|
|
1640
1749
|
break;
|
|
1641
1750
|
}
|
|
1642
1751
|
case 'intro':
|
|
1752
|
+
if (menu && menu[this.menuElegida]?.mencion) {
|
|
1753
|
+
this.elegirMencion(menu[this.menuElegida]);
|
|
1754
|
+
break;
|
|
1755
|
+
}
|
|
1643
1756
|
if (menu) {
|
|
1644
1757
|
const o = menu[this.menuElegida];
|
|
1645
1758
|
this.ponerValor(o.completar);
|
|
@@ -1651,6 +1764,10 @@ export class Superficie {
|
|
|
1651
1764
|
this.enviar();
|
|
1652
1765
|
break;
|
|
1653
1766
|
case 'tab':
|
|
1767
|
+
if (menu && menu[this.menuElegida]?.mencion) {
|
|
1768
|
+
this.elegirMencion(menu[this.menuElegida]);
|
|
1769
|
+
break;
|
|
1770
|
+
}
|
|
1654
1771
|
if (menu) {
|
|
1655
1772
|
this.ponerValor(menu[this.menuElegida].completar);
|
|
1656
1773
|
this.menuElegida = 0;
|