@getparallel/parx 0.4.3 → 0.4.4
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 +7 -0
- package/README.md +16 -5
- package/dist/actividad.js +131 -0
- package/dist/agente.js +35 -8
- package/dist/cli.js +45 -4
- package/dist/config.js +4 -0
- package/dist/herramientas.js +18 -4
- package/dist/textos.js +20 -10
- package/dist/tui.js +121 -29
- package/dist/ui.js +143 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ 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.4.4 (2026-10-11)
|
|
10
|
+
|
|
11
|
+
- parx always shows what it is doing: «Thinking…» from the first moment, then the real step as it happens («Reading src/app.ts…», «Searching for useAuth…», «Editing 2 files…», «Running tests…», «Writing the answer…»), with the time and the Actions. If the server takes a while, only honest phrases («Planning the steps…», «Looking at the results…», «Waiting for the server…»); the old brand phrases are gone. Same in `--lanes`, at 40 columns, with `NO_COLOR` and `--ascii`.
|
|
12
|
+
- The answer is written out word by word with a soft cursor, in 2 seconds at most, and ends exactly as before (markdown, code blocks, wrapping). Only the rows that change are repainted; scrolling up while it writes does not jump. Any key shows the rest at once; `/animation off` (saved) or `--no-animation` shows answers whole, and so do pipes, CI, `--lanes` and `/report`.
|
|
13
|
+
- `/new` starts truly fresh: the screen and the terminal scrollback are cleared in one go, the queue, the waiting lines and what the agent remembers are gone, and you see the welcome and the tips again, like when parx opens. Mode, model and sign-in stay. If a task is running, it asks «Stop and start new?» with buttons first.
|
|
14
|
+
- `/mouse off` is remembered in the next session too (it was saved but not read back).
|
|
15
|
+
|
|
9
16
|
## 0.4.3 (2026-10-11)
|
|
10
17
|
|
|
11
18
|
- Each permission mode has its own colour, from least to most freedom: Read only teal, Ask violet, Auto orange, Bypass red. It shows in the status row, the `/mode` menu, the `/mode` confirmation, `/help` and the `╱╱` input marker; bash mode keeps its amber.
|
package/README.md
CHANGED
|
@@ -93,10 +93,19 @@ when it asks first:
|
|
|
93
93
|
## Inside a session
|
|
94
94
|
|
|
95
95
|
The input sits at the bottom from the start; the status lives in one place, the row under the rail:
|
|
96
|
-
the mode and the model at rest; while it works, what it is doing
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
the mode and the model at rest; while it works, it always says what it is doing right now, with a
|
|
97
|
+
small rail animation in violet, the time and the Actions so far. It is never silent: «Thinking…»
|
|
98
|
+
while Parallel thinks, then the real step as it happens («Reading src/app.ts…», «Searching for
|
|
99
|
+
useAuth…», «Editing 2 files…», «Running tests…», «Running git status…», «Writing the answer…»). If
|
|
100
|
+
the server takes a while, only what is really going on («Planning the steps…», «Looking at the
|
|
101
|
+
results…», «Waiting for the server…»). It works the same in `--lanes` (one row per lane), at 40
|
|
102
|
+
columns, with `NO_COLOR` and with `--ascii`.
|
|
103
|
+
|
|
104
|
+
**The answer is written out**, word by word, with a soft cursor, in 2 seconds at most (long answers
|
|
105
|
+
go faster). It is laid out whole first, so markdown and code blocks end exactly as if they had
|
|
106
|
+
appeared at once, and scrolling up while it writes does not move what you are reading. Any key shows
|
|
107
|
+
the rest at once. `/animation off` (saved) or `--no-animation` shows answers whole; without a
|
|
108
|
+
terminal (a pipe, CI), in `--lanes` and in `/report` they are always whole.
|
|
100
109
|
|
|
101
110
|
Type `/` for the commands; the menu filters as you type, ↑ ↓ move, Tab or Enter pick, Esc closes.
|
|
102
111
|
|
|
@@ -110,10 +119,12 @@ Type `/` for the commands; the menu filters as you type, ↑ ↓ move, Tab or En
|
|
|
110
119
|
| `/inbox` | what your missions are waiting for (approvals, proposed changes, your steps), as buttons; what you pick takes effect like on the web |
|
|
111
120
|
| `/report` | a plain-language summary of what was done, copied to the clipboard for an email or WhatsApp |
|
|
112
121
|
| `/teach` | class mode: explains each step as it codes, like a teacher |
|
|
113
|
-
| `/
|
|
122
|
+
| `/new` | a clean conversation, as if you had just opened parx: the screen, the terminal scrollback, the queue and what the agent remembers are cleared (mode, model and sign-in stay). If a task is running it asks «Stop and start new?» first |
|
|
123
|
+
| `/undo`, `/balance`, `/log`, `/help`, `/exit` | the usual |
|
|
114
124
|
| `/notify` on, off | desktop notifications |
|
|
115
125
|
| `/tips` on, off | the welcome tips |
|
|
116
126
|
| `/mouse` on, off | clicks and wheel inside parx (saved; `--no-mouse` for one session) |
|
|
127
|
+
| `/animation` on, off | answers written out as they arrive, or whole (saved; `--no-animation` for one session) |
|
|
117
128
|
|
|
118
129
|
**Bash mode:** start the input with `!` and it turns amber with a small `bash` label; Enter runs the
|
|
119
130
|
command in your shell, in the project folder, and its output streams into the conversation with the
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { capacidades, c } from "./marca.js";
|
|
2
|
+
import { t } from "./textos.js";
|
|
3
|
+
import { anchoDeCaracter, sinColor } from "./texto.js";
|
|
4
|
+
export function elipsis() {
|
|
5
|
+
return capacidades().unicode ? '…' : '...';
|
|
6
|
+
}
|
|
7
|
+
export function conElipsis(texto) {
|
|
8
|
+
const s = texto.trimEnd();
|
|
9
|
+
if (!s || /(…|\.\.\.|[.!?:])$/.test(s))
|
|
10
|
+
return s;
|
|
11
|
+
return `${s}${elipsis()}`;
|
|
12
|
+
}
|
|
13
|
+
const AL_EMPEZAR = [
|
|
14
|
+
[0, 'trabajo.pensando'],
|
|
15
|
+
[6_000, 'trabajo.planear'],
|
|
16
|
+
[15_000, 'trabajo.sigue'],
|
|
17
|
+
[35_000, 'trabajo.servidor'],
|
|
18
|
+
];
|
|
19
|
+
const TRAS_HERRAMIENTAS = [
|
|
20
|
+
[0, 'trabajo.pensando'],
|
|
21
|
+
[5_000, 'trabajo.resultados'],
|
|
22
|
+
[11_000, 'trabajo.siguiente'],
|
|
23
|
+
[20_000, 'trabajo.sigue'],
|
|
24
|
+
[40_000, 'trabajo.servidor'],
|
|
25
|
+
];
|
|
26
|
+
export function fraseDeEspera(ms, trasHerramientas = false) {
|
|
27
|
+
const tabla = trasHerramientas ? TRAS_HERRAMIENTAS : AL_EMPEZAR;
|
|
28
|
+
let clave = tabla[0][1];
|
|
29
|
+
for (const [desde, k] of tabla)
|
|
30
|
+
if (Math.max(0, ms) >= desde)
|
|
31
|
+
clave = k;
|
|
32
|
+
return conElipsis(t(clave));
|
|
33
|
+
}
|
|
34
|
+
export const CARACTERES_POR_SEGUNDO = 110;
|
|
35
|
+
export const MS_MAX_RESPUESTA = 2000;
|
|
36
|
+
export const MS_MAX_INTERMEDIO = 900;
|
|
37
|
+
export const MS_POR_FOTOGRAMA = 33;
|
|
38
|
+
export function duracionDeRevelado(caracteres, maxMs = MS_MAX_RESPUESTA) {
|
|
39
|
+
if (caracteres <= 0)
|
|
40
|
+
return 0;
|
|
41
|
+
return Math.round(Math.min(maxMs, Math.max(160, (caracteres / CARACTERES_POR_SEGUNDO) * 1000)));
|
|
42
|
+
}
|
|
43
|
+
export function caracteresVisibles(total, ms, duracion) {
|
|
44
|
+
if (duracion <= 0 || ms >= duracion)
|
|
45
|
+
return total;
|
|
46
|
+
return Math.max(0, Math.min(total, Math.floor((total * Math.max(0, ms)) / duracion)));
|
|
47
|
+
}
|
|
48
|
+
export function cursorEncendido(ms) {
|
|
49
|
+
return Math.floor(Math.max(0, ms) / 500) % 2 === 0;
|
|
50
|
+
}
|
|
51
|
+
export function simboloDeCursor() {
|
|
52
|
+
return c.violeta(capacidades().unicode ? '▍' : '_');
|
|
53
|
+
}
|
|
54
|
+
export function caracteresDe(lineas) {
|
|
55
|
+
let n = 0;
|
|
56
|
+
for (const l of lineas)
|
|
57
|
+
n += anchoVisible(l);
|
|
58
|
+
return n;
|
|
59
|
+
}
|
|
60
|
+
function anchoVisible(l) {
|
|
61
|
+
let n = 0;
|
|
62
|
+
for (const ch of sinColor(l))
|
|
63
|
+
n += anchoDeCaracter(ch);
|
|
64
|
+
return n;
|
|
65
|
+
}
|
|
66
|
+
const ESCAPE = /\x1b\[[0-9;?]*[ -/]*[@-~]/y;
|
|
67
|
+
export function cortarVisible(linea, n) {
|
|
68
|
+
if (n <= 0)
|
|
69
|
+
return '';
|
|
70
|
+
let out = '';
|
|
71
|
+
let w = 0;
|
|
72
|
+
let color = false;
|
|
73
|
+
let i = 0;
|
|
74
|
+
while (i < linea.length) {
|
|
75
|
+
if (linea[i] === '\x1b') {
|
|
76
|
+
ESCAPE.lastIndex = i;
|
|
77
|
+
const m = ESCAPE.exec(linea);
|
|
78
|
+
if (m) {
|
|
79
|
+
out += m[0];
|
|
80
|
+
color = true;
|
|
81
|
+
i += m[0].length;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const ch = String.fromCodePoint(linea.codePointAt(i));
|
|
86
|
+
const cw = anchoDeCaracter(ch);
|
|
87
|
+
if (w + cw > n)
|
|
88
|
+
return `${out}${color ? '\x1b[0m' : ''}`;
|
|
89
|
+
out += ch;
|
|
90
|
+
w += cw;
|
|
91
|
+
i += ch.length;
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
export function revelarLineas(lineas, n, cursor, ancho = Infinity) {
|
|
96
|
+
const total = caracteresDe(lineas);
|
|
97
|
+
if (n >= total)
|
|
98
|
+
return cursor ? conCursor([...lineas], cursor, ancho) : [...lineas];
|
|
99
|
+
const out = [];
|
|
100
|
+
let quedan = Math.max(0, n);
|
|
101
|
+
for (const l of lineas) {
|
|
102
|
+
const w = anchoVisible(l);
|
|
103
|
+
if (quedan >= w) {
|
|
104
|
+
out.push(l);
|
|
105
|
+
quedan -= w;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const plano = Array.from(sinColor(l));
|
|
109
|
+
let cols = 0;
|
|
110
|
+
let k = 0;
|
|
111
|
+
while (k < plano.length && cols < quedan)
|
|
112
|
+
cols += anchoDeCaracter(plano[k++]);
|
|
113
|
+
while (k < plano.length && plano[k] !== ' ')
|
|
114
|
+
cols += anchoDeCaracter(plano[k++]);
|
|
115
|
+
const parte = cortarVisible(l, cols);
|
|
116
|
+
out.push(sinColor(parte).trim() ? parte : '');
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
while (out.length > 1 && out[out.length - 1] === '' && lineas[out.length - 1] !== '')
|
|
120
|
+
out.pop();
|
|
121
|
+
return cursor ? conCursor(out, cursor, ancho) : out;
|
|
122
|
+
}
|
|
123
|
+
function conCursor(lineas, cursor, ancho) {
|
|
124
|
+
if (!lineas.length)
|
|
125
|
+
return [cursor];
|
|
126
|
+
const i = lineas.length - 1;
|
|
127
|
+
const l = lineas[i];
|
|
128
|
+
if (anchoVisible(l) + 1 < ancho)
|
|
129
|
+
lineas[i] = `${l.replace(/\s+$/, '')}${anchoVisible(l) ? ' ' : ''}${cursor}`;
|
|
130
|
+
return lineas;
|
|
131
|
+
}
|
package/dist/agente.js
CHANGED
|
@@ -153,29 +153,31 @@ export async function hacerTarea(s, pedido, señal, imagenes = []) {
|
|
|
153
153
|
disponibles = r.acciones.disponibles;
|
|
154
154
|
pantalla.progreso(acciones);
|
|
155
155
|
registro.apuntar('turno', { tarea, vuelta, acciones });
|
|
156
|
-
const llamadas =
|
|
156
|
+
const llamadas = r.salida.filter((i) => i.type === 'function_call');
|
|
157
157
|
for (const item of r.salida) {
|
|
158
158
|
s.conversacion.push(item);
|
|
159
159
|
if (item.type === 'message') {
|
|
160
160
|
const texto = textoDeMensaje(item).trim();
|
|
161
161
|
if (texto) {
|
|
162
162
|
respuesta = texto;
|
|
163
|
+
pantalla.espera(t('hace.respuesta'), 'tendido', true);
|
|
163
164
|
pantalla.escribir();
|
|
164
|
-
pantalla.
|
|
165
|
+
await pantalla.textoAnimado(texto, { intermedio: llamadas.length > 0 });
|
|
165
166
|
pantalla.escribir();
|
|
167
|
+
pantalla.espera(null);
|
|
166
168
|
}
|
|
167
169
|
}
|
|
168
|
-
else if (item.type === 'function_call')
|
|
169
|
-
llamadas.push(item);
|
|
170
170
|
}
|
|
171
171
|
if (!llamadas.length)
|
|
172
172
|
break;
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
for (const ll of llamadas) {
|
|
173
|
+
const todas = llamadas.map((l) => ({ name: String(l.name ?? ''), arguments: String(l.arguments ?? '{}') }));
|
|
174
|
+
for (let i = 0; i < llamadas.length; i++) {
|
|
175
|
+
const ll = llamadas[i];
|
|
177
176
|
if (señal.aborted)
|
|
178
177
|
throw new ErrorDeParallel('interrumpido');
|
|
178
|
+
const etiqueta = etiquetaDeTanda(grupoDesde(todas, i));
|
|
179
|
+
if (etiqueta && empiezaGrupo(todas, i))
|
|
180
|
+
pantalla.espera(etiqueta.texto, etiqueta.anim, false);
|
|
179
181
|
const salida = await ejecutarHerramienta(String(ll.name ?? ''), String(ll.arguments ?? '{}'), s.ctx);
|
|
180
182
|
s.conversacion.push({ type: 'function_call_output', call_id: ll.call_id, output: salida });
|
|
181
183
|
}
|
|
@@ -209,6 +211,31 @@ export async function hacerTarea(s, pedido, señal, imagenes = []) {
|
|
|
209
211
|
pantalla.cierre(`${t('estado.hecho')}${sep}${c.gris(textoDeAcciones(acciones))}${sep}${c.gris(duracion(ms))}`);
|
|
210
212
|
return true;
|
|
211
213
|
}
|
|
214
|
+
function familia(nombre) {
|
|
215
|
+
if (nombre === 'write_file' || nombre === 'edit_file')
|
|
216
|
+
return 'cambio';
|
|
217
|
+
if (nombre === 'read_file' || nombre === 'search' || nombre === 'list_files')
|
|
218
|
+
return nombre;
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
export function grupoDesde(llamadas, i) {
|
|
222
|
+
const actual = llamadas[i];
|
|
223
|
+
if (!actual)
|
|
224
|
+
return [];
|
|
225
|
+
const f = familia(actual.name);
|
|
226
|
+
if (!f)
|
|
227
|
+
return [actual];
|
|
228
|
+
let j = i;
|
|
229
|
+
while (j < llamadas.length && familia(llamadas[j].name) === f)
|
|
230
|
+
j++;
|
|
231
|
+
return llamadas.slice(i, j);
|
|
232
|
+
}
|
|
233
|
+
export function empiezaGrupo(llamadas, i) {
|
|
234
|
+
if (i === 0)
|
|
235
|
+
return true;
|
|
236
|
+
const f = familia(llamadas[i].name);
|
|
237
|
+
return !f || f !== familia(llamadas[i - 1].name);
|
|
238
|
+
}
|
|
212
239
|
function cerrarLlamadasPendientes(items) {
|
|
213
240
|
const respondidas = new Set(items.filter((i) => i.type === 'function_call_output').map((i) => i.call_id));
|
|
214
241
|
for (const i of [...items]) {
|
package/dist/cli.js
CHANGED
|
@@ -43,6 +43,8 @@ function opciones() {
|
|
|
43
43
|
ascii: { type: 'boolean' },
|
|
44
44
|
mouse: { type: 'boolean' },
|
|
45
45
|
'no-mouse': { type: 'boolean' },
|
|
46
|
+
'no-animation': { type: 'boolean' },
|
|
47
|
+
animation: { type: 'boolean' },
|
|
46
48
|
version: { type: 'boolean', short: 'v' },
|
|
47
49
|
help: { type: 'boolean', short: 'h' },
|
|
48
50
|
browser: { type: 'boolean' },
|
|
@@ -262,10 +264,40 @@ async function principal() {
|
|
|
262
264
|
const quien = quienEs(cuenta);
|
|
263
265
|
let avisos = ajustes.avisos !== false;
|
|
264
266
|
const conRaton = !v['no-mouse'] && v.mouse !== false && (Boolean(v.mouse) || ajustes.raton !== false);
|
|
267
|
+
pantalla.animar = !v['no-animation'] && v.animation !== false && (Boolean(v.animation) || ajustes.animacion !== false);
|
|
265
268
|
let esperandoEnBandeja = null;
|
|
266
269
|
let control = new AbortController();
|
|
267
270
|
let ocupado = false;
|
|
268
271
|
let superficie = null;
|
|
272
|
+
let bienvenida;
|
|
273
|
+
let consejosDeEntrada = [];
|
|
274
|
+
const confirmarNuevaEnMarcha = async () => {
|
|
275
|
+
if (!superficie)
|
|
276
|
+
return;
|
|
277
|
+
const i = await superficie.elegir(t('nueva.parar'), [{ etiqueta: t('nueva.si'), peligro: true }, { etiqueta: t('nueva.no') }], { detalle: t('nueva.parar_detalle'), elegida: 1 });
|
|
278
|
+
if (i !== 0)
|
|
279
|
+
return;
|
|
280
|
+
superficie.nuevaTrasParar();
|
|
281
|
+
if (ocupado)
|
|
282
|
+
control.abort();
|
|
283
|
+
};
|
|
284
|
+
const empezarDeNuevo = () => {
|
|
285
|
+
sesion.conversacion = [];
|
|
286
|
+
sesion.diario = [];
|
|
287
|
+
sesion.id = randomUUID();
|
|
288
|
+
sesion.ultimaTarea = undefined;
|
|
289
|
+
sesion.ctx.hechos = { ficheros: [], comandos: [] };
|
|
290
|
+
sesion.ctx.permitidos.clear();
|
|
291
|
+
sesion.ctx.deshacer.length = 0;
|
|
292
|
+
if (sesion.ctx.lecturas)
|
|
293
|
+
sesion.ctx.lecturas.length = 0;
|
|
294
|
+
registro.apuntar('nueva', {});
|
|
295
|
+
pantalla.reiniciar();
|
|
296
|
+
if (superficie)
|
|
297
|
+
superficie.reiniciar(bienvenida, consejosDeEntrada);
|
|
298
|
+
else
|
|
299
|
+
pantalla.escribir(t('repl.nueva'));
|
|
300
|
+
};
|
|
269
301
|
let ctrlC = 0;
|
|
270
302
|
const totalDeLaSesion = () => sesion.total.tareas ? c.gris(t('estado.total', { acciones: acciones(sesion.total.acciones), tiempo: duracion(sesion.total.ms) })) : '';
|
|
271
303
|
const despedida = () => [totalDeLaSesion(), c.gris(t('repl.adios'))].filter(Boolean).join('\n ');
|
|
@@ -419,6 +451,7 @@ async function principal() {
|
|
|
419
451
|
if (avisos && superficie?.enfocada === false)
|
|
420
452
|
avisarEnElEscritorio({ titulo: 'parx', texto: t('aviso.te_necesita', { pregunta: texto }) });
|
|
421
453
|
},
|
|
454
|
+
alNuevaEnMarcha: () => void confirmarNuevaEnMarcha(),
|
|
422
455
|
});
|
|
423
456
|
const s = superficie;
|
|
424
457
|
process.on('exit', () => s.restaurarDeUrgencia());
|
|
@@ -437,7 +470,8 @@ async function principal() {
|
|
|
437
470
|
const elegidos = elegirConsejos(leerAjustes().consejos);
|
|
438
471
|
if (elegidos.ids.length)
|
|
439
472
|
guardarAjustes({ consejos: elegidos.estado });
|
|
440
|
-
|
|
473
|
+
consejosDeEntrada = conCarriles || quiereBypass ? [] : elegidos.ids;
|
|
474
|
+
bienvenida = bloqueDeBienvenida({
|
|
441
475
|
version: VERSION,
|
|
442
476
|
quien: quien.includes('@') ? quien : (quien.split(/\s+/)[0] ?? ''),
|
|
443
477
|
carpeta: rutaCorta(raiz, 60),
|
|
@@ -447,7 +481,8 @@ async function principal() {
|
|
|
447
481
|
return nueva ? t('update.disponible', { version: nueva }) : undefined;
|
|
448
482
|
})(),
|
|
449
483
|
peligro: () => sesion.ctx.modo === 'bypass',
|
|
450
|
-
})
|
|
484
|
+
});
|
|
485
|
+
superficie.iniciar(bienvenida, consejosDeEntrada);
|
|
451
486
|
if (quiereBypass)
|
|
452
487
|
await confirmarBypass();
|
|
453
488
|
const mirarBandeja = async () => {
|
|
@@ -819,9 +854,15 @@ async function principal() {
|
|
|
819
854
|
break;
|
|
820
855
|
}
|
|
821
856
|
case 'new':
|
|
822
|
-
|
|
823
|
-
|
|
857
|
+
empezarDeNuevo();
|
|
858
|
+
break;
|
|
859
|
+
case 'animation': {
|
|
860
|
+
const si = ALIAS_SI_NO[arg] ?? (pantalla.animar ? 'no' : 'si');
|
|
861
|
+
pantalla.animar = si === 'si';
|
|
862
|
+
guardarAjustes({ animacion: pantalla.animar });
|
|
863
|
+
pantalla.escribir(pantalla.animar ? t('animacion.on') : t('animacion.off'));
|
|
824
864
|
break;
|
|
865
|
+
}
|
|
825
866
|
case 'undo': {
|
|
826
867
|
const r2 = deshacerUltimo(sesion.ctx);
|
|
827
868
|
pantalla.escribir(r2 ? t('repl.deshecho', { ruta: r2 }) : t('repl.nada_que_deshacer'));
|
package/dist/config.js
CHANGED
|
@@ -92,6 +92,10 @@ export function leerAjustes(dir = carpetaDeConfiguracion()) {
|
|
|
92
92
|
out.modo = MODO_VIEJO[a.modo];
|
|
93
93
|
if (typeof a.avisos === 'boolean')
|
|
94
94
|
out.avisos = a.avisos;
|
|
95
|
+
if (typeof a.raton === 'boolean')
|
|
96
|
+
out.raton = a.raton;
|
|
97
|
+
if (typeof a.animacion === 'boolean')
|
|
98
|
+
out.animacion = a.animacion;
|
|
95
99
|
const k = a.consejos;
|
|
96
100
|
if (k && typeof k === 'object') {
|
|
97
101
|
const n = (x) => (typeof x === 'number' && Number.isFinite(x) && x >= 0 ? Math.floor(x) : 0);
|
package/dist/herramientas.js
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname, join, relative } from 'node:path';
|
|
|
3
3
|
import { contarCambios, diffDeLineas } from "./diff.js";
|
|
4
4
|
import { cuantos, t } from "./textos.js";
|
|
5
5
|
import { c } from "./marca.js";
|
|
6
|
+
import { elipsis } from "./actividad.js";
|
|
6
7
|
import { estaDentro, familiaDeComando, nivelDeComando, resolverRuta, rutaReal } from "./seguridad.js";
|
|
7
8
|
import { ejecutarComando, recortar } from "./shell.js";
|
|
8
9
|
const nulable = (tipo, descripcion) => ({ type: [tipo, 'null'], description: descripcion });
|
|
@@ -211,7 +212,15 @@ export function etiquetaDeComando(comando) {
|
|
|
211
212
|
return t('hace.instalar');
|
|
212
213
|
const primera = c1.split(/\s*(?:&&|\|\||;|\|)\s*/)[0] ?? c1;
|
|
213
214
|
const corto = primera.split(/\s+/).filter((p) => !/^[A-Z_]+=/.test(p)).slice(0, 3).join(' ');
|
|
214
|
-
return t('hace.ejecutar', { comando: corto.length > 28 ? `${corto.slice(0, 27)}
|
|
215
|
+
return t('hace.ejecutar', { comando: corto.length > 28 ? `${corto.slice(0, 27)}${elipsis()}` : corto });
|
|
216
|
+
}
|
|
217
|
+
export function rutaParaLaBarra(ruta) {
|
|
218
|
+
const r = ruta.trim().replace(/^\.\/(?=.)/, '') || '.';
|
|
219
|
+
if (r.length <= 30)
|
|
220
|
+
return r;
|
|
221
|
+
const partes = r.split(/[\\/]/).filter(Boolean);
|
|
222
|
+
const cola = partes.slice(-2).join('/');
|
|
223
|
+
return cola.length <= 30 ? `${elipsis()}/${cola}` : `${elipsis()}/${partes[partes.length - 1]}`;
|
|
215
224
|
}
|
|
216
225
|
export function etiquetaDeTanda(llamadas) {
|
|
217
226
|
if (!llamadas.length)
|
|
@@ -225,7 +234,7 @@ export function etiquetaDeTanda(llamadas) {
|
|
|
225
234
|
}
|
|
226
235
|
});
|
|
227
236
|
const nombres = llamadas.map((l) => l.name);
|
|
228
|
-
const base = (p) => String(p ?? '.')
|
|
237
|
+
const base = (p) => rutaParaLaBarra(String(p ?? '.'));
|
|
229
238
|
const cmd = nombres.indexOf('run_command');
|
|
230
239
|
if (cmd !== -1)
|
|
231
240
|
return { texto: etiquetaDeComando(String(args[cmd].command ?? '')), anim: 'tren' };
|
|
@@ -244,9 +253,14 @@ export function etiquetaDeTanda(llamadas) {
|
|
|
244
253
|
const b = nombres.indexOf('search');
|
|
245
254
|
if (b !== -1) {
|
|
246
255
|
const patron = String(args[b].pattern ?? '');
|
|
247
|
-
|
|
256
|
+
if (!patron.trim())
|
|
257
|
+
return { texto: t('hace.buscar_ficheros'), anim: 'lectura' };
|
|
258
|
+
return { texto: t('hace.buscar', { patron: patron.length > 24 ? `${patron.slice(0, 23)}${elipsis()}` : patron }), anim: 'lectura' };
|
|
248
259
|
}
|
|
249
|
-
|
|
260
|
+
const l = nombres.indexOf('list_files');
|
|
261
|
+
if (l !== -1)
|
|
262
|
+
return { texto: t('hace.listar', { ruta: base(args[l].path ?? '.') }), anim: 'lectura' };
|
|
263
|
+
return { texto: t('hace.generico'), anim: 'ruta' };
|
|
250
264
|
}
|
|
251
265
|
export async function ejecutarHerramienta(nombre, argumentos, ctx) {
|
|
252
266
|
let a;
|
package/dist/textos.js
CHANGED
|
@@ -19,7 +19,7 @@ export const TEXTOS = {
|
|
|
19
19
|
'tui.si_ediciones': 'Yes, allow all edits',
|
|
20
20
|
'tui.si_ediciones_corto': 'All edits',
|
|
21
21
|
'tui.una_vez': 'Yes, this once',
|
|
22
|
-
'tui.atajos': 'Shift+Tab mode · ↑ ↓ history · ! runs a shell command · Esc stops a task · Ctrl+C twice exits · Ctrl+L redraws',
|
|
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
24
|
'tui.al_fondo': 'Jump to bottom',
|
|
25
25
|
'tui.nuevas': '{n} new',
|
|
@@ -76,21 +76,22 @@ export const TEXTOS = {
|
|
|
76
76
|
'carpeta.lista': 'Allowed folders: {lista}',
|
|
77
77
|
'carpeta.ninguna': 'Only the project folder. Add one with /folder <path>.',
|
|
78
78
|
'carpeta.no_existe': 'That folder does not exist: {ruta}',
|
|
79
|
-
'trabajo.
|
|
80
|
-
'trabajo.
|
|
81
|
-
'trabajo.
|
|
82
|
-
'trabajo.
|
|
83
|
-
'trabajo.
|
|
84
|
-
'trabajo.
|
|
85
|
-
'
|
|
86
|
-
'
|
|
79
|
+
'trabajo.pensando': 'Thinking',
|
|
80
|
+
'trabajo.planear': 'Planning the steps',
|
|
81
|
+
'trabajo.resultados': 'Looking at the results',
|
|
82
|
+
'trabajo.siguiente': 'Deciding the next step',
|
|
83
|
+
'trabajo.sigue': 'Still thinking',
|
|
84
|
+
'trabajo.servidor': 'Waiting for the server',
|
|
85
|
+
'hace.respuesta': 'Writing the answer',
|
|
86
|
+
'hace.generico': 'Working',
|
|
87
|
+
'hace.buscar_ficheros': 'Searching files',
|
|
87
88
|
'hace.leer': 'Reading {n} files',
|
|
88
89
|
'hace.leer_uno': 'Reading {ruta}',
|
|
89
90
|
'hace.listar': 'Looking around {ruta}',
|
|
90
91
|
'hace.buscar': 'Searching for {patron}',
|
|
91
92
|
'hace.escribir': 'Writing {ruta}',
|
|
92
93
|
'hace.editar': 'Editing {ruta}',
|
|
93
|
-
'hace.cambios': '
|
|
94
|
+
'hace.cambios': 'Editing {n} files',
|
|
94
95
|
'hace.tests': 'Running tests',
|
|
95
96
|
'hace.build': 'Building',
|
|
96
97
|
'hace.instalar': 'Installing',
|
|
@@ -181,6 +182,12 @@ export const TEXTOS = {
|
|
|
181
182
|
'repl.adios': 'See you.',
|
|
182
183
|
'repl.registro': 'Log of this session: {ruta}',
|
|
183
184
|
'repl.nueva': 'New conversation.',
|
|
185
|
+
'nueva.parar': 'Stop and start new?',
|
|
186
|
+
'nueva.parar_detalle': 'parx is working. This stops it and clears the conversation.',
|
|
187
|
+
'nueva.si': 'Stop and start new',
|
|
188
|
+
'nueva.no': 'Keep working',
|
|
189
|
+
'animacion.on': 'Answers are written out as they arrive. Any key shows the rest at once.',
|
|
190
|
+
'animacion.off': 'Answers appear whole, at once. /animation on to turn it back on.',
|
|
184
191
|
'repl.desconocido': 'Unknown command. Type /help.',
|
|
185
192
|
'repl.deshecho': 'Undone: {ruta}',
|
|
186
193
|
'repl.nada_que_deshacer': 'Nothing to undo.',
|
|
@@ -261,6 +268,7 @@ export const COMANDOS = [
|
|
|
261
268
|
['notify', 'Desktop notifications: on or off'],
|
|
262
269
|
['tips', 'Tips when it opens: on or off'],
|
|
263
270
|
['mouse', 'Clicks and wheel in parx: on or off'],
|
|
271
|
+
['animation', 'Answers written out as they arrive: on or off'],
|
|
264
272
|
['log', 'Where the log of what it does is kept'],
|
|
265
273
|
['exit', 'Leave parx'],
|
|
266
274
|
];
|
|
@@ -273,6 +281,7 @@ export const ALIAS_COMANDO = {
|
|
|
273
281
|
mission: 'mission', mision: 'mission', misión: 'mission',
|
|
274
282
|
inbox: 'inbox', bandeja: 'inbox', approvals: 'inbox', waiting: 'inbox',
|
|
275
283
|
mouse: 'mouse', raton: 'mouse', ratón: 'mouse',
|
|
284
|
+
animation: 'animation', animations: 'animation', animacion: 'animation', animación: 'animation',
|
|
276
285
|
report: 'report', informe: 'report',
|
|
277
286
|
teach: 'teach', class: 'teach', clase: 'teach',
|
|
278
287
|
undo: 'undo', deshacer: 'undo',
|
|
@@ -304,6 +313,7 @@ export const USO = [
|
|
|
304
313
|
' --dir <folder> the project folder (default: current)',
|
|
305
314
|
' --ascii plain characters only (also NO_COLOR for no color)',
|
|
306
315
|
' --no-mouse leave the mouse to the terminal (no clicks or wheel in parx)',
|
|
316
|
+
' --no-animation show answers whole, without writing them out',
|
|
307
317
|
' --version, --help',
|
|
308
318
|
'',
|
|
309
319
|
'Sign in:',
|
package/dist/tui.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ALIAS_COMANDO, 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
6
|
export { anchoDe } from "./texto.js";
|
|
7
|
+
import { conElipsis, fraseDeEspera } from "./actividad.js";
|
|
7
8
|
export const MARGEN = 2;
|
|
8
9
|
export function anchoUtil(ancho) {
|
|
9
10
|
return Math.max(10, ancho - MARGEN * 2);
|
|
@@ -287,9 +288,9 @@ function teclaDeRaton(b, x, y, soltar, botonX10) {
|
|
|
287
288
|
}
|
|
288
289
|
export const RATON_SI = '\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h';
|
|
289
290
|
export const RATON_NO = '\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1006l';
|
|
290
|
-
const CON_VALOR = new Set(['mode', 'model', 'tips', 'notify', 'teach', 'mouse']);
|
|
291
|
+
const CON_VALOR = new Set(['mode', 'model', 'tips', 'notify', 'teach', 'mouse', 'animation']);
|
|
291
292
|
const CON_TEXTO = new Set(['folder']);
|
|
292
|
-
const SE_MANDAN_SOLOS = new Set(['teach', 'notify', 'tips', 'folder', 'mouse']);
|
|
293
|
+
const SE_MANDAN_SOLOS = new Set(['teach', 'notify', 'tips', 'folder', 'mouse', 'animation']);
|
|
293
294
|
const MODOS_DEL_MENU = ['ask', 'auto', 'read', 'bypass'];
|
|
294
295
|
export function opcionesDelMenu(valor) {
|
|
295
296
|
if (!valor.startsWith('/') || valor.includes('\n'))
|
|
@@ -321,8 +322,8 @@ export function opcionesDelMenu(valor) {
|
|
|
321
322
|
: cmd === 'model'
|
|
322
323
|
? ORDEN_DE_MODELOS.map((k) => [k, `${MODELOS[k].nombre} ${MODELOS[k].version}: ${MODELOS[k].descripcion}`])
|
|
323
324
|
: [
|
|
324
|
-
['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' : 'Show them when parx opens'],
|
|
325
|
-
['off', cmd === 'teach' ? 'Back to normal' : cmd === 'notify' ? 'No desktop notifications' : cmd === 'mouse' ? 'The terminal selects and scrolls as usual' : 'Don’t show them again'],
|
|
325
|
+
['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' : 'Show them when parx opens'],
|
|
326
|
+
['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' : 'Don’t show them again'],
|
|
326
327
|
];
|
|
327
328
|
return valores
|
|
328
329
|
.filter(([v]) => v.startsWith(q))
|
|
@@ -380,17 +381,11 @@ export function duracion(ms) {
|
|
|
380
381
|
export function acciones(n) {
|
|
381
382
|
return n === 1 ? tt('estado.accion') : tt('estado.acciones', { n: n.toLocaleString('en-US') });
|
|
382
383
|
}
|
|
383
|
-
|
|
384
|
-
export const MS_POR_FRASE = 2800;
|
|
385
|
-
export function fraseDeEspera(ms, semilla = 0) {
|
|
386
|
-
const vuelta = Math.floor(Math.max(0, ms) / MS_POR_FRASE);
|
|
387
|
-
if (vuelta === 0)
|
|
388
|
-
return `${tt(FRASES[0])}…`;
|
|
389
|
-
return `${tt(FRASES[1 + ((semilla + vuelta - 1) % (FRASES.length - 1))])}…`;
|
|
390
|
-
}
|
|
384
|
+
export { fraseDeEspera } from "./actividad.js";
|
|
391
385
|
export function ponerTextoDeTrabajo(w, texto, anim = 'ruta', vivo = false, ahora = Date.now()) {
|
|
392
386
|
if (texto) {
|
|
393
387
|
w.texto = texto;
|
|
388
|
+
w.trasHerramientas = true;
|
|
394
389
|
w.anim = anim;
|
|
395
390
|
w.vivo = vivo;
|
|
396
391
|
w.literalHasta = ahora + 1800;
|
|
@@ -401,12 +396,12 @@ export function ponerTextoDeTrabajo(w, texto, anim = 'ruta', vivo = false, ahora
|
|
|
401
396
|
}
|
|
402
397
|
}
|
|
403
398
|
export function nuevoTrabajo(ahora = Date.now()) {
|
|
404
|
-
return { texto: null, literalHasta: 0, vivo: false, anim: 'ruta', desde: ahora, esperaDesde: ahora, paso: 0, acciones: null,
|
|
399
|
+
return { texto: null, literalHasta: 0, vivo: false, anim: 'ruta', desde: ahora, esperaDesde: ahora, paso: 0, acciones: null, trasHerramientas: false };
|
|
405
400
|
}
|
|
406
401
|
export function textoDeTrabajo(w, ahora) {
|
|
407
402
|
if (w.texto && (w.vivo || ahora < w.literalHasta))
|
|
408
|
-
return { texto: w.texto, anim: w.anim };
|
|
409
|
-
return { texto: fraseDeEspera(ahora - w.esperaDesde, w.
|
|
403
|
+
return { texto: conElipsis(w.texto), anim: w.anim };
|
|
404
|
+
return { texto: fraseDeEspera(ahora - w.esperaDesde, w.trasHerramientas), anim: 'ruta' };
|
|
410
405
|
}
|
|
411
406
|
export function filaDeTrabajo(w, ahora, ancho = Infinity) {
|
|
412
407
|
const { texto, anim } = textoDeTrabajo(w, ahora);
|
|
@@ -712,6 +707,9 @@ export function opcionesDePregunta(tipo) {
|
|
|
712
707
|
return { opciones: [tt('tui.si'), tt('tui.siempre'), tt('tui.no')], cortas: [tt('tui.si'), tt('tui.siempre_corto'), tt('tui.no')], valores: ['si', 'siempre', 'no'], elegida: 0 };
|
|
713
708
|
}
|
|
714
709
|
}
|
|
710
|
+
export function esNueva(v) {
|
|
711
|
+
return ALIAS_COMANDO[/^\/(\S+)$/.exec(v.trim())?.[1]?.toLowerCase() ?? ''] === 'new';
|
|
712
|
+
}
|
|
715
713
|
const MAX_LINEAS = 4000;
|
|
716
714
|
const MAX_BLOQUES = 1500;
|
|
717
715
|
const PEGADO_LARGO = 280;
|
|
@@ -1045,6 +1043,98 @@ export class Superficie {
|
|
|
1045
1043
|
this.escribir(s);
|
|
1046
1044
|
this.pintar();
|
|
1047
1045
|
}
|
|
1046
|
+
refrescarUltimo() {
|
|
1047
|
+
const b = this.ultimoBloque();
|
|
1048
|
+
if (!b)
|
|
1049
|
+
return;
|
|
1050
|
+
const k = this.ultimoAlto;
|
|
1051
|
+
if (this.lineas.length < k)
|
|
1052
|
+
return;
|
|
1053
|
+
const nuevas = sinBlancoDoble(b(this.ancho), this.ultimoTrasBlanco);
|
|
1054
|
+
const base = this.lineas.length - k;
|
|
1055
|
+
const viejas = this.lineas.slice(base);
|
|
1056
|
+
if (nuevas.length === k && nuevas.every((l, i) => l === viejas[i]))
|
|
1057
|
+
return;
|
|
1058
|
+
if (nuevas.length < k) {
|
|
1059
|
+
this.bloques[this.bloques.length - 1] = b;
|
|
1060
|
+
this.recomponer();
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
this.lineas.splice(base, k, ...nuevas);
|
|
1064
|
+
this.ultimoAlto = nuevas.length;
|
|
1065
|
+
if (!this.activa) {
|
|
1066
|
+
this.volcadas = this.lineas.length;
|
|
1067
|
+
this.recortarMemoria();
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const subido = this.subidas > 0 || this.volcadas < base + k;
|
|
1071
|
+
const n = this.carrilPintadas || componerCarril(this.datos(), this.ancho, this.alto).carril.length;
|
|
1072
|
+
if (subido) {
|
|
1073
|
+
const desde = this.desplazadas(n) - this.subidas;
|
|
1074
|
+
const cambiaVisible = viejas.some((l, i) => base + i < this.volcadas && base + i >= desde && nuevas[i] !== l);
|
|
1075
|
+
if (cambiaVisible)
|
|
1076
|
+
this.vistaSucia = true;
|
|
1077
|
+
this.recortarMemoria();
|
|
1078
|
+
if (cambiaVisible || nuevas.length !== k)
|
|
1079
|
+
this.pintar();
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
const top = this.filaDelCarril(n);
|
|
1083
|
+
const tapadas = top - this.encimaPintadas;
|
|
1084
|
+
let s = '';
|
|
1085
|
+
for (let i = 0; i < k; i++) {
|
|
1086
|
+
if (nuevas[i] === viejas[i])
|
|
1087
|
+
continue;
|
|
1088
|
+
const fila = base + i - this.desplazadas(n) + 1;
|
|
1089
|
+
if (fila >= 1 && fila < tapadas)
|
|
1090
|
+
s += `\x1b[${fila};1H\x1b[2K${nuevas[i]}`;
|
|
1091
|
+
}
|
|
1092
|
+
if (s)
|
|
1093
|
+
this.escribir(`\x1b[?2026h\x1b7${s}\x1b8\x1b[?2026l`);
|
|
1094
|
+
if (nuevas.length > k) {
|
|
1095
|
+
this.escribir(this.destapar());
|
|
1096
|
+
this.volcar();
|
|
1097
|
+
this.pintar();
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
reiniciar(bienvenida, consejos = []) {
|
|
1101
|
+
this.bloques = [];
|
|
1102
|
+
this.lineas = [];
|
|
1103
|
+
this.volcadas = 0;
|
|
1104
|
+
this.subidas = 0;
|
|
1105
|
+
this.vistaSucia = false;
|
|
1106
|
+
this.cola = [];
|
|
1107
|
+
this.aviso = null;
|
|
1108
|
+
this.ultimoAlto = 0;
|
|
1109
|
+
this.ultimoTrasBlanco = false;
|
|
1110
|
+
this.encimaPintadas = 0;
|
|
1111
|
+
this.sobre = null;
|
|
1112
|
+
this.pulsado = null;
|
|
1113
|
+
this.ctrlC = 0;
|
|
1114
|
+
this.alRevelar = null;
|
|
1115
|
+
this.ponerBienvenida(bienvenida, consejos);
|
|
1116
|
+
this.recomponer();
|
|
1117
|
+
}
|
|
1118
|
+
ponerBienvenida(bienvenida, consejos = []) {
|
|
1119
|
+
if (bienvenida)
|
|
1120
|
+
this.bloques.push(bienvenida);
|
|
1121
|
+
this.consejos = [];
|
|
1122
|
+
this.consejosVisibles = false;
|
|
1123
|
+
if (consejos.length) {
|
|
1124
|
+
const ocupado = (bienvenida ? bienvenida(this.ancho).length : 0) + componerCarril(this.datos(), this.ancho, this.alto).carril.length;
|
|
1125
|
+
let n = consejos.length;
|
|
1126
|
+
while (n > 0 && ocupado + bloqueDeConsejos(() => consejos.slice(0, n))(this.ancho).length > this.alto)
|
|
1127
|
+
n--;
|
|
1128
|
+
this.consejos = consejos.slice(0, n);
|
|
1129
|
+
this.consejosVisibles = n > 0;
|
|
1130
|
+
if (n > 0)
|
|
1131
|
+
this.bloques.push(bloqueDeConsejos(() => (this.consejosVisibles ? this.consejos : [])));
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
alRevelar = null;
|
|
1135
|
+
revelando(fn) {
|
|
1136
|
+
this.alRevelar = fn;
|
|
1137
|
+
}
|
|
1048
1138
|
recomponer(alEmpezar = false) {
|
|
1049
1139
|
if (!this.activa)
|
|
1050
1140
|
return;
|
|
@@ -1098,6 +1188,10 @@ export class Superficie {
|
|
|
1098
1188
|
this.volcar();
|
|
1099
1189
|
this.pintar();
|
|
1100
1190
|
}
|
|
1191
|
+
nuevaTrasParar() {
|
|
1192
|
+
this.cola = ['/new'];
|
|
1193
|
+
this.mostrarAviso(null);
|
|
1194
|
+
}
|
|
1101
1195
|
get _vista() {
|
|
1102
1196
|
return { subidas: this.subidas, pendientes: this.lineas.length - this.volcadas };
|
|
1103
1197
|
}
|
|
@@ -1175,18 +1269,7 @@ export class Superficie {
|
|
|
1175
1269
|
if (this.activa)
|
|
1176
1270
|
return;
|
|
1177
1271
|
this.activa = true;
|
|
1178
|
-
|
|
1179
|
-
this.bloques.push(bienvenida);
|
|
1180
|
-
if (consejos.length) {
|
|
1181
|
-
const ocupado = (bienvenida ? bienvenida(this.ancho).length : 0) + componerCarril(this.datos(), this.ancho, this.alto).carril.length;
|
|
1182
|
-
let n = consejos.length;
|
|
1183
|
-
while (n > 0 && ocupado + bloqueDeConsejos(() => consejos.slice(0, n))(this.ancho).length > this.alto)
|
|
1184
|
-
n--;
|
|
1185
|
-
this.consejos = consejos.slice(0, n);
|
|
1186
|
-
this.consejosVisibles = n > 0;
|
|
1187
|
-
if (n > 0)
|
|
1188
|
-
this.bloques.push(bloqueDeConsejos(() => (this.consejosVisibles ? this.consejos : [])));
|
|
1189
|
-
}
|
|
1272
|
+
this.ponerBienvenida(bienvenida, consejos);
|
|
1190
1273
|
this.escribir(this.modosSi());
|
|
1191
1274
|
try {
|
|
1192
1275
|
this.o.entrada.setRawMode?.(true);
|
|
@@ -1321,7 +1404,7 @@ export class Superficie {
|
|
|
1321
1404
|
const r = enCola;
|
|
1322
1405
|
this.ejemplo = null;
|
|
1323
1406
|
this.mostrarAviso(this.cola.length ? tt('tui.en_cola_n', { n: this.cola.length }) : null);
|
|
1324
|
-
if (r.trim() && r.trim() !== '!')
|
|
1407
|
+
if (r.trim() && r.trim() !== '!' && !esNueva(r.trim()))
|
|
1325
1408
|
this.anadir(this.bloqueDeEnvio(r));
|
|
1326
1409
|
return Promise.resolve(this.expandir(r));
|
|
1327
1410
|
}
|
|
@@ -1479,6 +1562,10 @@ export class Superficie {
|
|
|
1479
1562
|
this.posHistorial = this.historial.length;
|
|
1480
1563
|
this.irAlFondo();
|
|
1481
1564
|
if (this.trabajo || !this.resolverLectura) {
|
|
1565
|
+
if (this.trabajo && this.o.alNuevaEnMarcha && esNueva(v)) {
|
|
1566
|
+
this.o.alNuevaEnMarcha();
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1482
1569
|
if (v) {
|
|
1483
1570
|
this.cola.push(crudo);
|
|
1484
1571
|
this.mostrarAviso(this.cola.length > 1 ? tt('tui.en_cola_n', { n: this.cola.length }) : tt('tui.en_cola'));
|
|
@@ -1487,7 +1574,7 @@ export class Superficie {
|
|
|
1487
1574
|
}
|
|
1488
1575
|
const r = this.resolverLectura;
|
|
1489
1576
|
this.resolverLectura = null;
|
|
1490
|
-
if (v && v !== '!')
|
|
1577
|
+
if (v && v !== '!' && !esNueva(v))
|
|
1491
1578
|
this.anadir(this.bloqueDeEnvio(crudo));
|
|
1492
1579
|
else
|
|
1493
1580
|
this.pintar();
|
|
@@ -1507,6 +1594,11 @@ export class Superficie {
|
|
|
1507
1594
|
}
|
|
1508
1595
|
if (k.t === 'raton')
|
|
1509
1596
|
return this.alRaton(k);
|
|
1597
|
+
if (this.alRevelar && k.t !== 'pagina_arriba' && k.t !== 'pagina_abajo' && k.t !== 'fondo') {
|
|
1598
|
+
const f = this.alRevelar;
|
|
1599
|
+
this.alRevelar = null;
|
|
1600
|
+
f();
|
|
1601
|
+
}
|
|
1510
1602
|
if (k.t === 'pagina_arriba' || k.t === 'pagina_abajo')
|
|
1511
1603
|
return this.desplazar((k.t === 'pagina_arriba' ? 1 : -1) * Math.max(1, this.filasDeVista() - 2));
|
|
1512
1604
|
if (k.t === 'fondo')
|
package/dist/ui.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { animacion, c, capacidades, SIMBOLOS } from "./marca.js";
|
|
2
2
|
import { cuantos, t } from "./textos.js";
|
|
3
3
|
import { trozos } from "./diff.js";
|
|
4
|
+
import { caracteresDe, caracteresVisibles, cursorEncendido, duracionDeRevelado, MS_MAX_INTERMEDIO, MS_MAX_RESPUESTA, MS_POR_FOTOGRAMA, revelarLineas, simboloDeCursor } from "./actividad.js";
|
|
4
5
|
import { anchoDe, envolver, recortar, recortarMedio, rellenar, sinColor } from "./texto.js";
|
|
5
6
|
import { anchoUtil, bloqueDeCierre, bloqueDeLinea, bloqueDeTexto, filaDeTrabajo, MARGEN, nuevoTrabajo, opcionesDePregunta, ponerTextoDeTrabajo, } from "./tui.js";
|
|
6
7
|
export function leerRespuesta(texto, tipo = 'normal') {
|
|
@@ -89,6 +90,8 @@ export class Pantalla {
|
|
|
89
90
|
};
|
|
90
91
|
}
|
|
91
92
|
bloque(b, grupo = 'otro') {
|
|
93
|
+
if (this.revelado)
|
|
94
|
+
this.terminarRevelado();
|
|
92
95
|
const bc = this.conCarril(this.conAire(b, grupo));
|
|
93
96
|
if (this.superficie) {
|
|
94
97
|
this.superficie.anadir(bc);
|
|
@@ -290,42 +293,147 @@ export class Pantalla {
|
|
|
290
293
|
}
|
|
291
294
|
}
|
|
292
295
|
texto(md) {
|
|
293
|
-
|
|
294
|
-
this.bloque((
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
296
|
+
this.terminarRevelado();
|
|
297
|
+
this.bloque(bloqueDeMarkdown(md), 'texto');
|
|
298
|
+
}
|
|
299
|
+
animar = true;
|
|
300
|
+
revelado = null;
|
|
301
|
+
terminarRevelado() {
|
|
302
|
+
const f = this.revelado;
|
|
303
|
+
this.revelado = null;
|
|
304
|
+
f?.();
|
|
305
|
+
}
|
|
306
|
+
textoAnimado(md, o = {}) {
|
|
307
|
+
this.terminarRevelado();
|
|
308
|
+
const conSuperficie = this.superficie !== null;
|
|
309
|
+
if (!this.animar || this.carril || !this.tty || (!conSuperficie && !process.stdout.isTTY)) {
|
|
310
|
+
this.texto(md);
|
|
311
|
+
return Promise.resolve();
|
|
312
|
+
}
|
|
313
|
+
const completo = bloqueDeMarkdown(md);
|
|
314
|
+
const maxMs = o.intermedio ? MS_MAX_INTERMEDIO : MS_MAX_RESPUESTA;
|
|
315
|
+
const estado = { inicio: Date.now(), fin: false, ahora: Date.now() };
|
|
316
|
+
const total = caracteresDe(completo(this.superficie?.ancho ?? Math.max(20, process.stdout.columns || 80)));
|
|
317
|
+
const duracion = duracionDeRevelado(total, maxMs);
|
|
318
|
+
const parcial = (ancho) => {
|
|
319
|
+
const lineas = completo(ancho);
|
|
320
|
+
if (estado.fin)
|
|
321
|
+
return lineas;
|
|
322
|
+
const ms = estado.ahora - estado.inicio;
|
|
323
|
+
const n = caracteresVisibles(caracteresDe(lineas), ms, duracion);
|
|
324
|
+
return revelarLineas(lineas, n, cursorEncendido(ms) ? simboloDeCursor() : ' ', anchoUtil(ancho) + MARGEN * 2);
|
|
325
|
+
};
|
|
326
|
+
return new Promise((resolver) => {
|
|
327
|
+
let reloj = null;
|
|
328
|
+
const acabar = () => {
|
|
329
|
+
if (estado.fin)
|
|
330
|
+
return;
|
|
331
|
+
estado.fin = true;
|
|
332
|
+
if (reloj)
|
|
333
|
+
clearInterval(reloj);
|
|
334
|
+
if (this.revelado === acabar)
|
|
335
|
+
this.revelado = null;
|
|
336
|
+
this.superficie?.revelando(null);
|
|
337
|
+
if (conSuperficie)
|
|
338
|
+
this.superficie.refrescarUltimo();
|
|
339
|
+
else
|
|
340
|
+
this.revelarEnLinea(completo, Infinity);
|
|
341
|
+
resolver();
|
|
342
|
+
};
|
|
343
|
+
if (conSuperficie) {
|
|
344
|
+
this.bloque(parcial, 'texto');
|
|
345
|
+
this.superficie.revelando(acabar);
|
|
326
346
|
}
|
|
327
|
-
|
|
328
|
-
|
|
347
|
+
else {
|
|
348
|
+
this.pausarLinea();
|
|
349
|
+
this.ultimoGrupo = 'texto';
|
|
350
|
+
this.lineaEnCurso = { escritas: 0, parcial: '' };
|
|
351
|
+
this.revelarEnLinea(completo, 0);
|
|
352
|
+
}
|
|
353
|
+
this.revelado = acabar;
|
|
354
|
+
const fotograma = () => {
|
|
355
|
+
estado.ahora = Date.now();
|
|
356
|
+
if (estado.ahora - estado.inicio >= duracion)
|
|
357
|
+
return acabar();
|
|
358
|
+
if (conSuperficie)
|
|
359
|
+
this.superficie.refrescarUltimo();
|
|
360
|
+
else
|
|
361
|
+
this.revelarEnLinea(completo, caracteresVisibles(total, estado.ahora - estado.inicio, duracion), cursorEncendido(estado.ahora - estado.inicio));
|
|
362
|
+
};
|
|
363
|
+
reloj = setInterval(fotograma, MS_POR_FOTOGRAMA);
|
|
364
|
+
reloj.unref?.();
|
|
365
|
+
});
|
|
329
366
|
}
|
|
367
|
+
lineaEnCurso = null;
|
|
368
|
+
revelarEnLinea(completo, n, cursor = false) {
|
|
369
|
+
const l = this.lineaEnCurso;
|
|
370
|
+
if (!l)
|
|
371
|
+
return;
|
|
372
|
+
const ancho = Math.max(20, process.stdout.columns || capacidades().ancho || 80);
|
|
373
|
+
const lineas = completo(ancho);
|
|
374
|
+
const vistas = revelarLineas(lineas, n, null);
|
|
375
|
+
let s = '';
|
|
376
|
+
while (l.escritas < vistas.length - 1 || (n >= caracteresDe(lineas) && l.escritas < lineas.length)) {
|
|
377
|
+
s += `\r\x1b[2K${lineas[l.escritas]}\n`;
|
|
378
|
+
l.escritas++;
|
|
379
|
+
l.parcial = '';
|
|
380
|
+
}
|
|
381
|
+
if (n < caracteresDe(lineas)) {
|
|
382
|
+
const parcial = vistas[l.escritas] ?? '';
|
|
383
|
+
s += `\r\x1b[2K${parcial}${cursor ? `${anchoDe(parcial) ? ' ' : ''}${simboloDeCursor()}` : ''}`;
|
|
384
|
+
l.parcial = parcial;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
this.lineaEnCurso = null;
|
|
388
|
+
if (lineas.length)
|
|
389
|
+
this.enBlanco = lineas[lineas.length - 1].trim() === '';
|
|
390
|
+
}
|
|
391
|
+
if (s)
|
|
392
|
+
process.stdout.write(s);
|
|
393
|
+
}
|
|
394
|
+
reiniciar() {
|
|
395
|
+
this.terminarRevelado();
|
|
396
|
+
this.ultimoGrupo = null;
|
|
397
|
+
this.enBlanco = true;
|
|
398
|
+
this.lecturas = null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
export function bloqueDeMarkdown(md) {
|
|
402
|
+
const lineas = md.replace(/\r/g, '').split('\n');
|
|
403
|
+
return (ancho) => {
|
|
404
|
+
const util = anchoUtil(ancho);
|
|
405
|
+
const pre = ' '.repeat(MARGEN);
|
|
406
|
+
const out = [];
|
|
407
|
+
let enBloque = false;
|
|
408
|
+
for (const linea of lineas) {
|
|
409
|
+
if (/^```/.test(linea.trim())) {
|
|
410
|
+
enBloque = !enBloque;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (enBloque) {
|
|
414
|
+
out.push(`${pre} ${c.marca(recortar(linea.replace(/\t/g, ' '), util - 2))}`);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const titulo = /^#{1,6}\s+(.*)$/.exec(linea);
|
|
418
|
+
if (titulo) {
|
|
419
|
+
for (const l of envolver(c.negrita(titulo[1]), util))
|
|
420
|
+
out.push(`${pre}${l}`);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
let l = linea;
|
|
424
|
+
let sangria = '';
|
|
425
|
+
const lista = /^(\s*)([-*]|\d+[.)])\s+/.exec(l);
|
|
426
|
+
if (lista) {
|
|
427
|
+
const marca = /\d/.test(lista[2]) ? lista[2] : SIMBOLOS.punto === '●' ? '·' : '-';
|
|
428
|
+
l = `${lista[1]}${c.claro(marca)} ${l.slice(lista[0].length)}`;
|
|
429
|
+
sangria = ' '.repeat(anchoDe(lista[1]) + anchoDe(marca) + 1);
|
|
430
|
+
}
|
|
431
|
+
l = l.replace(/\*\*([^*]+)\*\*/g, (_m, x) => c.negrita(x));
|
|
432
|
+
l = l.replace(/`([^`]+)`/g, (_m, x) => c.marca(x));
|
|
433
|
+
for (const p of envolver(l, util, sangria))
|
|
434
|
+
out.push(`${pre}${p}`);
|
|
435
|
+
}
|
|
436
|
+
return out;
|
|
437
|
+
};
|
|
330
438
|
}
|
|
331
439
|
export { bloqueDeLinea };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getparallel/parx",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "parx: the coding agent of Parallel in your terminal. Reads and edits your project, runs commands and tests, and does coding tasks end to end. Mac, Windows and Linux.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"parx",
|