chocolatito-code 1.0.0 → 1.2.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/README.md +155 -0
- package/dist/agent/compaction.d.ts +65 -0
- package/dist/agent/compaction.js +134 -0
- package/dist/agent/context.d.ts +3 -0
- package/dist/agent/context.js +35 -0
- package/dist/agent/loop.d.ts +91 -2
- package/dist/agent/loop.js +642 -91
- package/dist/agent/loopDetector.d.ts +57 -0
- package/dist/agent/loopDetector.js +0 -0
- package/dist/agent/mentions.d.ts +13 -0
- package/dist/agent/mentions.js +49 -0
- package/dist/agent/repoMap.d.ts +117 -0
- package/dist/agent/repoMap.js +560 -0
- package/dist/agent/result.d.ts +64 -0
- package/dist/agent/result.js +48 -0
- package/dist/agent/retry.d.ts +35 -0
- package/dist/agent/retry.js +123 -4
- package/dist/agent/salidaDelAgente.d.ts +43 -0
- package/dist/agent/salidaDelAgente.js +53 -0
- package/dist/agent/subagent.js +62 -15
- package/dist/agent/syntaxValidator.js +288 -52
- package/dist/agent/toolGate.d.ts +58 -0
- package/dist/agent/toolGate.js +111 -0
- package/dist/agent/tracker.js +4 -7
- package/dist/agent/undoManager.d.ts +44 -11
- package/dist/agent/undoManager.js +95 -24
- package/dist/agent/usageMeter.d.ts +56 -0
- package/dist/agent/usageMeter.js +51 -0
- package/dist/agent/verifier.d.ts +49 -0
- package/dist/agent/verifier.js +158 -0
- package/dist/config/constants.js +14 -0
- package/dist/config/engine.d.ts +19 -0
- package/dist/config/engine.js +35 -3
- package/dist/config/license.d.ts +5 -0
- package/dist/config/license.js +107 -9
- package/dist/config/limits.d.ts +28 -0
- package/dist/config/limits.js +51 -0
- package/dist/config/nodeVersion.d.ts +13 -0
- package/dist/config/nodeVersion.js +26 -0
- package/dist/config/permissions.d.ts +76 -1
- package/dist/config/permissions.js +379 -23
- package/dist/config/quota.js +24 -1
- package/dist/config/updater.d.ts +68 -0
- package/dist/config/updater.js +215 -0
- package/dist/hooks/manager.js +159 -14
- package/dist/index.js +240 -37
- package/dist/mcp/client.d.ts +50 -0
- package/dist/mcp/client.js +155 -19
- package/dist/mcp/ide.d.ts +76 -0
- package/dist/mcp/ide.js +160 -0
- package/dist/mcp/manager.js +9 -0
- package/dist/prompts/systemPrompt.js +12 -1
- package/dist/sessions/manager.d.ts +12 -0
- package/dist/sessions/manager.js +33 -5
- package/dist/sessions/resume.d.ts +36 -0
- package/dist/sessions/resume.js +43 -0
- package/dist/skills/manager.d.ts +1 -0
- package/dist/skills/manager.js +9 -0
- package/dist/tools/binary.d.ts +46 -0
- package/dist/tools/binary.js +100 -0
- package/dist/tools/browserCdp.js +13 -1
- package/dist/tools/browserExtension.d.ts +5 -0
- package/dist/tools/browserExtension.js +237 -22
- package/dist/tools/browserSession.d.ts +23 -0
- package/dist/tools/browserSession.js +28 -0
- package/dist/tools/computerUse.js +33 -1
- package/dist/tools/createImage.d.ts +11 -0
- package/dist/tools/createImage.js +13 -1
- package/dist/tools/definitions.js +17 -4
- package/dist/tools/editDiagnosis.d.ts +54 -0
- package/dist/tools/editDiagnosis.js +142 -0
- package/dist/tools/editFile.d.ts +1 -0
- package/dist/tools/editFile.js +118 -62
- package/dist/tools/findFiles.d.ts +21 -0
- package/dist/tools/findFiles.js +75 -6
- package/dist/tools/getSystemInfo.js +15 -7
- package/dist/tools/gitTools.d.ts +24 -0
- package/dist/tools/gitTools.js +87 -26
- package/dist/tools/grepSearch.js +111 -23
- package/dist/tools/moveCopyFile.d.ts +18 -1
- package/dist/tools/moveCopyFile.js +74 -4
- package/dist/tools/patchCascade.d.ts +57 -0
- package/dist/tools/patchCascade.js +338 -0
- package/dist/tools/runCommand.js +213 -25
- package/dist/tools/runner.js +58 -5
- package/dist/tools/toolDefsComputer.js +12 -2
- package/dist/tools/truncator.d.ts +27 -1
- package/dist/tools/truncator.js +117 -11
- package/dist/tools/viewFile.js +15 -11
- package/dist/tools/visionBridge.js +7 -0
- package/dist/tools/webSearch.d.ts +21 -0
- package/dist/tools/webSearch.js +125 -14
- package/dist/tools/writeFile.d.ts +16 -1
- package/dist/tools/writeFile.js +40 -2
- package/dist/ui/interrupt.d.ts +4 -1
- package/dist/ui/interrupt.js +59 -7
- package/dist/ui/keyboardGuard.d.ts +76 -0
- package/dist/ui/keyboardGuard.js +94 -0
- package/dist/ui/loopPrompt.d.ts +17 -0
- package/dist/ui/loopPrompt.js +59 -0
- package/dist/ui/pegados.d.ts +50 -0
- package/dist/ui/pegados.js +83 -0
- package/dist/ui/permissionPrompt.js +84 -2
- package/dist/ui/prompt.d.ts +40 -0
- package/dist/ui/prompt.js +436 -131
- package/dist/ui/reasoningStream.d.ts +8 -0
- package/dist/ui/reasoningStream.js +99 -7
- package/dist/ui/salida.d.ts +10 -0
- package/dist/ui/salida.js +50 -0
- package/extension/README.md +95 -74
- package/extension/background.js +772 -181
- package/extension/content.js +527 -46
- package/extension/fuentes/Chocolatito-Marca.ttf +0 -0
- package/extension/iconos/128.png +0 -0
- package/extension/iconos/16.png +0 -0
- package/extension/iconos/32.png +0 -0
- package/extension/iconos/48.png +0 -0
- package/extension/iconos/logotipo.png +0 -0
- package/extension/manifest.json +28 -4
- package/extension/popup.html +16 -6
- package/extension/popup.js +28 -12
- package/package.json +3 -2
package/dist/ui/interrupt.js
CHANGED
|
@@ -3,6 +3,7 @@ import process from "node:process";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { cycleMode, modeAnnouncement } from "./modes.js";
|
|
5
5
|
import { pushTyped, backspaceTyped, commitTyped } from "./typeAhead.js";
|
|
6
|
+
import { dibujar } from "./salida.js";
|
|
6
7
|
/**
|
|
7
8
|
* Interrupcion con Esc mientras el agente trabaja.
|
|
8
9
|
*
|
|
@@ -32,6 +33,42 @@ import { pushTyped, backspaceTyped, commitTyped } from "./typeAhead.js";
|
|
|
32
33
|
*/
|
|
33
34
|
const activos = new Set();
|
|
34
35
|
/** Suelta el teclado de cualquier vigilante que lo tenga. Lo llama el prompt. */
|
|
36
|
+
/**
|
|
37
|
+
* Como estaba el terminal ANTES de que ningun vigilante lo tocara.
|
|
38
|
+
*
|
|
39
|
+
* Cada vigilante se guardaba su propio `wasRaw` leyendo el estado en el momento
|
|
40
|
+
* de arrancar. Con un vigilante suelto -y el bucle tiene veinte salidas, basta
|
|
41
|
+
* que una se escape- el siguiente leia el modo crudo que habia dejado el
|
|
42
|
+
* anterior y se lo apuntaba como "lo de antes". Al soltarlos todos, cada uno
|
|
43
|
+
* restauraba su version y ganaba el ultimo: el terminal acababa en un estado que
|
|
44
|
+
* no era el de nadie.
|
|
45
|
+
*
|
|
46
|
+
* Con una sola marca, tomada por el primero y devuelta por el ultimo, el numero
|
|
47
|
+
* de vigilantes deja de importar.
|
|
48
|
+
*/
|
|
49
|
+
let modoCrudoOriginal = null;
|
|
50
|
+
/**
|
|
51
|
+
* Hay un prompt dibujado ahora mismo.
|
|
52
|
+
*
|
|
53
|
+
* Un vigilante puede pararse TARDE: el bucle vuelve, el prompt se dibuja y coge
|
|
54
|
+
* el modo crudo, y solo entonces una promesa rezagada del turno anterior llama a
|
|
55
|
+
* su `stop()`. Ese stop restauraba "como estaba antes del turno" —modo cocido—
|
|
56
|
+
* y se lo quitaba al prompt recien abierto. Desde fuera se veia como el fallo
|
|
57
|
+
* que se reporto tres veces: tecleas y tu texto sale en la linea de ayuda, no
|
|
58
|
+
* en el recuadro, porque quien hace el eco es el terminal.
|
|
59
|
+
*
|
|
60
|
+
* Con esta marca, el ultimo vigilante en irse solo devuelve el terminal si no
|
|
61
|
+
* hay nadie esperandolo.
|
|
62
|
+
*/
|
|
63
|
+
let promptConElTeclado = false;
|
|
64
|
+
/** Lo llama el prompt al abrirse, despues de coger el modo crudo. */
|
|
65
|
+
export function tomarTecladoParaPrompt() {
|
|
66
|
+
promptConElTeclado = true;
|
|
67
|
+
}
|
|
68
|
+
/** Lo llama el prompt al cerrarse, para que el siguiente turno pueda restaurar. */
|
|
69
|
+
export function devolverTecladoDelPrompt() {
|
|
70
|
+
promptConElTeclado = false;
|
|
71
|
+
}
|
|
35
72
|
export function releaseKeyboard() {
|
|
36
73
|
for (const w of Array.from(activos)) {
|
|
37
74
|
w.stop();
|
|
@@ -62,7 +99,11 @@ export class InterruptWatcher {
|
|
|
62
99
|
return this.controller.signal;
|
|
63
100
|
}
|
|
64
101
|
readline.emitKeypressEvents(process.stdin);
|
|
65
|
-
|
|
102
|
+
// Solo el primero apunta como estaba: los que vengan detras verian el modo
|
|
103
|
+
// crudo que dejo este, no el del usuario.
|
|
104
|
+
if (activos.size === 0)
|
|
105
|
+
modoCrudoOriginal = process.stdin.isRaw || false;
|
|
106
|
+
this.wasRaw = modoCrudoOriginal ?? false;
|
|
66
107
|
process.stdin.setRawMode(true);
|
|
67
108
|
process.stdin.resume();
|
|
68
109
|
this.onKey = (str, key) => {
|
|
@@ -72,7 +113,7 @@ export class InterruptWatcher {
|
|
|
72
113
|
Boolean(key && key.sequence === "\x1b[Z");
|
|
73
114
|
if (isShiftTab) {
|
|
74
115
|
cycleMode();
|
|
75
|
-
|
|
116
|
+
dibujar(`\r\x1b[2K${modeAnnouncement()}\n`);
|
|
76
117
|
return;
|
|
77
118
|
}
|
|
78
119
|
// Ignorar secuencias de escape multi-byte (flechas, inicio, fin, etc.) para que no aborten
|
|
@@ -90,7 +131,7 @@ export class InterruptWatcher {
|
|
|
90
131
|
const texto = commitTyped();
|
|
91
132
|
if (texto) {
|
|
92
133
|
const corto = texto.length > 70 ? `${texto.slice(0, 67)}...` : texto;
|
|
93
|
-
|
|
134
|
+
dibujar(`\r\x1b[2K${chalk.gray("↳ en cola:")} ${chalk.white(corto)}\n`);
|
|
94
135
|
}
|
|
95
136
|
return;
|
|
96
137
|
}
|
|
@@ -137,12 +178,23 @@ export class InterruptWatcher {
|
|
|
137
178
|
process.stdin.removeListener("keypress", this.onKey);
|
|
138
179
|
this.onKey = null;
|
|
139
180
|
}
|
|
140
|
-
|
|
141
|
-
// Se devuelve el terminal como estaba, o el prompt siguiente queda roto.
|
|
142
|
-
process.stdin.setRawMode(this.wasRaw);
|
|
143
|
-
}
|
|
181
|
+
const escuchaba = this.listening;
|
|
144
182
|
this.listening = false;
|
|
145
183
|
activos.delete(this);
|
|
184
|
+
// El terminal se devuelve cuando se va el ULTIMO, no cada vez que se va
|
|
185
|
+
// uno: si quedan vigilantes vivos, restaurar aqui les quita el teclado a
|
|
186
|
+
// ellos a mitad de turno.
|
|
187
|
+
if (escuchaba && activos.size === 0) {
|
|
188
|
+
if (promptConElTeclado) {
|
|
189
|
+
// Llegamos tarde: el prompt ya esta abierto y el modo crudo es suyo.
|
|
190
|
+
// Solo se olvida la marca, que ya no describe nada.
|
|
191
|
+
modoCrudoOriginal = null;
|
|
192
|
+
}
|
|
193
|
+
else if (process.stdin.setRawMode) {
|
|
194
|
+
process.stdin.setRawMode(modoCrudoOriginal ?? this.wasRaw);
|
|
195
|
+
modoCrudoOriginal = null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
146
198
|
}
|
|
147
199
|
}
|
|
148
200
|
/** Un error de aborto no es un fallo: es el usuario diciendo "para". */
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El prompt no suelta el teclado mientras esta abierto.
|
|
3
|
+
*
|
|
4
|
+
* EL FALLO QUE CIERRA ESTE ARCHIVO
|
|
5
|
+
*
|
|
6
|
+
* Reportado tres veces desde el uso real, siempre igual: "escribi 2 prompts y
|
|
7
|
+
* al siguiente ya no me deja escribir nada, teclee lo que teclee". La sesion
|
|
8
|
+
* pegada por el usuario tenia la pista buena, escondida en la ultima linea:
|
|
9
|
+
*
|
|
10
|
+
* ? /help para atajos · esc cancelar ⇥ modo normal · Espectro 1.0 (Agil) estaba haciendo y denuevo...
|
|
11
|
+
*
|
|
12
|
+
* Lo que escribia SI aparecia, pero pegado al final de la linea de ayuda en vez
|
|
13
|
+
* de dentro del recuadro. Eso solo pasa de una forma: el modo crudo estaba
|
|
14
|
+
* apagado, asi que quien hacia el eco era PowerShell —en el sitio donde estuviera
|
|
15
|
+
* el cursor— y el prompt no se enteraba de ninguna tecla, porque en modo cocido
|
|
16
|
+
* no llega nada hasta que se pulsa Enter. El teclado no estaba muerto: estaba
|
|
17
|
+
* en manos del terminal.
|
|
18
|
+
*
|
|
19
|
+
* POR QUE NO SE ARREGLA EN EL SITIO QUE LO ROMPE
|
|
20
|
+
*
|
|
21
|
+
* Hay demasiados sitios que lo rompen, y todos con razon:
|
|
22
|
+
*
|
|
23
|
+
* - `InterruptWatcher.stop()` devuelve el modo crudo a como estaba. Si un
|
|
24
|
+
* vigilante se para TARDE —despues de que el prompt ya se haya dibujado—
|
|
25
|
+
* restaura un estado que ya no toca y se lo quita al prompt.
|
|
26
|
+
* - `readline.createInterface(...).close()` hace por dentro `pause()` y
|
|
27
|
+
* `setRawMode(false)`. Lo usan el dialogo de "¿que prefieres que haga?" y la
|
|
28
|
+
* pregunta del detector de bucles.
|
|
29
|
+
* - Cualquier salida del bucle que se escape sin parar lo suyo.
|
|
30
|
+
*
|
|
31
|
+
* Ya hubo dos intentos de arreglarlo apretando tuercas una por una, y el fallo
|
|
32
|
+
* volvio las dos veces, porque basta que UNA de las veinte salidas se escape.
|
|
33
|
+
* Asi que el prompt deja de confiar: mientras esta abierto, comprueba cuatro
|
|
34
|
+
* veces por segundo que el teclado sigue siendo suyo y lo recupera si no.
|
|
35
|
+
*
|
|
36
|
+
* Cuesta cuatro comparaciones cada 250 ms. El fallo costaba cerrar el terminal.
|
|
37
|
+
*/
|
|
38
|
+
/** Lo minimo de `process.stdin` que hace falta, para poder probarlo con uno falso. */
|
|
39
|
+
export interface EntradaDeTeclado {
|
|
40
|
+
isTTY?: boolean;
|
|
41
|
+
isRaw?: boolean;
|
|
42
|
+
setRawMode?: (flag: boolean) => unknown;
|
|
43
|
+
isPaused?: () => boolean;
|
|
44
|
+
resume?: () => unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Devuelve el teclado al prompt si alguien se lo ha quitado.
|
|
48
|
+
*
|
|
49
|
+
* Devuelve la lista de lo que ha tenido que reparar. Vacia es lo normal: si
|
|
50
|
+
* alguna vez devuelve algo, hay un fallo de verdad en quien solto el teclado, y
|
|
51
|
+
* por eso `CHOCOLATITO_TECLADO=debug` lo saca por pantalla en vez de callarselo.
|
|
52
|
+
*/
|
|
53
|
+
export declare function repararTeclado(entrada: EntradaDeTeclado): string[];
|
|
54
|
+
/** Cada cuanto se comprueba. Cuatro veces por segundo se nota como instantaneo. */
|
|
55
|
+
export declare const MS_ENTRE_COMPROBACIONES = 250;
|
|
56
|
+
/**
|
|
57
|
+
* Vuelve a poner el modo crudo DE VERDAD, no solo en la cuenta de Node.
|
|
58
|
+
*
|
|
59
|
+
* EL AGUJERO QUE TAPA
|
|
60
|
+
*
|
|
61
|
+
* `setRawMode(true)` no siempre llega al terminal. Por debajo, libuv guarda el
|
|
62
|
+
* modo que cree tener y empieza con `if (tty->mode == mode) return 0;`. Asi que
|
|
63
|
+
* si algo cambia el modo de la consola por fuera de Node -un proceso hijo, la
|
|
64
|
+
* propia shell, el terminal- Node sigue creyendo que esta en crudo, y a partir
|
|
65
|
+
* de ahi TODOS los `setRawMode(true)` se convierten en no-op. Incluido el que
|
|
66
|
+
* hace el prompt al abrirse.
|
|
67
|
+
*
|
|
68
|
+
* Se veia asi, y se reporto cuatro veces: el usuario teclea, su texto sale al
|
|
69
|
+
* final de la linea de ayuda -lo esta haciendo el eco del terminal, no el
|
|
70
|
+
* prompt- y el recuadro no se entera de nada. El vigilante tampoco reparaba
|
|
71
|
+
* nada, porque preguntaba a Node y Node decia que todo estaba bien.
|
|
72
|
+
*
|
|
73
|
+
* Pasar por `false` primero rompe esa cache: la segunda llamada ya ve un cambio
|
|
74
|
+
* de verdad y lo aplica sobre la consola.
|
|
75
|
+
*/
|
|
76
|
+
export declare function reafirmarModoCrudo(entrada: EntradaDeTeclado): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El prompt no suelta el teclado mientras esta abierto.
|
|
3
|
+
*
|
|
4
|
+
* EL FALLO QUE CIERRA ESTE ARCHIVO
|
|
5
|
+
*
|
|
6
|
+
* Reportado tres veces desde el uso real, siempre igual: "escribi 2 prompts y
|
|
7
|
+
* al siguiente ya no me deja escribir nada, teclee lo que teclee". La sesion
|
|
8
|
+
* pegada por el usuario tenia la pista buena, escondida en la ultima linea:
|
|
9
|
+
*
|
|
10
|
+
* ? /help para atajos · esc cancelar ⇥ modo normal · Espectro 1.0 (Agil) estaba haciendo y denuevo...
|
|
11
|
+
*
|
|
12
|
+
* Lo que escribia SI aparecia, pero pegado al final de la linea de ayuda en vez
|
|
13
|
+
* de dentro del recuadro. Eso solo pasa de una forma: el modo crudo estaba
|
|
14
|
+
* apagado, asi que quien hacia el eco era PowerShell —en el sitio donde estuviera
|
|
15
|
+
* el cursor— y el prompt no se enteraba de ninguna tecla, porque en modo cocido
|
|
16
|
+
* no llega nada hasta que se pulsa Enter. El teclado no estaba muerto: estaba
|
|
17
|
+
* en manos del terminal.
|
|
18
|
+
*
|
|
19
|
+
* POR QUE NO SE ARREGLA EN EL SITIO QUE LO ROMPE
|
|
20
|
+
*
|
|
21
|
+
* Hay demasiados sitios que lo rompen, y todos con razon:
|
|
22
|
+
*
|
|
23
|
+
* - `InterruptWatcher.stop()` devuelve el modo crudo a como estaba. Si un
|
|
24
|
+
* vigilante se para TARDE —despues de que el prompt ya se haya dibujado—
|
|
25
|
+
* restaura un estado que ya no toca y se lo quita al prompt.
|
|
26
|
+
* - `readline.createInterface(...).close()` hace por dentro `pause()` y
|
|
27
|
+
* `setRawMode(false)`. Lo usan el dialogo de "¿que prefieres que haga?" y la
|
|
28
|
+
* pregunta del detector de bucles.
|
|
29
|
+
* - Cualquier salida del bucle que se escape sin parar lo suyo.
|
|
30
|
+
*
|
|
31
|
+
* Ya hubo dos intentos de arreglarlo apretando tuercas una por una, y el fallo
|
|
32
|
+
* volvio las dos veces, porque basta que UNA de las veinte salidas se escape.
|
|
33
|
+
* Asi que el prompt deja de confiar: mientras esta abierto, comprueba cuatro
|
|
34
|
+
* veces por segundo que el teclado sigue siendo suyo y lo recupera si no.
|
|
35
|
+
*
|
|
36
|
+
* Cuesta cuatro comparaciones cada 250 ms. El fallo costaba cerrar el terminal.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Devuelve el teclado al prompt si alguien se lo ha quitado.
|
|
40
|
+
*
|
|
41
|
+
* Devuelve la lista de lo que ha tenido que reparar. Vacia es lo normal: si
|
|
42
|
+
* alguna vez devuelve algo, hay un fallo de verdad en quien solto el teclado, y
|
|
43
|
+
* por eso `CHOCOLATITO_TECLADO=debug` lo saca por pantalla en vez de callarselo.
|
|
44
|
+
*/
|
|
45
|
+
export function repararTeclado(entrada) {
|
|
46
|
+
// Sin terminal delante (una tuberia, CI, `chocolatito "tarea"`) no hay modo
|
|
47
|
+
// crudo que defender y forzarlo seria un error.
|
|
48
|
+
if (!entrada || !entrada.isTTY || typeof entrada.setRawMode !== "function")
|
|
49
|
+
return [];
|
|
50
|
+
const reparado = [];
|
|
51
|
+
if (entrada.isRaw !== true) {
|
|
52
|
+
reafirmarModoCrudo(entrada);
|
|
53
|
+
reparado.push("modo crudo");
|
|
54
|
+
}
|
|
55
|
+
// `pause()` corta el flujo sin avisar a nadie: el oyente de `keypress` sigue
|
|
56
|
+
// apuntado y no recibe ni una tecla. Es lo que deja detras un readline al
|
|
57
|
+
// cerrarse.
|
|
58
|
+
if (typeof entrada.isPaused === "function" && entrada.isPaused() && typeof entrada.resume === "function") {
|
|
59
|
+
entrada.resume();
|
|
60
|
+
reparado.push("entrada en pausa");
|
|
61
|
+
}
|
|
62
|
+
return reparado;
|
|
63
|
+
}
|
|
64
|
+
/** Cada cuanto se comprueba. Cuatro veces por segundo se nota como instantaneo. */
|
|
65
|
+
export const MS_ENTRE_COMPROBACIONES = 250;
|
|
66
|
+
/**
|
|
67
|
+
* Vuelve a poner el modo crudo DE VERDAD, no solo en la cuenta de Node.
|
|
68
|
+
*
|
|
69
|
+
* EL AGUJERO QUE TAPA
|
|
70
|
+
*
|
|
71
|
+
* `setRawMode(true)` no siempre llega al terminal. Por debajo, libuv guarda el
|
|
72
|
+
* modo que cree tener y empieza con `if (tty->mode == mode) return 0;`. Asi que
|
|
73
|
+
* si algo cambia el modo de la consola por fuera de Node -un proceso hijo, la
|
|
74
|
+
* propia shell, el terminal- Node sigue creyendo que esta en crudo, y a partir
|
|
75
|
+
* de ahi TODOS los `setRawMode(true)` se convierten en no-op. Incluido el que
|
|
76
|
+
* hace el prompt al abrirse.
|
|
77
|
+
*
|
|
78
|
+
* Se veia asi, y se reporto cuatro veces: el usuario teclea, su texto sale al
|
|
79
|
+
* final de la linea de ayuda -lo esta haciendo el eco del terminal, no el
|
|
80
|
+
* prompt- y el recuadro no se entera de nada. El vigilante tampoco reparaba
|
|
81
|
+
* nada, porque preguntaba a Node y Node decia que todo estaba bien.
|
|
82
|
+
*
|
|
83
|
+
* Pasar por `false` primero rompe esa cache: la segunda llamada ya ve un cambio
|
|
84
|
+
* de verdad y lo aplica sobre la consola.
|
|
85
|
+
*/
|
|
86
|
+
export function reafirmarModoCrudo(entrada) {
|
|
87
|
+
if (!entrada || typeof entrada.setRawMode !== "function")
|
|
88
|
+
return;
|
|
89
|
+
try {
|
|
90
|
+
entrada.setRawMode(false);
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
entrada.setRawMode(true);
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AvisoDeBucle } from "../agent/loopDetector.js";
|
|
2
|
+
export interface RespuestaDeBucle {
|
|
3
|
+
/** "seguir" deja al agente repetir; "parar" corta esa llamada y le obliga a cambiar. */
|
|
4
|
+
accion: "seguir" | "parar";
|
|
5
|
+
indicacion?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Que hacer cuando el agente lleva tres vueltas haciendo exactamente lo mismo.
|
|
9
|
+
*
|
|
10
|
+
* Sin terminal —CI, `chocolatito "tarea"`, un cron— se PARA. Es lo contrario de
|
|
11
|
+
* lo que hace el dialogo de permisos, que ante la duda deniega para no tocar
|
|
12
|
+
* nada, y el motivo es que aqui el riesgo es el opuesto: no hay nada que
|
|
13
|
+
* proteger del agente, hay una cuota que proteger de un bucle que nadie esta
|
|
14
|
+
* mirando. Parar no mata el turno: le devuelve al modelo un mensaje explicando
|
|
15
|
+
* el atasco, y el turno sigue.
|
|
16
|
+
*/
|
|
17
|
+
export declare function preguntarPorBucle(aviso: AvisoDeBucle): Promise<RespuestaDeBucle>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
/**
|
|
5
|
+
* Que hacer cuando el agente lleva tres vueltas haciendo exactamente lo mismo.
|
|
6
|
+
*
|
|
7
|
+
* Sin terminal —CI, `chocolatito "tarea"`, un cron— se PARA. Es lo contrario de
|
|
8
|
+
* lo que hace el dialogo de permisos, que ante la duda deniega para no tocar
|
|
9
|
+
* nada, y el motivo es que aqui el riesgo es el opuesto: no hay nada que
|
|
10
|
+
* proteger del agente, hay una cuota que proteger de un bucle que nadie esta
|
|
11
|
+
* mirando. Parar no mata el turno: le devuelve al modelo un mensaje explicando
|
|
12
|
+
* el atasco, y el turno sigue.
|
|
13
|
+
*/
|
|
14
|
+
export async function preguntarPorBucle(aviso) {
|
|
15
|
+
const cabecera = chalk.yellow(`\n↻ Parece un bucle: `) +
|
|
16
|
+
chalk.white(`"${aviso.herramienta}"`) +
|
|
17
|
+
chalk.yellow(` lleva ${aviso.repeticiones} llamadas identicas con el mismo resultado.`);
|
|
18
|
+
if (!process.stdin.isTTY) {
|
|
19
|
+
console.log(cabecera + chalk.gray("\n Sin terminal interactiva: se corta la repeticion para no gastar cuota.\n"));
|
|
20
|
+
return { accion: "parar" };
|
|
21
|
+
}
|
|
22
|
+
console.log(cabecera);
|
|
23
|
+
console.log(chalk.gray(" ¿Le dejo seguir intentandolo, o le digo que cambie de enfoque?\n"));
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
26
|
+
// Como en el dialogo de permisos: 'close' llega tambien tras una respuesta
|
|
27
|
+
// normal, y sin guarda el mensaje de corte pisaria lo que el usuario acaba
|
|
28
|
+
// de escribir. Manda quien llegue primero.
|
|
29
|
+
let asentada = false;
|
|
30
|
+
const asentar = (r) => {
|
|
31
|
+
if (asentada)
|
|
32
|
+
return;
|
|
33
|
+
asentada = true;
|
|
34
|
+
resolve(r);
|
|
35
|
+
};
|
|
36
|
+
// Ctrl+C, Ctrl+D o stdin cerrandose significan lo mismo: no hay respuesta.
|
|
37
|
+
// Aqui eso es "para", que es la opcion que no cuesta dinero.
|
|
38
|
+
rl.on("close", () => {
|
|
39
|
+
// readline duerme su entrada al cerrarse (`close()` llama a `pause()`).
|
|
40
|
+
// Aqui estamos a mitad de turno: si se queda dormida, el vigilante que
|
|
41
|
+
// sigue no recibe ni una tecla y el Esc deja de cancelar.
|
|
42
|
+
process.stdin.resume();
|
|
43
|
+
if (asentada)
|
|
44
|
+
return;
|
|
45
|
+
process.stdout.write("\n");
|
|
46
|
+
asentar({ accion: "parar" });
|
|
47
|
+
});
|
|
48
|
+
rl.question(chalk.hex("#A855F7")(" [enter] que cambie de enfoque · [s] dejale seguir · o escribe una indicacion ❯ "), (respuesta) => {
|
|
49
|
+
const texto = respuesta.trim();
|
|
50
|
+
if (texto.toLowerCase() === "s" || texto.toLowerCase() === "si") {
|
|
51
|
+
asentar({ accion: "seguir" });
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
asentar({ accion: "parar", indicacion: texto || undefined });
|
|
55
|
+
}
|
|
56
|
+
rl.close();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Un pegado grande no se mete entero en el recuadro.
|
|
3
|
+
*
|
|
4
|
+
* EL PROBLEMA
|
|
5
|
+
*
|
|
6
|
+
* Pegar un error de compilacion de 300 lineas metia las 300 lineas dentro del
|
|
7
|
+
* prompt. El recuadro crecia hasta llenar la pantalla, el marco se volvia
|
|
8
|
+
* inutil, y para deshacerlo habia que pulsar borrar trescientas veces. Es el
|
|
9
|
+
* caso normal de usar un agente: uno pega un log, una traza, un archivo entero.
|
|
10
|
+
*
|
|
11
|
+
* COMO QUEDA
|
|
12
|
+
*
|
|
13
|
+
* El texto se guarda aparte y en el recuadro solo aparece una marca corta:
|
|
14
|
+
*
|
|
15
|
+
* ❯ arregla esto [pegado #1 · 347 lineas]
|
|
16
|
+
*
|
|
17
|
+
* Al enviar, la marca se sustituye por el texto completo, asi que el modelo
|
|
18
|
+
* recibe exactamente lo que se pego. Y un solo borrar se lleva la marca entera:
|
|
19
|
+
* lo que ocupa una linea se borra como una linea.
|
|
20
|
+
*
|
|
21
|
+
* POR QUE UNA MARCA DE TEXTO Y NO UNA ESTRUCTURA APARTE
|
|
22
|
+
*
|
|
23
|
+
* Porque el buffer del prompt es una cadena y todo lo demas -el cursor, el
|
|
24
|
+
* historial, el recorte del marco, la busqueda de menciones- cuenta caracteres.
|
|
25
|
+
* Meter ahi un objeto obligaria a tocar las seis cosas. Una marca de texto se
|
|
26
|
+
* lleva bien con todo lo que ya funciona.
|
|
27
|
+
*/
|
|
28
|
+
/** A partir de aqui, un pegado se guarda aparte en vez de llenar el recuadro. */
|
|
29
|
+
export declare const MINIMO_LINEAS = 5;
|
|
30
|
+
export declare const MINIMO_CARACTERES = 400;
|
|
31
|
+
/** Si merece la pena guardarlo aparte, o es lo bastante corto para verlo entero. */
|
|
32
|
+
export declare function esGrande(texto: string): boolean;
|
|
33
|
+
/** Guarda el texto y devuelve la marca que va en el recuadro. */
|
|
34
|
+
export declare function guardarPegado(texto: string): string;
|
|
35
|
+
/** Devuelve el texto de verdad, con las marcas sustituidas. Es lo que ve el modelo. */
|
|
36
|
+
export declare function expandirPegados(entrada: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Si justo antes del cursor termina una marca, dice donde empieza.
|
|
39
|
+
*
|
|
40
|
+
* Es lo que hace que un solo borrar se lleve el pegado entero. Sin esto habria
|
|
41
|
+
* que borrar la marca letra a letra, que es un poco menos absurdo que borrar
|
|
42
|
+
* las 347 lineas pero sigue siendo absurdo.
|
|
43
|
+
*/
|
|
44
|
+
export declare function marcaQueTerminaEn(buffer: string, cursor: number): {
|
|
45
|
+
inicio: number;
|
|
46
|
+
} | null;
|
|
47
|
+
/** Se olvidan al cerrar el prompt: cada turno empieza con su propia cuenta. */
|
|
48
|
+
export declare function limpiarPegados(): void;
|
|
49
|
+
/** Cuantos hay guardados ahora mismo. Solo para las pruebas. */
|
|
50
|
+
export declare function cuantosPegados(): number;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Un pegado grande no se mete entero en el recuadro.
|
|
3
|
+
*
|
|
4
|
+
* EL PROBLEMA
|
|
5
|
+
*
|
|
6
|
+
* Pegar un error de compilacion de 300 lineas metia las 300 lineas dentro del
|
|
7
|
+
* prompt. El recuadro crecia hasta llenar la pantalla, el marco se volvia
|
|
8
|
+
* inutil, y para deshacerlo habia que pulsar borrar trescientas veces. Es el
|
|
9
|
+
* caso normal de usar un agente: uno pega un log, una traza, un archivo entero.
|
|
10
|
+
*
|
|
11
|
+
* COMO QUEDA
|
|
12
|
+
*
|
|
13
|
+
* El texto se guarda aparte y en el recuadro solo aparece una marca corta:
|
|
14
|
+
*
|
|
15
|
+
* ❯ arregla esto [pegado #1 · 347 lineas]
|
|
16
|
+
*
|
|
17
|
+
* Al enviar, la marca se sustituye por el texto completo, asi que el modelo
|
|
18
|
+
* recibe exactamente lo que se pego. Y un solo borrar se lleva la marca entera:
|
|
19
|
+
* lo que ocupa una linea se borra como una linea.
|
|
20
|
+
*
|
|
21
|
+
* POR QUE UNA MARCA DE TEXTO Y NO UNA ESTRUCTURA APARTE
|
|
22
|
+
*
|
|
23
|
+
* Porque el buffer del prompt es una cadena y todo lo demas -el cursor, el
|
|
24
|
+
* historial, el recorte del marco, la busqueda de menciones- cuenta caracteres.
|
|
25
|
+
* Meter ahi un objeto obligaria a tocar las seis cosas. Una marca de texto se
|
|
26
|
+
* lleva bien con todo lo que ya funciona.
|
|
27
|
+
*/
|
|
28
|
+
/** A partir de aqui, un pegado se guarda aparte en vez de llenar el recuadro. */
|
|
29
|
+
export const MINIMO_LINEAS = 5;
|
|
30
|
+
export const MINIMO_CARACTERES = 400;
|
|
31
|
+
const guardados = new Map();
|
|
32
|
+
let siguiente = 1;
|
|
33
|
+
/** Reconoce una marca. El id es lo unico que hace falta para recuperar el texto. */
|
|
34
|
+
const MARCA = /\[pegado #(\d+) · [^\]]*\]/g;
|
|
35
|
+
function resumen(texto) {
|
|
36
|
+
const lineas = texto.split("\n").length;
|
|
37
|
+
if (lineas > 1)
|
|
38
|
+
return `${lineas.toLocaleString("es")} lineas`;
|
|
39
|
+
return `${texto.length.toLocaleString("es")} caracteres`;
|
|
40
|
+
}
|
|
41
|
+
/** Si merece la pena guardarlo aparte, o es lo bastante corto para verlo entero. */
|
|
42
|
+
export function esGrande(texto) {
|
|
43
|
+
return texto.length >= MINIMO_CARACTERES || texto.split("\n").length > MINIMO_LINEAS;
|
|
44
|
+
}
|
|
45
|
+
/** Guarda el texto y devuelve la marca que va en el recuadro. */
|
|
46
|
+
export function guardarPegado(texto) {
|
|
47
|
+
const id = siguiente++;
|
|
48
|
+
guardados.set(id, texto);
|
|
49
|
+
return `[pegado #${id} · ${resumen(texto)}]`;
|
|
50
|
+
}
|
|
51
|
+
/** Devuelve el texto de verdad, con las marcas sustituidas. Es lo que ve el modelo. */
|
|
52
|
+
export function expandirPegados(entrada) {
|
|
53
|
+
// Una marca de un id que ya no existe se deja tal cual: es texto que el
|
|
54
|
+
// usuario escribio a mano, y borrarselo seria peor que dejarlo.
|
|
55
|
+
return entrada.replace(MARCA, (todo, id) => guardados.get(Number(id)) ?? todo);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Si justo antes del cursor termina una marca, dice donde empieza.
|
|
59
|
+
*
|
|
60
|
+
* Es lo que hace que un solo borrar se lleve el pegado entero. Sin esto habria
|
|
61
|
+
* que borrar la marca letra a letra, que es un poco menos absurdo que borrar
|
|
62
|
+
* las 347 lineas pero sigue siendo absurdo.
|
|
63
|
+
*/
|
|
64
|
+
export function marcaQueTerminaEn(buffer, cursor) {
|
|
65
|
+
if (cursor <= 0 || buffer[cursor - 1] !== "]")
|
|
66
|
+
return null;
|
|
67
|
+
const trozo = buffer.slice(0, cursor);
|
|
68
|
+
const inicio = trozo.lastIndexOf("[pegado #");
|
|
69
|
+
if (inicio < 0)
|
|
70
|
+
return null;
|
|
71
|
+
// Que lo que hay entre los corchetes sea de verdad una marca y no un
|
|
72
|
+
// "[pegado #" que el usuario escribiera a mano.
|
|
73
|
+
return /^\[pegado #\d+ · [^\]]*\]$/.test(trozo.slice(inicio)) ? { inicio } : null;
|
|
74
|
+
}
|
|
75
|
+
/** Se olvidan al cerrar el prompt: cada turno empieza con su propia cuenta. */
|
|
76
|
+
export function limpiarPegados() {
|
|
77
|
+
guardados.clear();
|
|
78
|
+
siguiente = 1;
|
|
79
|
+
}
|
|
80
|
+
/** Cuantos hay guardados ahora mismo. Solo para las pruebas. */
|
|
81
|
+
export function cuantosPegados() {
|
|
82
|
+
return guardados.size;
|
|
83
|
+
}
|
|
@@ -2,6 +2,7 @@ import readline from "node:readline";
|
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { cycleMode, describeMode, getMode, modeAnnouncement } from "./modes.js";
|
|
5
|
+
import { isOutsideProject, isSvgSource } from "../config/permissions.js";
|
|
5
6
|
/**
|
|
6
7
|
* Aprobacion automatica: se activa con --yes / -y o con la variable de entorno
|
|
7
8
|
* CHOCOLATITO_AUTO_APPROVE. Pensada para modo no interactivo y CI.
|
|
@@ -77,9 +78,45 @@ export async function askToolPermission(toolName, args, cwd = process.cwd()) {
|
|
|
77
78
|
input: process.stdin,
|
|
78
79
|
output: process.stdout,
|
|
79
80
|
});
|
|
81
|
+
// Guarda de resolucion unica: 'close' llega tambien despues de una
|
|
82
|
+
// respuesta normal, y sin ella el mensaje de cancelado pisaria al texto
|
|
83
|
+
// que el usuario acaba de escribir. Manda quien llegue primero.
|
|
84
|
+
let asentada = false;
|
|
85
|
+
const asentar = (texto) => {
|
|
86
|
+
if (asentada)
|
|
87
|
+
return;
|
|
88
|
+
asentada = true;
|
|
89
|
+
resFeedback(texto);
|
|
90
|
+
};
|
|
91
|
+
// Ctrl+C aqui dentro no llama al callback de 'line': readline cierra la
|
|
92
|
+
// interfaz y punto. Sin este oyente la promesa no se asentaba nunca, el
|
|
93
|
+
// `await` de askToolPermission no volvia y el bucle del agente se
|
|
94
|
+
// quedaba vivo pero inerte: ni ejecutaba, ni cancelaba, ni devolvia el
|
|
95
|
+
// prompt. La unica salida era un segundo Ctrl+C, que ya llegaba con el
|
|
96
|
+
// terminal en modo cocido y mataba la sesion entera.
|
|
97
|
+
//
|
|
98
|
+
// Vale igual para Ctrl+D y para stdin cerrandose por su cuenta: todo lo
|
|
99
|
+
// que cierre la interfaz sin linea significa lo mismo, que no hay
|
|
100
|
+
// respuesta, y eso se traduce en cancelar la accion.
|
|
101
|
+
rl.on("close", () => {
|
|
102
|
+
if (asentada)
|
|
103
|
+
return;
|
|
104
|
+
// El cursor se queda a medio renglon, pegado al "❯": se cierra la
|
|
105
|
+
// linea para que lo siguiente no salga encima del prompt.
|
|
106
|
+
process.stdout.write("\n");
|
|
107
|
+
asentar("Cancelado por el usuario con Ctrl+C.");
|
|
108
|
+
});
|
|
109
|
+
// Un readline, al cerrarse, hace por dentro `pause()` sobre su entrada
|
|
110
|
+
// ademas de apagar el modo crudo. Quien venga detras —el vigilante del
|
|
111
|
+
// resto del turno— da por hecho que la entrada fluye, se apunta como
|
|
112
|
+
// oyente y no recibe ni una tecla: el Esc deja de cancelar y lo que se
|
|
113
|
+
// teclee no se encola. Se despierta aqui, que es donde se durmio.
|
|
114
|
+
rl.on("close", () => {
|
|
115
|
+
process.stdin.resume();
|
|
116
|
+
});
|
|
80
117
|
rl.question(chalk.hex("#A855F7")("\n¿Qué prefieres que haga? ❯ "), (answer) => {
|
|
118
|
+
asentar(answer.trim() || "El usuario canceló la acción sin dar detalles adicionales.");
|
|
81
119
|
rl.close();
|
|
82
|
-
resFeedback(answer.trim() || "El usuario canceló la acción sin dar detalles adicionales.");
|
|
83
120
|
});
|
|
84
121
|
});
|
|
85
122
|
}
|
|
@@ -235,6 +272,10 @@ function classify(toolName, args) {
|
|
|
235
272
|
return { title: " Controlar tu ordenador", color: "#A855F7" };
|
|
236
273
|
return { title: " Permiso de ejecución", color: "#D97757" };
|
|
237
274
|
}
|
|
275
|
+
/** Herramientas que conducen pestanas del navegador del usuario. */
|
|
276
|
+
function esNavegador(toolName) {
|
|
277
|
+
return toolName === "chrome" || toolName === "browser" || toolName === "browser_live";
|
|
278
|
+
}
|
|
238
279
|
/** Las lineas de detalle, ya recortadas para que quepan dentro del marco. */
|
|
239
280
|
function details(toolName, args, cwd, width) {
|
|
240
281
|
const max = width - 8;
|
|
@@ -258,14 +299,55 @@ function details(toolName, args, cwd, width) {
|
|
|
258
299
|
add(plain, `${chalk.green(`+${mas} líneas`)} ${chalk.red(`-${menos} líneas`)}`);
|
|
259
300
|
}
|
|
260
301
|
}
|
|
302
|
+
else if (typeof args.outputPath === "string") {
|
|
303
|
+
// create_image no trae ni `command` ni `path`, asi que el dialogo caia en el
|
|
304
|
+
// caso de abajo y pedia aprobar la palabra "create_image" a secas: ni el
|
|
305
|
+
// destino ni el detalle de que el generatorScript se ejecuta con python.
|
|
306
|
+
add(args.outputPath, chalk.bold.white(args.outputPath));
|
|
307
|
+
const script = typeof args.generatorScript === "string" ? args.generatorScript : "";
|
|
308
|
+
const lineas = script.split("\n").length;
|
|
309
|
+
if (isSvgSource(script)) {
|
|
310
|
+
add(`SVG, ${lineas} líneas`, chalk.gray(`SVG, ${lineas} líneas`));
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
const plain = `ejecuta ${lineas} líneas de Python`;
|
|
314
|
+
add(plain, chalk.hex("#F59E0B")(plain));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else if (typeof args.sourcePath === "string") {
|
|
318
|
+
// move_copy_file no trae `path` ni `outputPath`, asi que caia en el caso de
|
|
319
|
+
// abajo y el dialogo pedia aprobar la palabra "move_copy_file" a secas. Lo
|
|
320
|
+
// que hay que ver aqui es justo lo contrario: que archivo se pisa.
|
|
321
|
+
add(args.sourcePath, chalk.bold.white(args.sourcePath));
|
|
322
|
+
const destino = String(args.destinationPath ?? "");
|
|
323
|
+
add(`-> ${destino}`, `${chalk.gray("->")} ${chalk.bold.white(destino)}`);
|
|
324
|
+
if (args.overwrite === true) {
|
|
325
|
+
const aviso = "el destino ya existe y su contenido se pierde";
|
|
326
|
+
add(aviso, chalk.hex("#EF4444")(aviso));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
261
329
|
else if (typeof args.action === "string") {
|
|
262
|
-
|
|
330
|
+
// Sobre QUE actua importa tanto como la accion. Un "close_tab" a secas no
|
|
331
|
+
// decia que pestana moria, asi que aprobarlo era aprobar a ciegas.
|
|
332
|
+
const objetivo = args.url || args.appName || args.window || args.text ||
|
|
333
|
+
(typeof args.tabId === "number" ? `pestaña ${args.tabId}` : "") ||
|
|
334
|
+
(typeof args.ref === "number" ? `elemento [${args.ref}]` : "") ||
|
|
335
|
+
"";
|
|
336
|
+
const extra = objetivo || (esNavegador(toolName) ? "en la pestaña de trabajo" : "");
|
|
263
337
|
const plain = extra ? `${args.action} ${String(extra)}` : String(args.action);
|
|
264
338
|
add(plain, extra ? `${chalk.bold.white(args.action)} ${chalk.gray(String(extra))}` : chalk.bold.white(args.action));
|
|
265
339
|
}
|
|
266
340
|
else {
|
|
267
341
|
add(toolName, chalk.bold.white(toolName));
|
|
268
342
|
}
|
|
343
|
+
// Una ruta larga se lee igual venga de donde venga: "C:\Users\yo\proyecto\src"
|
|
344
|
+
// y "C:\Windows\System32" ocupan lo mismo en el marco. Este es justo el
|
|
345
|
+
// dialogo que se aprueba de un vistazo, asi que el motivo por el que aparece
|
|
346
|
+
// va escrito con letras.
|
|
347
|
+
if ([args.path, args.outputPath, args.sourcePath, args.destinationPath].some((r) => isOutsideProject(cwd, r))) {
|
|
348
|
+
const aviso = "fuera del proyecto";
|
|
349
|
+
add(aviso, chalk.hex("#EF4444")(aviso));
|
|
350
|
+
}
|
|
269
351
|
out.push({ text: "", length: 0 });
|
|
270
352
|
return out;
|
|
271
353
|
}
|
package/dist/ui/prompt.d.ts
CHANGED
|
@@ -1,7 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imprime sin romper el prompt. Uselo en vez de `console.log` para todo lo que
|
|
3
|
+
* pueda llegar TARDE: conexiones en segundo plano, avisos, temporizadores.
|
|
4
|
+
*/
|
|
5
|
+
export declare function imprimirSobreElPrompt(texto: string): void;
|
|
1
6
|
export interface SlashCommandItem {
|
|
2
7
|
name: string;
|
|
3
8
|
description: string;
|
|
4
9
|
args?: string;
|
|
5
10
|
}
|
|
6
11
|
export declare const SLASH_COMMANDS: SlashCommandItem[];
|
|
12
|
+
export interface DropdownItem {
|
|
13
|
+
type: "command" | "file";
|
|
14
|
+
label: string;
|
|
15
|
+
secondary?: string;
|
|
16
|
+
insertText: string;
|
|
17
|
+
}
|
|
18
|
+
export interface PromptFrameOptions {
|
|
19
|
+
cols: number;
|
|
20
|
+
/** Alto de la ventana; sirve para no dibujar un marco que no quepa. */
|
|
21
|
+
rows?: number;
|
|
22
|
+
promptSymbol?: string;
|
|
23
|
+
input?: string;
|
|
24
|
+
cursorIndex?: number;
|
|
25
|
+
items?: DropdownItem[];
|
|
26
|
+
selectedIndex?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface PromptFrame {
|
|
29
|
+
/** Lineas LOGICAS. Cualquiera puede ocupar mas de una fila fisica. */
|
|
30
|
+
lines: string[];
|
|
31
|
+
/** Filas de pantalla que ocupara el marco entero con este ancho. */
|
|
32
|
+
totalRows: number;
|
|
33
|
+
/** Fila fisica del cursor, con 0 en la primera fila del marco. */
|
|
34
|
+
cursorRow: number;
|
|
35
|
+
cursorCol: number;
|
|
36
|
+
/** Primer elemento del desplegable que se muestra (ventana deslizante). */
|
|
37
|
+
dropdownOffset: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Arma el marco del prompt y dice exactamente donde queda el cursor.
|
|
41
|
+
*
|
|
42
|
+
* Va aparte de render() por dos razones: se puede probar sin terminal, y la
|
|
43
|
+
* aritmetica de filas se hace UNA vez sobre las mismas lineas que se pintan, en
|
|
44
|
+
* lugar de estimarse por otro lado y descuadrarse.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildPromptFrame(opts: PromptFrameOptions): PromptFrame;
|
|
7
47
|
export declare function askInteractivePrompt(promptSymbol?: string, cwd?: string): Promise<string>;
|