@getparallel/parx 0.4.1 → 0.4.3
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 +25 -0
- package/README.md +20 -2
- package/dist/agente.js +9 -6
- package/dist/api.js +6 -0
- package/dist/bash.js +89 -0
- package/dist/cli.js +206 -16
- package/dist/consejos.js +4 -1
- package/dist/herramientas.js +12 -8
- package/dist/informe.js +6 -0
- package/dist/marca.js +26 -0
- package/dist/textos.js +50 -2
- package/dist/tui.js +693 -140
- package/dist/ui.js +66 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,31 @@ 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.3 (2026-10-11)
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
|
|
13
|
+
## 0.4.2 (2026-10-11)
|
|
14
|
+
|
|
15
|
+
- Bash mode: type `!` first and the input turns amber with a `bash` label; Enter runs the command in the project folder, its output streams into the conversation with the exit code, and the agent sees it next turn. Read only refuses, «Careful» commands ask first, credentials stay out of the conversation, Esc or Ctrl+C stops it.
|
|
16
|
+
- Everything is clickable: permission questions, Accept/Decline, the `/` menu and its values, `/inbox` and the status row are buttons («[ 1 Yes ] [ 2 Always ] [ 3 No ]») with hover; the keyboard works as before. `/mouse on|off` and `--no-mouse`; Shift+drag selects text. The terminal always gets the mouse back (exit, Ctrl+Z, kill, crash).
|
|
17
|
+
- The wheel and PgUp/PgDn scroll the conversation; «↓ Jump to bottom · N new» (or End) takes you back down. New output waits below while you read.
|
|
18
|
+
- `/inbox` and «● 2 waiting»: answer what your Parallel missions are waiting for (approvals, proposed changes, your steps) from parx. Needs the matching getparallel.net release.
|
|
19
|
+
- More air: a blank row between the conversation and the input and between the input and the status row, a blank row between groups (your message, actions, warnings, answers), the question as its own block, never two blank rows in a row. «Careful: leaves the project folder.» instead of the long sentence.
|
|
20
|
+
- Ctrl+Z suspends parx like any terminal program; `fg` brings it back.
|
|
21
|
+
- A question no longer hides the last lines above it: the warning it explains («never to the log.») stays on screen, and the answer is written whole at any width.
|
|
22
|
+
- Credentials: when you ask for something that needs one, parx asks you (it no longer refuses on its own), and a «No» only counts for that task.
|
|
23
|
+
- `/teach` explains every step and ends with a short lesson, also with Swift; `/teach <task>` turns it on and does the task.
|
|
24
|
+
- Typing a whole command and Enter runs it (`/teach`, `/notify`, `/tips`, `/folder`) instead of waiting for a value.
|
|
25
|
+
- `/help` and `/tips` wrap in two clean columns at any width, from 40 columns up.
|
|
26
|
+
- Clearer notes: «3 lines», «2 items», «no matches», «… 8 more lines above»; test output shows pass and fail instead of the runner's filler.
|
|
27
|
+
- `/mission` gives the link to the mission itself once it launches.
|
|
28
|
+
- `/report` copies plain text (no backticks or asterisks) that reads well in an email or WhatsApp.
|
|
29
|
+
- «Running tests» and «Building» only for real test and build commands, not for files like `sum.test.js`.
|
|
30
|
+
- With `--lanes` in a narrow window, each lane keeps its task name instead of a lone «…».
|
|
31
|
+
- Everything you type while parx works (or several lines at once) waits in line, in order; before, only the last one was kept.
|
|
32
|
+
- A permission question that pops up while you are typing your next task is not answered by your Enter: what you typed stays in the box. Esc still answers «No» at once.
|
|
33
|
+
|
|
9
34
|
## 0.4.1 (2026-10-10)
|
|
10
35
|
|
|
11
36
|
- El nombre visible es siempre «parx», nunca «Par» (consejos, avisos, README)
|
package/README.md
CHANGED
|
@@ -78,8 +78,8 @@ when it asks first:
|
|
|
78
78
|
| **Bypass** | no questions anywhere | no questions |
|
|
79
79
|
|
|
80
80
|
- **Bypass** is never the default and never saved: turn it on yourself with `--bypass`, or with
|
|
81
|
-
`/mode bypass
|
|
82
|
-
|
|
81
|
+
`/mode bypass` and a clear warning to accept (Decline comes selected; you pick Accept). The status
|
|
82
|
+
line shows `▲ Bypass` in red while it is on. Shift+Tab (Ask → Auto → Read only) leaves it.
|
|
83
83
|
- **Credentials always ask**, in every mode including Bypass: `.env` files, SSH keys, `~/.aws`,
|
|
84
84
|
`.npmrc`, keychains, tokens. Each time it needs an explicit «Yes, this once» (No comes selected).
|
|
85
85
|
Their paths, commands and contents never go to the log, and commands run with secret-looking
|
|
@@ -107,11 +107,29 @@ Type `/` for the commands; the menu filters as you type, ↑ ↓ move, Tab or En
|
|
|
107
107
|
| `/folder <path>` | let parx work in another folder |
|
|
108
108
|
| `/lanes` | 2 or 3 tasks at once, each in its own lane, all moving together |
|
|
109
109
|
| `/mission [goal]` | turn the work into a Parallel mission that keeps running on Parallel’s servers after you close the terminal; phone notification when it is done |
|
|
110
|
+
| `/inbox` | what your missions are waiting for (approvals, proposed changes, your steps), as buttons; what you pick takes effect like on the web |
|
|
110
111
|
| `/report` | a plain-language summary of what was done, copied to the clipboard for an email or WhatsApp |
|
|
111
112
|
| `/teach` | class mode: explains each step as it codes, like a teacher |
|
|
112
113
|
| `/undo`, `/new`, `/balance`, `/log`, `/help`, `/exit` | the usual |
|
|
113
114
|
| `/notify` on, off | desktop notifications |
|
|
114
115
|
| `/tips` on, off | the welcome tips |
|
|
116
|
+
| `/mouse` on, off | clicks and wheel inside parx (saved; `--no-mouse` for one session) |
|
|
117
|
+
|
|
118
|
+
**Bash mode:** start the input with `!` and it turns amber with a small `bash` label; Enter runs the
|
|
119
|
+
command in your shell, in the project folder, and its output streams into the conversation with the
|
|
120
|
+
exit code. The agent sees the command and its output in the next task. Backspace on a lone `!`
|
|
121
|
+
leaves the mode; Esc or Ctrl+C stops a running command. Read only runs nothing; a command with
|
|
122
|
+
effects outside the project («Careful: leaves the project folder.») asks first (not in Bypass); the
|
|
123
|
+
output of a command that touches credentials stays on your screen and out of the conversation.
|
|
124
|
+
|
|
125
|
+
**Mouse:** click any option, button or menu item (permission questions, Accept/Decline, `/` menu,
|
|
126
|
+
`/inbox`); hovering highlights but never picks. The wheel or PgUp/PgDn scroll the conversation; when
|
|
127
|
+
you are up, a «↓ Jump to bottom» button (with «N new» if output arrived) takes you back, and so does
|
|
128
|
+
End. To select text, drag with Shift (Option in iTerm2, Fn in macOS Terminal), or `/mouse off`. The
|
|
129
|
+
terminal is always given back as it was: on exit, on Ctrl+Z, if parx is killed or crashes.
|
|
130
|
+
|
|
131
|
+
**Waiting for you:** the status row shows «● 2 waiting» when your Parallel missions need an answer;
|
|
132
|
+
click it or type `/inbox`. parx never approves anything on its own: always your click or your key.
|
|
115
133
|
|
|
116
134
|
**Images:** drag a screenshot into the window (or paste its path) and it becomes a chip
|
|
117
135
|
(`[Image · error.png]`); parx sends it with the task to understand the error or the design. PNG,
|
package/dist/agente.js
CHANGED
|
@@ -29,15 +29,17 @@ const PERMISOS = {
|
|
|
29
29
|
read: 'Read only: you can read and search, but you cannot write files or run commands. Explain what you would change instead.',
|
|
30
30
|
bypass: 'Bypass: the user turned off the questions for this session; you can work anywhere on this computer. Be careful and do only what the task needs.',
|
|
31
31
|
};
|
|
32
|
+
export const RECORDATORIO_DE_CLASE = '(Teach mode is on: before each step, explain in 1-3 plain sentences what you will do and why; at the end, add a short lesson for a beginner.)';
|
|
32
33
|
export function instrucciones(raiz, modo, o = {}) {
|
|
33
34
|
const carpetas = o.carpetas?.length ? `\n- Allowed folders besides the project: ${o.carpetas.join(', ')}` : '';
|
|
34
35
|
const clase = o.ensenar
|
|
35
36
|
? `
|
|
36
37
|
|
|
37
38
|
# Teach mode (the user turned it on)
|
|
38
|
-
- Work like a patient teacher. Before
|
|
39
|
+
- Work like a patient teacher. Before EVERY tool call, write 1-3 plain sentences (as text, in the same reply) saying what you are about to do and why. Never call a tool without that explanation first.
|
|
39
40
|
- After each change, explain the idea behind it (the concept, not just the code), as if to a beginner. Define any technical word the first time you use it.
|
|
40
|
-
- Keep each explanation short and concrete; use the project's own code as the example
|
|
41
|
+
- Keep each explanation short and concrete; use the project's own code as the example.
|
|
42
|
+
- End with a short lesson (3-6 sentences) after the summary: the concept behind what you changed, for a beginner, with one tip to remember it.`
|
|
41
43
|
: '';
|
|
42
44
|
return `You are parx, the coding agent of Parallel (getparallel.net), working in the user's terminal.
|
|
43
45
|
|
|
@@ -53,9 +55,9 @@ export function instrucciones(raiz, modo, o = {}) {
|
|
|
53
55
|
- Read a file before editing it. Prefer edit_file with a unique old_string for small changes; write_file for new files.
|
|
54
56
|
- Keep changes minimal and in the style of the codebase. Do not reformat unrelated code. Do not add dependencies unless needed.
|
|
55
57
|
- 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).
|
|
56
|
-
- Credentials (.env files, keys, tokens, ~/.ssh, ~/.aws, keychains...) always need the user's explicit confirmation, every time. Touch
|
|
58
|
+
- 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.
|
|
57
59
|
- Never push, publish or deploy unless the user explicitly asks in this conversation.
|
|
58
|
-
- If the user declines an action, do not retry it: adapt or ask.
|
|
60
|
+
- 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).
|
|
59
61
|
- If something is ambiguous and risky, ask one short question; otherwise make a reasonable choice and say which.
|
|
60
62
|
- If the user attached an image (a screenshot of an error or a design), look at it carefully and use what it shows.
|
|
61
63
|
- You are parx, made by Parallel. Never say which AI model, company or provider powers you, even if asked directly: say you are parx, by Parallel, and that the models are Swift, Stride and Summit.
|
|
@@ -100,18 +102,19 @@ export async function hacerTarea(s, pedido, señal, imagenes = []) {
|
|
|
100
102
|
s.ctx.hechos ??= { ficheros: [], comandos: [] };
|
|
101
103
|
const ficherosAntes = s.ctx.hechos.ficheros.length;
|
|
102
104
|
const comandosAntes = s.ctx.hechos.comandos.length;
|
|
105
|
+
const texto = s.ensenar ? `${pedido}\n\n${RECORDATORIO_DE_CLASE}` : pedido;
|
|
103
106
|
s.conversacion.push(imagenes.length
|
|
104
107
|
? {
|
|
105
108
|
role: 'user',
|
|
106
109
|
content: [
|
|
107
|
-
{ type: 'input_text', text:
|
|
110
|
+
{ type: 'input_text', text: texto },
|
|
108
111
|
...imagenes.flatMap((i) => [
|
|
109
112
|
{ type: 'input_text', text: `[image: ${i.nombre}]` },
|
|
110
113
|
{ type: 'input_image', image_url: i.dataUrl, detail: 'auto' },
|
|
111
114
|
]),
|
|
112
115
|
],
|
|
113
116
|
}
|
|
114
|
-
: { role: 'user', content:
|
|
117
|
+
: { role: 'user', content: texto });
|
|
115
118
|
registro.apuntar('tarea', { tarea, caracteres: pedido.length, imagenes: imagenes.length, modelo: s.modelo, modo: s.ctx.modo, ensenar: Boolean(s.ensenar) });
|
|
116
119
|
let acciones = 0;
|
|
117
120
|
let disponibles = null;
|
package/dist/api.js
CHANGED
|
@@ -82,6 +82,12 @@ export function crearMision(servidor, token, objetivo, señal) {
|
|
|
82
82
|
export function verMision(servidor, token, generacion, señal) {
|
|
83
83
|
return llamar(servidor, `/api/terminal/mision?id=${encodeURIComponent(generacion)}`, { token, señal });
|
|
84
84
|
}
|
|
85
|
+
export function leerBandeja(servidor, token, señal) {
|
|
86
|
+
return llamar(servidor, '/api/terminal/bandeja', { token, señal, plazoMs: 20_000 });
|
|
87
|
+
}
|
|
88
|
+
export function responderBandeja(servidor, token, cuerpo, señal) {
|
|
89
|
+
return llamar(servidor, '/api/terminal/bandeja', { token, cuerpo, señal, plazoMs: 60_000 });
|
|
90
|
+
}
|
|
85
91
|
const CORREO_INTERNO = /@cuentas\.getparallel\.net$/i;
|
|
86
92
|
export function correoVisible(correo) {
|
|
87
93
|
const c = (correo ?? '').trim();
|
package/dist/bash.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { nivelDeComando } from "./seguridad.js";
|
|
2
|
+
import { ejecutarComando, recortar } from "./shell.js";
|
|
3
|
+
import { sinColor } from "./texto.js";
|
|
4
|
+
import { t } from "./textos.js";
|
|
5
|
+
import { bloqueDeFinDeBash, bloqueDeSalidaDeBash, duracion } from "./tui.js";
|
|
6
|
+
export const MAX_LINEAS_A_LA_VISTA = 1000;
|
|
7
|
+
export const PLAZO_BASH_MS = 600_000;
|
|
8
|
+
export function lineasLimpias(texto) {
|
|
9
|
+
return sinColor(texto)
|
|
10
|
+
.split('\n')
|
|
11
|
+
.map((l) => {
|
|
12
|
+
const partes = l.replace(/\r+$/, '').split('\r');
|
|
13
|
+
return partes[partes.length - 1] ?? '';
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export function textoParaElAgente(comando, r) {
|
|
17
|
+
if (!r.compartir)
|
|
18
|
+
return '(I ran a command in my own shell that touches credentials. Its output was kept out of this conversation on purpose.)';
|
|
19
|
+
const cabecera = r.plazoAgotado ? 'Timed out (killed).' : r.interrumpido ? 'Stopped by me before it finished.' : `Exit code: ${r.codigo}`;
|
|
20
|
+
return `(I ran this myself in my shell, in the project folder:\n$ ${comando}\n${cabecera}\n${recortar(r.salida.replace(/\r/g, ''), 12_000).trim() || '(no output)'})`;
|
|
21
|
+
}
|
|
22
|
+
export async function ejecutarBash(comando, ctx, señal, alEmpezar) {
|
|
23
|
+
const p = ctx.pantalla;
|
|
24
|
+
if (ctx.modo === 'read') {
|
|
25
|
+
p.aviso(t('bash.solo_lectura'));
|
|
26
|
+
ctx.registro.apuntar('bloqueado', { herramienta: 'bash', motivo: 'solo_lectura' });
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const { nivel, motivo } = nivelDeComando(comando);
|
|
30
|
+
const credencial = nivel === 'credencial';
|
|
31
|
+
if (nivel === 'riesgo' && ctx.modo !== 'bypass') {
|
|
32
|
+
const r = await p.preguntar(t('pregunta.ejecutar'), { tipo: 'dos', aviso: t('aviso.riesgo', { motivo: motivo ?? '' }) });
|
|
33
|
+
if (r === 'no') {
|
|
34
|
+
ctx.registro.apuntar('rechazado', { herramienta: 'bash', comando: comando.slice(0, 500) });
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
alEmpezar?.();
|
|
39
|
+
p.marcarGrupo('accion');
|
|
40
|
+
let pendiente = '';
|
|
41
|
+
let vistas = 0;
|
|
42
|
+
let ocultas = 0;
|
|
43
|
+
const soltar = (todo) => {
|
|
44
|
+
const partes = pendiente.split('\n');
|
|
45
|
+
pendiente = todo ? '' : (partes.pop() ?? '');
|
|
46
|
+
const lineas = lineasLimpias(partes.join('\n'));
|
|
47
|
+
if (todo && lineas.length && lineas[lineas.length - 1] === '')
|
|
48
|
+
lineas.pop();
|
|
49
|
+
if (!partes.length || !lineas.length)
|
|
50
|
+
return;
|
|
51
|
+
const caben = Math.max(0, MAX_LINEAS_A_LA_VISTA - vistas);
|
|
52
|
+
const ahora = lineas.slice(0, caben);
|
|
53
|
+
ocultas += lineas.length - ahora.length;
|
|
54
|
+
vistas += ahora.length;
|
|
55
|
+
if (ahora.length)
|
|
56
|
+
p.bloque(bloqueDeSalidaDeBash(ahora), 'accion');
|
|
57
|
+
};
|
|
58
|
+
const reloj = setInterval(() => soltar(false), 80);
|
|
59
|
+
let res;
|
|
60
|
+
try {
|
|
61
|
+
res = await ejecutarComando(comando, {
|
|
62
|
+
cwd: ctx.raiz,
|
|
63
|
+
plazoMs: PLAZO_BASH_MS,
|
|
64
|
+
jaula: Boolean(ctx.jaula),
|
|
65
|
+
carpetas: ctx.carpetas ?? [],
|
|
66
|
+
señal,
|
|
67
|
+
alSalir: (trozo) => {
|
|
68
|
+
pendiente += trozo;
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
clearInterval(reloj);
|
|
74
|
+
}
|
|
75
|
+
soltar(true);
|
|
76
|
+
if (ocultas)
|
|
77
|
+
p.bloque(bloqueDeSalidaDeBash([t('bash.mas', { n: ocultas.toLocaleString('en-US') })]), 'accion');
|
|
78
|
+
const bien = res.codigo === 0 && !res.plazoAgotado && !res.interrumpido;
|
|
79
|
+
const tiempo = duracion(res.ms);
|
|
80
|
+
const fin = res.plazoAgotado ? `timeout · ${tiempo}` : res.interrumpido ? `${t('estado.interrumpido').replace(/\.$/, '')} · ${tiempo}` : `exit ${res.codigo ?? '?'} · ${tiempo}`;
|
|
81
|
+
p.bloque(bloqueDeFinDeBash(bien, vistas ? fin : `${t('bash.sin_salida')} · ${fin}`), 'accion');
|
|
82
|
+
if (credencial)
|
|
83
|
+
p.escribir(t('bash.no_compartido'), 'accion');
|
|
84
|
+
ctx.hechos ??= { ficheros: [], comandos: [] };
|
|
85
|
+
if (!credencial)
|
|
86
|
+
ctx.hechos.comandos.push({ comando: comando.slice(0, 200), codigo: res.codigo });
|
|
87
|
+
ctx.registro.apuntar('bash', { comando: credencial ? '[credential]' : comando.slice(0, 500), codigo: res.codigo, ms: res.ms, jaula: res.enJaula });
|
|
88
|
+
return { codigo: res.codigo, salida: res.salida, ms: res.ms, interrumpido: res.interrumpido, plazoAgotado: res.plazoAgotado, compartir: !credencial };
|
|
89
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -6,24 +6,25 @@ 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, fijarCapacidades, sep, signo } from "./marca.js";
|
|
9
|
+
import { c, colorDeModo, fijarCapacidades, sep, signo, 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 { correoVisible, crearMision, ErrorDeParallel, leerCuenta, quienEs, verMision } from "./api.js";
|
|
12
|
+
import { correoVisible, crearMision, ErrorDeParallel, leerBandeja, leerCuenta, quienEs, responderBandeja, verMision } from "./api.js";
|
|
13
13
|
import { comoHasEntrado, credencialesActivas, entrar, formaDeLaOpcion, salir } from "./cuenta.js";
|
|
14
14
|
import { hacerTarea, mensajeDeError, nuevaSesion } from "./agente.js";
|
|
15
15
|
import { deshacerUltimo, permitirCarpeta } from "./herramientas.js";
|
|
16
16
|
import { Registro } from "./registro.js";
|
|
17
17
|
import { Pantalla } from "./ui.js";
|
|
18
|
-
import { acciones, bloqueDeBienvenida, bloqueDeLaPersona, duracion, Superficie } from "./tui.js";
|
|
18
|
+
import { acciones, bloqueDeBienvenida, bloqueDeLaPersona, bloqueDeTabla, duracion, Superficie } from "./tui.js";
|
|
19
19
|
import { CONSEJOS, cambiarConsejos, consejo, elegirConsejos, ordenDeConsejos } from "./consejos.js";
|
|
20
20
|
import { anchoDe, recortarMedio } from "./texto.js";
|
|
21
21
|
import { cargarImagen, ErrorDeImagen, extraerImagenes } from "./imagenes.js";
|
|
22
22
|
import { avisarEnElEscritorio, debeAvisar } from "./notificar.js";
|
|
23
|
-
import { copiarAlPortapapeles, escribirInforme } from "./informe.js";
|
|
23
|
+
import { copiarAlPortapapeles, escribirInforme, textoParaPegar } from "./informe.js";
|
|
24
24
|
import { objetivoDeMision } from "./mision.js";
|
|
25
25
|
import { ejecutarCarriles, MAX_CARRILES } from "./carriles.js";
|
|
26
26
|
import { spawnSync } from 'node:child_process';
|
|
27
|
+
import { ejecutarBash, textoParaElAgente } from "./bash.js";
|
|
27
28
|
import { comprobarDeFondo, esMasNueva, ordenDeActualizar, rutaDelCli, ultimaPublicada, versionNuevaConocida } from "./actualizar.js";
|
|
28
29
|
function opciones() {
|
|
29
30
|
return parseArgs({
|
|
@@ -40,6 +41,8 @@ function opciones() {
|
|
|
40
41
|
lanes: { type: 'boolean' },
|
|
41
42
|
dir: { type: 'string', short: 'C' },
|
|
42
43
|
ascii: { type: 'boolean' },
|
|
44
|
+
mouse: { type: 'boolean' },
|
|
45
|
+
'no-mouse': { type: 'boolean' },
|
|
43
46
|
version: { type: 'boolean', short: 'v' },
|
|
44
47
|
help: { type: 'boolean', short: 'h' },
|
|
45
48
|
browser: { type: 'boolean' },
|
|
@@ -82,10 +85,12 @@ export function modoDe(texto) {
|
|
|
82
85
|
return tabla[v] ?? null;
|
|
83
86
|
}
|
|
84
87
|
export const SIGUIENTE_MODO = { ask: 'auto', auto: 'read', read: 'ask', bypass: 'ask' };
|
|
88
|
+
export function modosEnColor() {
|
|
89
|
+
const nombres = ['read', 'ask', 'auto', 'bypass'].map((m) => colorDeModo(m)(nombreDeModo(m)));
|
|
90
|
+
return `${nombres.slice(0, 3).join(', ')} or ${nombres[3]}`;
|
|
91
|
+
}
|
|
85
92
|
function ayudaDeComandos() {
|
|
86
|
-
|
|
87
|
-
const filas = COMANDOS.map(([k, d]) => ` ${c.marca(`/${k}`.padEnd(ancho))}${d}`);
|
|
88
|
-
return [...filas, '', c.gris(t('tui.atajos'))].join('\n');
|
|
93
|
+
return bloqueDeTabla([['!', t('tip.bash')], ...COMANDOS.map(([k, d]) => [`/${k}`, k === 'mode' ? modosEnColor() : d])], { pie: `${t('tui.atajos')}\n${t('tui.atajos_raton')}` });
|
|
89
94
|
}
|
|
90
95
|
async function cuentaOError(cred, pantalla) {
|
|
91
96
|
try {
|
|
@@ -256,6 +261,8 @@ async function principal() {
|
|
|
256
261
|
const saldoCorto = () => (disponibles == null ? '' : disponibles.toLocaleString('en-US'));
|
|
257
262
|
const quien = quienEs(cuenta);
|
|
258
263
|
let avisos = ajustes.avisos !== false;
|
|
264
|
+
const conRaton = !v['no-mouse'] && v.mouse !== false && (Boolean(v.mouse) || ajustes.raton !== false);
|
|
265
|
+
let esperandoEnBandeja = null;
|
|
259
266
|
let control = new AbortController();
|
|
260
267
|
let ocupado = false;
|
|
261
268
|
let superficie = null;
|
|
@@ -390,7 +397,9 @@ async function principal() {
|
|
|
390
397
|
saldo: saldo(),
|
|
391
398
|
saldoCorto: saldoCorto(),
|
|
392
399
|
peligro: sesion.ctx.modo === 'bypass',
|
|
400
|
+
clave: sesion.ctx.modo,
|
|
393
401
|
extra: sesion.ensenar ? 'Teach' : undefined,
|
|
402
|
+
esperando: esperandoEnBandeja ?? 0,
|
|
394
403
|
});
|
|
395
404
|
if (interactiva) {
|
|
396
405
|
superficie = new Superficie({
|
|
@@ -404,6 +413,8 @@ async function principal() {
|
|
|
404
413
|
guardarAjustes({ modo: sesion.ctx.modo });
|
|
405
414
|
},
|
|
406
415
|
alSalir: adios,
|
|
416
|
+
raton: conRaton,
|
|
417
|
+
alBandeja: () => superficie?.escribirYMandar('/inbox'),
|
|
407
418
|
alPreguntar: (texto) => {
|
|
408
419
|
if (avisos && superficie?.enfocada === false)
|
|
409
420
|
avisarEnElEscritorio({ titulo: 'parx', texto: t('aviso.te_necesita', { pregunta: texto }) });
|
|
@@ -411,6 +422,17 @@ async function principal() {
|
|
|
411
422
|
});
|
|
412
423
|
const s = superficie;
|
|
413
424
|
process.on('exit', () => s.restaurarDeUrgencia());
|
|
425
|
+
for (const [señal, codigo] of [['SIGTERM', 143], ['SIGHUP', 129]]) {
|
|
426
|
+
process.on(señal, () => {
|
|
427
|
+
try {
|
|
428
|
+
s.terminar();
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
s.restaurarDeUrgencia();
|
|
432
|
+
}
|
|
433
|
+
process.exit(codigo);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
414
436
|
pantalla.fijarSuperficie(superficie);
|
|
415
437
|
const elegidos = elegirConsejos(leerAjustes().consejos);
|
|
416
438
|
if (elegidos.ids.length)
|
|
@@ -428,6 +450,25 @@ async function principal() {
|
|
|
428
450
|
}), conCarriles || quiereBypass ? [] : elegidos.ids);
|
|
429
451
|
if (quiereBypass)
|
|
430
452
|
await confirmarBypass();
|
|
453
|
+
const mirarBandeja = async () => {
|
|
454
|
+
if (ocupado)
|
|
455
|
+
return;
|
|
456
|
+
try {
|
|
457
|
+
const r = await leerBandeja(cred.servidor, cred.token);
|
|
458
|
+
const n = r.elementos.length;
|
|
459
|
+
if (n !== esperandoEnBandeja) {
|
|
460
|
+
esperandoEnBandeja = n;
|
|
461
|
+
superficie?.pintar();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch (e) {
|
|
465
|
+
if (e instanceof ErrorDeParallel && (e.codigo === 'sin_acceso' || e.codigo === 'sesion'))
|
|
466
|
+
clearInterval(relojBandeja);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
const relojBandeja = setInterval(() => void mirarBandeja(), 90_000);
|
|
470
|
+
relojBandeja.unref();
|
|
471
|
+
void mirarBandeja();
|
|
431
472
|
}
|
|
432
473
|
const leerLinea = async (ejemplo = null) => {
|
|
433
474
|
if (superficie)
|
|
@@ -457,6 +498,131 @@ async function principal() {
|
|
|
457
498
|
}
|
|
458
499
|
return tareas;
|
|
459
500
|
}
|
|
501
|
+
const pedirTexto = async (e, opcion) => {
|
|
502
|
+
const obligatorio = opcion === 'cambios' && e.texto_libre;
|
|
503
|
+
const opcional = e.tipo === 'paso' && opcion === 'hecho' && e.texto_libre;
|
|
504
|
+
if (!obligatorio && !opcional)
|
|
505
|
+
return undefined;
|
|
506
|
+
const l = ((await leerLinea(t(obligatorio ? 'bandeja.escribe' : 'bandeja.nota'))) ?? '').trim();
|
|
507
|
+
if (!l && obligatorio)
|
|
508
|
+
return null;
|
|
509
|
+
return l || undefined;
|
|
510
|
+
};
|
|
511
|
+
const bandeja = async () => {
|
|
512
|
+
const leer = async () => {
|
|
513
|
+
ocupado = true;
|
|
514
|
+
control = new AbortController();
|
|
515
|
+
superficie?.trabajar(() => control.abort());
|
|
516
|
+
pantalla.espera(t('bandeja.mirando'), 'ruta', true);
|
|
517
|
+
try {
|
|
518
|
+
const r = await leerBandeja(cred.servidor, cred.token, control.signal);
|
|
519
|
+
esperandoEnBandeja = r.elementos.length;
|
|
520
|
+
return r.elementos;
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
if (!(e instanceof ErrorDeParallel))
|
|
524
|
+
throw e;
|
|
525
|
+
if (e.codigo === 'sin_acceso')
|
|
526
|
+
pantalla.error(t('bandeja.no_disponible'));
|
|
527
|
+
else if (e.codigo !== 'interrumpido')
|
|
528
|
+
pantalla.error(mensajeDeError(e));
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
finally {
|
|
532
|
+
pantalla.pararEspera();
|
|
533
|
+
ocupado = false;
|
|
534
|
+
superficie?.descansar();
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
let lista = await leer();
|
|
538
|
+
if (!lista)
|
|
539
|
+
return;
|
|
540
|
+
if (!lista.length) {
|
|
541
|
+
pantalla.escribir(t('bandeja.nada'));
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (!superficie) {
|
|
545
|
+
for (const e of lista)
|
|
546
|
+
pantalla.escribir(`${e.titulo}${c.gris(`${sep()}${e.mision}`)} ${c.gris(e.url)}`);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
let elegida = 0;
|
|
550
|
+
for (;;) {
|
|
551
|
+
const i = await superficie.elegir(t('bandeja.titulo'), lista.map((e) => ({ etiqueta: e.titulo, nota: e.mision })), { vertical: true, elegida });
|
|
552
|
+
if (i === null)
|
|
553
|
+
break;
|
|
554
|
+
elegida = i;
|
|
555
|
+
const e = lista[i];
|
|
556
|
+
if (!e.opciones.length) {
|
|
557
|
+
pantalla.escribir(t('bandeja.abrir', { url: c.marca(e.url) }));
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const recomendada = Math.max(0, e.opciones.findIndex((o) => o.recomendada));
|
|
561
|
+
const j = await superficie.elegir(e.titulo, [...e.opciones.map((o) => ({ etiqueta: o.etiqueta, recomendada: o.recomendada, peligro: o.peligro })), { etiqueta: t('bandeja.volver') }], { detalle: [e.mision, e.detalle].filter(Boolean).join(sep()), elegida: recomendada });
|
|
562
|
+
if (j === null || j >= e.opciones.length)
|
|
563
|
+
continue;
|
|
564
|
+
const opcion = e.opciones[j];
|
|
565
|
+
const texto = await pedirTexto(e, opcion.id);
|
|
566
|
+
if (texto === null) {
|
|
567
|
+
pantalla.escribir(c.gris(t('bandeja.cancelado')));
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
ocupado = true;
|
|
571
|
+
control = new AbortController();
|
|
572
|
+
superficie.trabajar(() => control.abort());
|
|
573
|
+
pantalla.espera(t('bandeja.mirando'), 'ruta', true);
|
|
574
|
+
try {
|
|
575
|
+
const r = await responderBandeja(cred.servidor, cred.token, { id: e.id, opcion: opcion.id, ...(texto ? { texto } : {}) }, control.signal);
|
|
576
|
+
registro.apuntar('bandeja', { tipo: e.tipo, opcion: opcion.id.replace(/:.*/, '') });
|
|
577
|
+
const hecho = { approved: 'bandeja.hecho_aprobado', rejected: 'bandeja.hecho_rechazado', revising: 'bandeja.hecho_cambios', done: 'bandeja.hecho_paso', skipped: 'bandeja.hecho_saltado' };
|
|
578
|
+
pantalla.escribir(`${c.ok(SIMBOLOS.hecho)} ${t(hecho[r.estado] ?? 'bandeja.hecho_aprobado')}${c.gris(`${sep()}${e.mision}`)}`);
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
if (!(err instanceof ErrorDeParallel))
|
|
582
|
+
throw err;
|
|
583
|
+
if (err.codigo === 'servidor' && err.detalle.startsWith('409'))
|
|
584
|
+
pantalla.aviso(t('bandeja.ya'));
|
|
585
|
+
else if (err.codigo === 'sin_acceso')
|
|
586
|
+
pantalla.aviso(t('bandeja.no_existe'));
|
|
587
|
+
else if (err.codigo === 'servidor' && err.detalle.startsWith('429'))
|
|
588
|
+
pantalla.aviso(t('bandeja.despacio'));
|
|
589
|
+
else if (err.codigo !== 'interrumpido')
|
|
590
|
+
pantalla.error(mensajeDeError(err));
|
|
591
|
+
}
|
|
592
|
+
finally {
|
|
593
|
+
pantalla.pararEspera();
|
|
594
|
+
ocupado = false;
|
|
595
|
+
superficie.descansar();
|
|
596
|
+
}
|
|
597
|
+
lista = await leer();
|
|
598
|
+
superficie.pintar();
|
|
599
|
+
if (!lista)
|
|
600
|
+
return;
|
|
601
|
+
if (!lista.length) {
|
|
602
|
+
pantalla.escribir(t('bandeja.todo'));
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
elegida = Math.min(elegida, lista.length - 1);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
const bash = async (comando) => {
|
|
609
|
+
if (!comando)
|
|
610
|
+
return;
|
|
611
|
+
control = new AbortController();
|
|
612
|
+
try {
|
|
613
|
+
const r = await ejecutarBash(comando, sesion.ctx, control.signal, () => {
|
|
614
|
+
ocupado = true;
|
|
615
|
+
superficie?.trabajar(() => control.abort());
|
|
616
|
+
superficie?.textoDeTrabajo(t('bash.corriendo', { comando: comando.length > 28 ? `${comando.slice(0, 27)}…` : comando }), 'tren', true);
|
|
617
|
+
});
|
|
618
|
+
if (r)
|
|
619
|
+
sesion.conversacion.push({ role: 'user', content: textoParaElAgente(comando, r) });
|
|
620
|
+
}
|
|
621
|
+
finally {
|
|
622
|
+
ocupado = false;
|
|
623
|
+
superficie?.descansar();
|
|
624
|
+
}
|
|
625
|
+
};
|
|
460
626
|
for (;;) {
|
|
461
627
|
const r = await leerLinea();
|
|
462
628
|
if (r === null)
|
|
@@ -465,6 +631,10 @@ async function principal() {
|
|
|
465
631
|
ctrlC = 0;
|
|
466
632
|
if (!linea)
|
|
467
633
|
continue;
|
|
634
|
+
if (linea.startsWith('!')) {
|
|
635
|
+
await bash(linea.slice(1).trim());
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
468
638
|
if (linea.startsWith('/')) {
|
|
469
639
|
const [nombre = '', ...args] = linea.slice(1).split(/\s+/);
|
|
470
640
|
const cmd = ALIAS_COMANDO[nombre.toLowerCase()];
|
|
@@ -479,7 +649,7 @@ async function principal() {
|
|
|
479
649
|
rl?.close();
|
|
480
650
|
return 0;
|
|
481
651
|
case 'help':
|
|
482
|
-
pantalla.
|
|
652
|
+
pantalla.bloque(ayudaDeComandos());
|
|
483
653
|
break;
|
|
484
654
|
case 'mode': {
|
|
485
655
|
const m = modoDe(arg);
|
|
@@ -497,7 +667,7 @@ async function principal() {
|
|
|
497
667
|
guardarAjustes({ modo: m });
|
|
498
668
|
registro.apuntar('modo', { modo: m });
|
|
499
669
|
}
|
|
500
|
-
pantalla.escribir(t('modo.ahora', { modo: nombreDeModo(sesion.ctx.modo), desc: t(`modo.${sesion.ctx.modo}.desc`) }));
|
|
670
|
+
pantalla.escribir(t('modo.ahora', { modo: colorDeModo(sesion.ctx.modo)(c.negrita(nombreDeModo(sesion.ctx.modo))), desc: t(`modo.${sesion.ctx.modo}.desc`) }));
|
|
501
671
|
superficie?.pintar();
|
|
502
672
|
break;
|
|
503
673
|
}
|
|
@@ -534,10 +704,18 @@ async function principal() {
|
|
|
534
704
|
break;
|
|
535
705
|
}
|
|
536
706
|
case 'teach': {
|
|
537
|
-
const
|
|
707
|
+
const tareaConClase = !ALIAS_SI_NO[arg] && resto ? resto : null;
|
|
708
|
+
const si = tareaConClase ? 'si' : (ALIAS_SI_NO[arg] ?? (sesion.ensenar ? 'no' : 'si'));
|
|
709
|
+
const cambia = sesion.ensenar !== (si === 'si');
|
|
538
710
|
sesion.ensenar = si === 'si';
|
|
539
|
-
|
|
711
|
+
if (cambia || !tareaConClase)
|
|
712
|
+
pantalla.escribir(sesion.ensenar ? t('repl.ensenar_on') : t('repl.ensenar_off'));
|
|
540
713
|
superficie?.pintar();
|
|
714
|
+
if (tareaConClase && !(await hacer(tareaConClase))) {
|
|
715
|
+
superficie?.terminar(despedida());
|
|
716
|
+
rl?.close();
|
|
717
|
+
return 1;
|
|
718
|
+
}
|
|
541
719
|
break;
|
|
542
720
|
}
|
|
543
721
|
case 'report': {
|
|
@@ -558,7 +736,7 @@ async function principal() {
|
|
|
558
736
|
pantalla.escribir();
|
|
559
737
|
pantalla.texto(`**${t('informe.titulo')}**\n\n${inf.texto}`);
|
|
560
738
|
pantalla.escribir();
|
|
561
|
-
pantalla.cierre(`${copiarAlPortapapeles(inf.texto) ? t('informe.copiado') : t('informe.titulo')}${c.gris(sep())}${c.gris(acciones(inf.acciones))}`);
|
|
739
|
+
pantalla.cierre(`${copiarAlPortapapeles(textoParaPegar(inf.texto)) ? t('informe.copiado') : t('informe.titulo')}${c.gris(sep())}${c.gris(acciones(inf.acciones))}`);
|
|
562
740
|
}
|
|
563
741
|
catch (e) {
|
|
564
742
|
pantalla.pararEspera();
|
|
@@ -602,8 +780,11 @@ async function principal() {
|
|
|
602
780
|
break;
|
|
603
781
|
}
|
|
604
782
|
}
|
|
605
|
-
if (vista.estado === 'launched')
|
|
783
|
+
if (vista.estado === 'launched') {
|
|
606
784
|
pantalla.cierre(t('mision.lanzada'));
|
|
785
|
+
if (vista.mision && vista.url && vista.url !== m.url)
|
|
786
|
+
pantalla.escribir(t('mision.aqui', { url: c.marca(vista.url) }));
|
|
787
|
+
}
|
|
607
788
|
else if (vista.estado === 'failed')
|
|
608
789
|
pantalla.error(t('mision.no_disponible'));
|
|
609
790
|
}
|
|
@@ -626,6 +807,17 @@ async function principal() {
|
|
|
626
807
|
}
|
|
627
808
|
break;
|
|
628
809
|
}
|
|
810
|
+
case 'inbox':
|
|
811
|
+
await bandeja();
|
|
812
|
+
superficie?.pintar();
|
|
813
|
+
break;
|
|
814
|
+
case 'mouse': {
|
|
815
|
+
const si = ALIAS_SI_NO[arg] ?? (superficie?.conRaton ? 'no' : 'si');
|
|
816
|
+
guardarAjustes({ raton: si === 'si' });
|
|
817
|
+
superficie?.fijarRaton(si === 'si');
|
|
818
|
+
pantalla.escribir(si === 'si' ? t('raton.on') : t('raton.off'));
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
629
821
|
case 'new':
|
|
630
822
|
sesion.conversacion = [];
|
|
631
823
|
pantalla.escribir(t('repl.nueva'));
|
|
@@ -666,11 +858,9 @@ async function principal() {
|
|
|
666
858
|
}
|
|
667
859
|
else {
|
|
668
860
|
const todos = CONSEJOS.map((id) => consejo(id));
|
|
669
|
-
const ancho = Math.max(...todos.map((k) => anchoDe(k.tecla))) + 3;
|
|
670
|
-
const filas = todos.map((k) => ` ${c.marca(k.tecla + ' '.repeat(Math.max(0, ancho - anchoDe(k.tecla))))}${k.texto}`);
|
|
671
861
|
const apagados = leerAjustes().consejos?.apagados;
|
|
672
862
|
const pie = apagados ? t('consejos.apagados', { orden: ordenDeConsejos(true) }) : t('consejos.quitar', { orden: ordenDeConsejos(false) });
|
|
673
|
-
pantalla.
|
|
863
|
+
pantalla.bloque(bloqueDeTabla(todos.map((k) => [k.tecla, k.texto]), { titulo: t('consejos.titulo'), pie, sangria: 2 }));
|
|
674
864
|
}
|
|
675
865
|
break;
|
|
676
866
|
}
|
package/dist/consejos.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { capacidades } from "./marca.js";
|
|
2
2
|
import { t } from "./textos.js";
|
|
3
|
-
export const CONSEJOS = ['menu', 'modo', 'una', 'imagen', 'historial', 'pegar', 'deshacer', 'modelo', 'carriles', 'parar', 'cola', 'informe', 'mision', 'ensenar', 'saldo', 'cuenta', 'agents'];
|
|
3
|
+
export const CONSEJOS = ['menu', 'modo', 'una', 'bash', 'imagen', 'historial', 'pegar', 'deshacer', 'modelo', 'carriles', 'parar', 'cola', 'bandeja', 'raton', 'informe', 'mision', 'ensenar', 'saldo', 'cuenta', 'agents'];
|
|
4
4
|
export function consejo(id) {
|
|
5
5
|
const u = capacidades().unicode;
|
|
6
6
|
const tecla = {
|
|
@@ -21,6 +21,9 @@ export function consejo(id) {
|
|
|
21
21
|
saldo: '/balance',
|
|
22
22
|
cuenta: 'parx account',
|
|
23
23
|
agents: 'AGENTS.md',
|
|
24
|
+
bash: '!',
|
|
25
|
+
bandeja: '/inbox',
|
|
26
|
+
raton: 'Click',
|
|
24
27
|
};
|
|
25
28
|
return { tecla: tecla[id], texto: t(`tip.${id}`) };
|
|
26
29
|
}
|