chocolatito-code 1.3.0 → 1.3.2
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/dist/agent/loop.js +49 -13
- package/dist/config/quota.d.ts +11 -0
- package/dist/config/quota.js +18 -0
- package/dist/index.js +76 -38
- package/dist/prompts/systemPrompt.js +186 -150
- package/dist/ui/banner.d.ts +31 -0
- package/dist/ui/banner.js +45 -0
- package/dist/ui/comandos.d.ts +33 -0
- package/dist/ui/comandos.js +49 -0
- package/dist/ui/informeDeUso.d.ts +17 -0
- package/dist/ui/informeDeUso.js +81 -0
- package/dist/ui/marco.d.ts +35 -0
- package/dist/ui/marco.js +193 -0
- package/dist/ui/modes.d.ts +9 -1
- package/dist/ui/modes.js +11 -3
- package/dist/ui/motorActual.d.ts +28 -0
- package/dist/ui/motorActual.js +34 -0
- package/dist/ui/prompt.d.ts +4 -41
- package/dist/ui/prompt.js +42 -165
- package/dist/ui/renderer.d.ts +13 -0
- package/dist/ui/renderer.js +41 -12
- package/dist/ui/selector.d.ts +37 -0
- package/dist/ui/selector.js +81 -0
- package/dist/ui/spinner.d.ts +33 -5
- package/dist/ui/spinner.js +70 -44
- package/extension/manifest.json +60 -60
- package/package.json +67 -67
package/dist/ui/renderer.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POR QUE ESTO NO USA `console.log`
|
|
3
|
+
*
|
|
4
|
+
* Mientras el agente trabaja hay un bloque pintado abajo del todo -el spinner y
|
|
5
|
+
* la barra de escribir- que se borra y se repinta alrededor de cada escritura.
|
|
6
|
+
* Un `console.log` no pasa por ahi: escribe donde este el cursor, encima del
|
|
7
|
+
* bloque, y el repintado siguiente ya no sabe donde empieza el suyo. En pantalla
|
|
8
|
+
* eso se ve como un spinner que se queda atras y filas huerfanas por todos lados.
|
|
9
|
+
*
|
|
10
|
+
* Ademas era el ultimo sitio que ataba el agente a una terminal. `salida` existe
|
|
11
|
+
* justamente para que el bucle no sepa donde acaba lo que dice, y el
|
|
12
|
+
* renderizador -que es quien mas imprime- seguia escribiendo directo.
|
|
13
|
+
*/
|
|
1
14
|
import chalk from "chalk";
|
|
2
15
|
import { Marked } from "marked";
|
|
3
16
|
import { markedTerminal } from "marked-terminal";
|
|
17
|
+
import { salida } from "../agent/salidaDelAgente.js";
|
|
18
|
+
import { isManagedMode } from "../config/quota.js";
|
|
4
19
|
const marked = new Marked(markedTerminal({
|
|
5
20
|
heading: chalk.bold.hex("#D97757"),
|
|
6
21
|
firstHeading: chalk.bold.hex("#D97757").underline,
|
|
@@ -22,14 +37,14 @@ const marked = new Marked(markedTerminal({
|
|
|
22
37
|
export function renderUserPrompt(text) {
|
|
23
38
|
const symbol = chalk.bold.hex("#D97757")("❯");
|
|
24
39
|
const shaded = chalk.bgHex("#242730").hex("#F4F4F5").bold(` ${text.trim()} `);
|
|
25
|
-
|
|
40
|
+
salida.linea(`\n${symbol} ${shaded}\n`);
|
|
26
41
|
}
|
|
27
42
|
// Tool call rendering as specified in §B5
|
|
28
43
|
export function renderToolStart(name, summary) {
|
|
29
44
|
const bullet = chalk.hex("#FFA500")("●"); // Amber while starting
|
|
30
45
|
const toolName = chalk.bold.white(formatToolName(name));
|
|
31
46
|
const argPart = summary ? chalk.gray(`(${summary.length > 60 ? summary.slice(0, 57) + '...' : summary})`) : "";
|
|
32
|
-
|
|
47
|
+
salida.linea(`\n${bullet} ${toolName}${argPart}`);
|
|
33
48
|
}
|
|
34
49
|
export function renderToolSuccess(name, resultSummary) {
|
|
35
50
|
const rawLines = resultSummary.trim().split("\n");
|
|
@@ -40,23 +55,23 @@ export function renderToolSuccess(name, resultSummary) {
|
|
|
40
55
|
const prefix = idx === 0 ? ` ${chalk.gray("⎿")} ` : " ";
|
|
41
56
|
return `${prefix}${colorizeText(line)}`;
|
|
42
57
|
}).join("\n");
|
|
43
|
-
|
|
58
|
+
salida.linea(indented);
|
|
44
59
|
if (remainingCount > 0) {
|
|
45
|
-
|
|
60
|
+
salida.linea(chalk.gray(` … +${remainingCount} líneas más`));
|
|
46
61
|
}
|
|
47
62
|
}
|
|
48
63
|
export function renderToolError(name, errorSummary) {
|
|
49
|
-
|
|
64
|
+
salida.linea(` ${chalk.red("⎿")} ${chalk.red(errorSummary)}`);
|
|
50
65
|
}
|
|
51
66
|
export function renderDiff(filePath, diffText) {
|
|
52
|
-
|
|
67
|
+
salida.linea(`\n${diffText}\n`);
|
|
53
68
|
}
|
|
54
69
|
export function renderReasoning(reasoningText) {
|
|
55
70
|
if (!reasoningText.trim())
|
|
56
71
|
return;
|
|
57
72
|
const lines = reasoningText.trim().split("\n");
|
|
58
73
|
const formatted = lines.map((l) => ` ${chalk.hex("#8B5CF6")("│")} ${chalk.gray(l)}`).join("\n");
|
|
59
|
-
|
|
74
|
+
salida.linea(`\n ${chalk.bold.hex("#8B5CF6")("💭 Razonamiento:")}\n${formatted}\n`);
|
|
60
75
|
}
|
|
61
76
|
export function renderAssistantResponse(markdownText) {
|
|
62
77
|
try {
|
|
@@ -68,23 +83,37 @@ export function renderAssistantResponse(markdownText) {
|
|
|
68
83
|
let parsed = String(marked.parse(cleaned));
|
|
69
84
|
// Convert raw asterisks bullets to terracotta bullets
|
|
70
85
|
parsed = parsed.replace(/^(\s*)\*\s+/gm, `$1${chalk.hex("#D97757")("•")} `);
|
|
71
|
-
|
|
86
|
+
salida.linea(`\n${parsed}`);
|
|
72
87
|
}
|
|
73
88
|
catch {
|
|
74
|
-
|
|
89
|
+
salida.linea(`\n${markdownText.trim()}\n`);
|
|
75
90
|
}
|
|
76
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* La linea de cada turno.
|
|
94
|
+
*
|
|
95
|
+
* `⚡ 3.0s · 64 tokens salida (10.8k en caché) · Espectro 1.0 (Ágil) · 99%
|
|
96
|
+
* contexto libre · 28% de la cuota semanal`
|
|
97
|
+
*
|
|
98
|
+
* Con cuenta gestionada NO lleva importe. Se quito primero de /cost y esta se
|
|
99
|
+
* quedo atras; el usuario lo vio enseguida -"aun sigue saliendo el precio que se
|
|
100
|
+
* gasta"- y con razon: /cost se mira cuando uno se acuerda, esta linea sale en
|
|
101
|
+
* TODOS los turnos. De los dos sitios, era el que mas importaba.
|
|
102
|
+
*
|
|
103
|
+
* Con clave propia si sale: ahi el dinero es del usuario y es su factura.
|
|
104
|
+
*/
|
|
77
105
|
export function renderFooter(costStr, outTokens, cachedTokens, modelName, durationMs, contextPercent, quotaStr) {
|
|
78
106
|
const durationPart = durationMs ? `${(durationMs / 1000).toFixed(1)}s · ` : "";
|
|
79
107
|
const outPart = `${outTokens.toLocaleString()} tokens salida`;
|
|
80
108
|
const cachePart = cachedTokens > 0 ? ` (${(cachedTokens / 1000).toFixed(1)}k en caché)` : "";
|
|
81
109
|
const contextPart = contextPercent !== undefined ? ` · ${contextPercent}% contexto libre` : "";
|
|
82
110
|
const quotaPart = quotaStr ? ` · ${quotaStr}` : "";
|
|
83
|
-
const
|
|
84
|
-
|
|
111
|
+
const costPart = isManagedMode() ? "" : ` · ${chalk.green(costStr)}`;
|
|
112
|
+
const line = chalk.gray(` ${chalk.hex("#D97757")("⚡")} ${durationPart}${outPart}${cachePart}${costPart} · ${modelName}${contextPart}${quotaPart}`);
|
|
113
|
+
salida.linea(`${line}\n`);
|
|
85
114
|
}
|
|
86
115
|
export function renderError(message) {
|
|
87
|
-
|
|
116
|
+
salida.linea(`\n ${chalk.bgRed.white.bold(" ERROR ")} ${chalk.red(message)}\n`);
|
|
88
117
|
}
|
|
89
118
|
function colorizeText(text) {
|
|
90
119
|
// Highlight file paths (C:\..., /path/...) in cyan
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UNA LISTA POR LA QUE SE PUEDE SUBIR Y BAJAR
|
|
3
|
+
*
|
|
4
|
+
* `/effort` no era un menu: era un `console.log` con las cuatro opciones y una
|
|
5
|
+
* frase al final que decia "selecciona con /effort 2.0". El usuario lo vio en
|
|
6
|
+
* pantalla como una lista, hizo lo unico razonable -pulsar las flechas- y no
|
|
7
|
+
* paso nada. Sus palabras: "me salen en pantalla los modelos pero no puedo
|
|
8
|
+
* subir y bajar entre ellos con las flechitas".
|
|
9
|
+
*
|
|
10
|
+
* Una lista que parece elegible y no lo es engaña dos veces: primero no
|
|
11
|
+
* funciona, y despues hay que leerse la letra pequeña para descubrir que habia
|
|
12
|
+
* que escribir el comando otra vez con un argumento.
|
|
13
|
+
*
|
|
14
|
+
* POR QUE NO SE REUTILIZO EL DIALOGO DE PERMISOS
|
|
15
|
+
*
|
|
16
|
+
* Hace lo mismo -flechas y Enter- pero tiene dentro toda la logica de aprobar,
|
|
17
|
+
* rechazar, recordar patrones y pedir explicacion por escrito. Sacar de ahi la
|
|
18
|
+
* parte de elegir habria sido tocar el camino por el que pasa CADA accion con
|
|
19
|
+
* consecuencias del agente, y por un menu que se abre de higos a brevas. Esto
|
|
20
|
+
* son sesenta lineas que no tocan nada de eso.
|
|
21
|
+
*/
|
|
22
|
+
export interface OpcionDeLista {
|
|
23
|
+
/** Lo que se devuelve al elegirla. */
|
|
24
|
+
id: string;
|
|
25
|
+
/** Primera linea, la que se lee. */
|
|
26
|
+
titulo: string;
|
|
27
|
+
/** Segunda linea en gris. Opcional. */
|
|
28
|
+
detalle?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Enseña la lista y devuelve el id elegido. `null` si se sale con Esc.
|
|
32
|
+
*
|
|
33
|
+
* Sin terminal no hay nada que elegir: devuelve null en el acto en vez de
|
|
34
|
+
* quedarse esperando una tecla que no va a llegar nunca. Es lo que pasaria al
|
|
35
|
+
* correrlo desde un script o dentro de las pruebas.
|
|
36
|
+
*/
|
|
37
|
+
export declare function elegirDeLista(titulo: string, opciones: OpcionDeLista[], indiceInicial?: number): Promise<string | null>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import readline from "node:readline";
|
|
4
|
+
import { dibujar } from "./salida.js";
|
|
5
|
+
/**
|
|
6
|
+
* Enseña la lista y devuelve el id elegido. `null` si se sale con Esc.
|
|
7
|
+
*
|
|
8
|
+
* Sin terminal no hay nada que elegir: devuelve null en el acto en vez de
|
|
9
|
+
* quedarse esperando una tecla que no va a llegar nunca. Es lo que pasaria al
|
|
10
|
+
* correrlo desde un script o dentro de las pruebas.
|
|
11
|
+
*/
|
|
12
|
+
export async function elegirDeLista(titulo, opciones, indiceInicial = 0) {
|
|
13
|
+
if (opciones.length === 0)
|
|
14
|
+
return null;
|
|
15
|
+
if (!process.stdin.isTTY)
|
|
16
|
+
return null;
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
let elegido = Math.max(0, Math.min(indiceInicial, opciones.length - 1));
|
|
19
|
+
if (!readline.__choco_keypress) {
|
|
20
|
+
readline.emitKeypressEvents(process.stdin);
|
|
21
|
+
readline.__choco_keypress = true;
|
|
22
|
+
}
|
|
23
|
+
const eraCrudo = process.stdin.isRaw;
|
|
24
|
+
if (process.stdin.setRawMode)
|
|
25
|
+
process.stdin.setRawMode(true);
|
|
26
|
+
process.stdin.resume();
|
|
27
|
+
/** Filas del ultimo dibujo. Se repinta encima, sin dejar restos. */
|
|
28
|
+
let filas = 0;
|
|
29
|
+
function pintar() {
|
|
30
|
+
const lineas = ["", chalk.bold.hex("#E4CDEC")(titulo), ""];
|
|
31
|
+
opciones.forEach((o, i) => {
|
|
32
|
+
const puesto = i === elegido;
|
|
33
|
+
const punta = puesto ? chalk.bold.hex("#D97757")(" ❯ ") : " ";
|
|
34
|
+
const nombre = puesto ? chalk.bold.white(o.titulo) : chalk.hex("#A855F7")(o.titulo);
|
|
35
|
+
lineas.push(`${punta}${nombre}`);
|
|
36
|
+
if (o.detalle)
|
|
37
|
+
lineas.push(` ${chalk.gray(o.detalle)}`);
|
|
38
|
+
});
|
|
39
|
+
lineas.push("", chalk.gray(" ↑↓ para moverte · enter para elegir · esc para salir"), "");
|
|
40
|
+
// Se sube justo lo que se escribio la vez anterior: ni una fila mas, ni
|
|
41
|
+
// una menos. Contar de mas se come la linea de encima.
|
|
42
|
+
const salida = (filas > 0 ? `\x1b[${filas}A` : "") + lineas.map((l) => `\r\x1b[2K${l}`).join("\n") + "\n";
|
|
43
|
+
dibujar(salida);
|
|
44
|
+
filas = lineas.length;
|
|
45
|
+
}
|
|
46
|
+
function terminar(valor) {
|
|
47
|
+
process.stdin.removeListener("keypress", alPulsar);
|
|
48
|
+
// Se devuelve el modo que habia, no se fuerza uno: si el prompt tenia el
|
|
49
|
+
// teclado en crudo y aqui se dejara en cocido, la sesion se queda sin
|
|
50
|
+
// poder escribir y no se sabe por que.
|
|
51
|
+
if (process.stdin.setRawMode)
|
|
52
|
+
process.stdin.setRawMode(eraCrudo || false);
|
|
53
|
+
resolve(valor);
|
|
54
|
+
}
|
|
55
|
+
function alPulsar(_str, key) {
|
|
56
|
+
if (!key)
|
|
57
|
+
return;
|
|
58
|
+
if (key.name === "up" || (key.name === "k" && !key.ctrl)) {
|
|
59
|
+
elegido = (elegido - 1 + opciones.length) % opciones.length;
|
|
60
|
+
pintar();
|
|
61
|
+
}
|
|
62
|
+
else if (key.name === "down" || (key.name === "j" && !key.ctrl)) {
|
|
63
|
+
elegido = (elegido + 1) % opciones.length;
|
|
64
|
+
pintar();
|
|
65
|
+
}
|
|
66
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
67
|
+
terminar(opciones[elegido].id);
|
|
68
|
+
}
|
|
69
|
+
else if (key.name === "escape") {
|
|
70
|
+
terminar(null);
|
|
71
|
+
}
|
|
72
|
+
else if (key.ctrl && key.name === "c") {
|
|
73
|
+
// Ctrl+C aqui es "sal del menu", no "cierra el programa": estas
|
|
74
|
+
// eligiendo, no ejecutando nada.
|
|
75
|
+
terminar(null);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
pintar();
|
|
79
|
+
process.stdin.on("keypress", alPulsar);
|
|
80
|
+
});
|
|
81
|
+
}
|
package/dist/ui/spinner.d.ts
CHANGED
|
@@ -19,13 +19,41 @@ export declare class DynamicSpinner {
|
|
|
19
19
|
*/
|
|
20
20
|
private lineaDelSpinner;
|
|
21
21
|
/**
|
|
22
|
-
* La
|
|
22
|
+
* LA BARRA. La MISMA que se ve al escribir una orden, no una parecida.
|
|
23
23
|
*
|
|
24
|
-
* Antes
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Antes habia dos: al esperar salia el marco entero -barra, entrada, barra,
|
|
25
|
+
* linea de ayuda-; al trabajar, una version recortada con otro texto. Se
|
|
26
|
+
* parecian lo justo para que el cambio de una a otra se viera como un salto.
|
|
27
|
+
* Palabras del usuario: "aun no es fija... debe ser fija y por debajo deben
|
|
28
|
+
* pasar los otros mensajes".
|
|
29
|
+
*
|
|
30
|
+
* Ahora las dos salen de `buildPromptFrame`, asi que no pueden divergir: quien
|
|
31
|
+
* toque el marco lo toca en los dos estados a la vez.
|
|
32
|
+
*
|
|
33
|
+
* Sin desplegable: el autocompletado de "@" y "/" es del prompt de verdad, que
|
|
34
|
+
* es quien tiene el teclado. Aqui lo tecleado solo se encola.
|
|
28
35
|
*/
|
|
29
36
|
private lineasDeEntrada;
|
|
30
37
|
stop(): void;
|
|
31
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* DEJA EL PIE PUESTO MIENTRAS CORRE ALGO
|
|
41
|
+
*
|
|
42
|
+
* El spinner solo vivia mientras hablaba el modelo. En cuanto empezaba una
|
|
43
|
+
* herramienta se paraba, y ahi se acababa lo unico fijo de la pantalla: durante
|
|
44
|
+
* un `run_command` de veinte segundos, o una lectura de un archivo enorme, no
|
|
45
|
+
* habia ni cronometro ni forma de saber que seguia vivo. Justo el rato en que
|
|
46
|
+
* uno se pregunta si se ha colgado.
|
|
47
|
+
*
|
|
48
|
+
* EL RETRASO NO ES UN CAPRICHO
|
|
49
|
+
*
|
|
50
|
+
* La mayoria de las herramientas tardan milisegundos. Pintar el pie y borrarlo
|
|
51
|
+
* en el mismo suspiro es un parpadeo en cada llamada, y encima el pie mueve
|
|
52
|
+
* lineas: veinte herramientas seguidas serian veinte sacudidas. Con 150 ms de
|
|
53
|
+
* espera, lo rapido no pinta nada y lo lento sale igual.
|
|
54
|
+
*
|
|
55
|
+
* El `finally` es obligatorio. Un spinner que se queda corriendo repinta el pie
|
|
56
|
+
* ocho veces por segundo encima de lo que venga despues -incluido el prompt-, y
|
|
57
|
+
* de ahi no se sale sin cerrar el programa.
|
|
58
|
+
*/
|
|
59
|
+
export declare function conSpinner<T>(etiqueta: string, tarea: () => Promise<T>, retrasoMs?: number): Promise<T>;
|
package/dist/ui/spinner.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { typedText, queuedCount } from "./typeAhead.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { fijarSecciones, quitarSeccion } from "./pieFijo.js";
|
|
5
|
+
import { buildPromptFrame } from "./marco.js";
|
|
6
6
|
/**
|
|
7
7
|
* LA LINEA QUE DICE QUE ESTO SIGUE VIVO
|
|
8
8
|
*
|
|
@@ -31,18 +31,6 @@ const VERBS = [
|
|
|
31
31
|
"Tejiendo",
|
|
32
32
|
"Cocinando",
|
|
33
33
|
];
|
|
34
|
-
const PISTA_VACIA = "escribe para encolar un mensaje…";
|
|
35
|
-
function sinColores(texto) {
|
|
36
|
-
return texto.replace(/\x1b\[[0-9;]*m/g, "");
|
|
37
|
-
}
|
|
38
|
-
/** Deja las ULTIMAS `ancho` columnas: al escribir, lo que importa es el final. */
|
|
39
|
-
function colaVisible(texto, ancho) {
|
|
40
|
-
if (ancho <= 0)
|
|
41
|
-
return "";
|
|
42
|
-
if (texto.length <= ancho)
|
|
43
|
-
return texto;
|
|
44
|
-
return `…${texto.slice(-(ancho - 1))}`;
|
|
45
|
-
}
|
|
46
34
|
export class DynamicSpinner {
|
|
47
35
|
timer = null;
|
|
48
36
|
glyphIdx = 0;
|
|
@@ -89,42 +77,43 @@ export class DynamicSpinner {
|
|
|
89
77
|
* salian todas como "Rumiando…", indistinguibles de una peticion normal.
|
|
90
78
|
*/
|
|
91
79
|
lineaDelSpinner(elapsedSec) {
|
|
92
|
-
const cols = Math.max(20, process.stdout.columns || 80);
|
|
93
80
|
const glyph = chalk.hex("#D97757")(GLYPHS[this.glyphIdx]);
|
|
94
81
|
const verbo = this.prefix || VERBS[this.verbIdx];
|
|
95
82
|
const verb = chalk.italic.hex("#D97757")(`${verbo}…`);
|
|
96
83
|
// El contador va hacia abajo: son los tokens que ESTAN LLEGANDO del modelo.
|
|
97
84
|
const tokenStr = this.tokenCount > 0 ? ` · ↓ ${(this.tokenCount / 1000).toFixed(1)}k tokens` : "";
|
|
98
85
|
const cola = chalk.gray(`(${elapsedSec}s${tokenStr} · esc para pausar)`);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
const badge = modeBadge();
|
|
105
|
-
if (!badge)
|
|
106
|
-
return izquierda;
|
|
107
|
-
const anchoBadge = sinColores(badge).length;
|
|
108
|
-
const hueco = cols - anchoIzq - anchoBadge;
|
|
109
|
-
return hueco >= 2 ? `${izquierda}${" ".repeat(hueco)}${badge}` : izquierda;
|
|
86
|
+
// El modo YA NO va aqui. Vive en la linea de estado de la barra, que ahora
|
|
87
|
+
// es la misma en los dos estados: repetirlo era decir dos veces lo mismo en
|
|
88
|
+
// dos sitios que no siempre coincidian, y ademas obligaba a cuadrar anchos a
|
|
89
|
+
// mano justo en la linea mas propensa a partirse en dos filas.
|
|
90
|
+
return `${glyph} ${verb} ${cola}`;
|
|
110
91
|
}
|
|
111
92
|
/**
|
|
112
|
-
* La
|
|
93
|
+
* LA BARRA. La MISMA que se ve al escribir una orden, no una parecida.
|
|
94
|
+
*
|
|
95
|
+
* Antes habia dos: al esperar salia el marco entero -barra, entrada, barra,
|
|
96
|
+
* linea de ayuda-; al trabajar, una version recortada con otro texto. Se
|
|
97
|
+
* parecian lo justo para que el cambio de una a otra se viera como un salto.
|
|
98
|
+
* Palabras del usuario: "aun no es fija... debe ser fija y por debajo deben
|
|
99
|
+
* pasar los otros mensajes".
|
|
100
|
+
*
|
|
101
|
+
* Ahora las dos salen de `buildPromptFrame`, asi que no pueden divergir: quien
|
|
102
|
+
* toque el marco lo toca en los dos estados a la vez.
|
|
113
103
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* del vigilante y lo tecleado se acumulaba a ciegas. Ahora la barra esta
|
|
117
|
-
* siempre, y es la misma que se ve al escribir el prompt.
|
|
104
|
+
* Sin desplegable: el autocompletado de "@" y "/" es del prompt de verdad, que
|
|
105
|
+
* es quien tiene el teclado. Aqui lo tecleado solo se encola.
|
|
118
106
|
*/
|
|
119
107
|
lineasDeEntrada() {
|
|
120
|
-
const
|
|
121
|
-
|
|
108
|
+
const marco = buildPromptFrame({
|
|
109
|
+
cols: Math.max(20, process.stdout.columns || 80),
|
|
110
|
+
rows: process.stdout.rows || 24,
|
|
111
|
+
input: typedText(),
|
|
112
|
+
cursorIndex: typedText().length,
|
|
113
|
+
items: [],
|
|
114
|
+
});
|
|
115
|
+
const filas = [...marco.lines];
|
|
122
116
|
const pendientes = queuedCount();
|
|
123
|
-
const simbolo = chalk.bold.hex("#D97757")("❯");
|
|
124
|
-
const cuerpo = typing.length > 0
|
|
125
|
-
? `${chalk.white(colaVisible(typing, cols - 3))}${chalk.hex("#D97757")("▌")}`
|
|
126
|
-
: chalk.gray(colaVisible(PISTA_VACIA, cols - 2));
|
|
127
|
-
const filas = [chalk.gray("─".repeat(cols)), `${simbolo} ${cuerpo}`];
|
|
128
117
|
if (pendientes > 0) {
|
|
129
118
|
const plural = pendientes === 1 ? "mensaje en cola" : "mensajes en cola";
|
|
130
119
|
filas.push(chalk.gray(` ${pendientes} ${plural}; se enviará al terminar este turno`));
|
|
@@ -139,11 +128,48 @@ export class DynamicSpinner {
|
|
|
139
128
|
clearInterval(this.timer);
|
|
140
129
|
this.timer = null;
|
|
141
130
|
}
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
131
|
+
// Solo se va la linea del spinner. La BARRA se queda.
|
|
132
|
+
//
|
|
133
|
+
// Es lo que la hace fija de verdad: entre una herramienta y la siguiente el
|
|
134
|
+
// spinner se para y vuelve a arrancar, y si la barra se fuera con el, se
|
|
135
|
+
// veria desaparecer y reaparecer en cada llamada. Quien la retira es el
|
|
136
|
+
// prompt al abrirse (soltarPie), y ahi el relevo no se nota porque lo que
|
|
137
|
+
// dibuja justo despues es exactamente el mismo marco.
|
|
138
|
+
quitarSeccion("spinner");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* DEJA EL PIE PUESTO MIENTRAS CORRE ALGO
|
|
143
|
+
*
|
|
144
|
+
* El spinner solo vivia mientras hablaba el modelo. En cuanto empezaba una
|
|
145
|
+
* herramienta se paraba, y ahi se acababa lo unico fijo de la pantalla: durante
|
|
146
|
+
* un `run_command` de veinte segundos, o una lectura de un archivo enorme, no
|
|
147
|
+
* habia ni cronometro ni forma de saber que seguia vivo. Justo el rato en que
|
|
148
|
+
* uno se pregunta si se ha colgado.
|
|
149
|
+
*
|
|
150
|
+
* EL RETRASO NO ES UN CAPRICHO
|
|
151
|
+
*
|
|
152
|
+
* La mayoria de las herramientas tardan milisegundos. Pintar el pie y borrarlo
|
|
153
|
+
* en el mismo suspiro es un parpadeo en cada llamada, y encima el pie mueve
|
|
154
|
+
* lineas: veinte herramientas seguidas serian veinte sacudidas. Con 150 ms de
|
|
155
|
+
* espera, lo rapido no pinta nada y lo lento sale igual.
|
|
156
|
+
*
|
|
157
|
+
* El `finally` es obligatorio. Un spinner que se queda corriendo repinta el pie
|
|
158
|
+
* ocho veces por segundo encima de lo que venga despues -incluido el prompt-, y
|
|
159
|
+
* de ahi no se sale sin cerrar el programa.
|
|
160
|
+
*/
|
|
161
|
+
export async function conSpinner(etiqueta, tarea, retrasoMs = 150) {
|
|
162
|
+
let girando = null;
|
|
163
|
+
const aviso = setTimeout(() => {
|
|
164
|
+
girando = new DynamicSpinner(etiqueta);
|
|
165
|
+
girando.start();
|
|
166
|
+
}, retrasoMs);
|
|
167
|
+
try {
|
|
168
|
+
return await tarea();
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
clearTimeout(aviso);
|
|
172
|
+
if (girando)
|
|
173
|
+
girando.stop();
|
|
148
174
|
}
|
|
149
175
|
}
|
package/extension/manifest.json
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"manifest_version": 3,
|
|
3
|
-
"minimum_chrome_version": "102",
|
|
4
|
-
"name": "Chocolatito Code",
|
|
5
|
-
"version": "1.3.
|
|
6
|
-
"description": "Deja que Chocolatito Code trabaje dentro de tu Chrome, en segundo plano y con tus sesiones ya iniciadas.",
|
|
7
|
-
"permissions": [
|
|
8
|
-
"tabs",
|
|
9
|
-
"tabGroups",
|
|
10
|
-
"scripting",
|
|
11
|
-
"debugger",
|
|
12
|
-
"webNavigation",
|
|
13
|
-
"storage",
|
|
14
|
-
"downloads"
|
|
15
|
-
],
|
|
16
|
-
"host_permissions": [
|
|
17
|
-
"<all_urls>"
|
|
18
|
-
],
|
|
19
|
-
"icons": {
|
|
20
|
-
"16": "iconos/16.png",
|
|
21
|
-
"32": "iconos/32.png",
|
|
22
|
-
"48": "iconos/48.png",
|
|
23
|
-
"128": "iconos/128.png"
|
|
24
|
-
},
|
|
25
|
-
"background": {
|
|
26
|
-
"service_worker": "background.js"
|
|
27
|
-
},
|
|
28
|
-
"content_scripts": [
|
|
29
|
-
{
|
|
30
|
-
"matches": [
|
|
31
|
-
"<all_urls>"
|
|
32
|
-
],
|
|
33
|
-
"js": [
|
|
34
|
-
"content.js"
|
|
35
|
-
],
|
|
36
|
-
"run_at": "document_idle",
|
|
37
|
-
"all_frames": true
|
|
38
|
-
}
|
|
39
|
-
],
|
|
40
|
-
"action": {
|
|
41
|
-
"default_title": "Chocolatito Code",
|
|
42
|
-
"default_popup": "popup.html",
|
|
43
|
-
"default_icon": {
|
|
44
|
-
"16": "iconos/16.png",
|
|
45
|
-
"32": "iconos/32.png",
|
|
46
|
-
"48": "iconos/48.png"
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
"web_accessible_resources": [
|
|
50
|
-
{
|
|
51
|
-
"resources": [
|
|
52
|
-
"iconos/*",
|
|
53
|
-
"fuentes/*"
|
|
54
|
-
],
|
|
55
|
-
"matches": [
|
|
56
|
-
"<all_urls>"
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
]
|
|
60
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"minimum_chrome_version": "102",
|
|
4
|
+
"name": "Chocolatito Code",
|
|
5
|
+
"version": "1.3.2",
|
|
6
|
+
"description": "Deja que Chocolatito Code trabaje dentro de tu Chrome, en segundo plano y con tus sesiones ya iniciadas.",
|
|
7
|
+
"permissions": [
|
|
8
|
+
"tabs",
|
|
9
|
+
"tabGroups",
|
|
10
|
+
"scripting",
|
|
11
|
+
"debugger",
|
|
12
|
+
"webNavigation",
|
|
13
|
+
"storage",
|
|
14
|
+
"downloads"
|
|
15
|
+
],
|
|
16
|
+
"host_permissions": [
|
|
17
|
+
"<all_urls>"
|
|
18
|
+
],
|
|
19
|
+
"icons": {
|
|
20
|
+
"16": "iconos/16.png",
|
|
21
|
+
"32": "iconos/32.png",
|
|
22
|
+
"48": "iconos/48.png",
|
|
23
|
+
"128": "iconos/128.png"
|
|
24
|
+
},
|
|
25
|
+
"background": {
|
|
26
|
+
"service_worker": "background.js"
|
|
27
|
+
},
|
|
28
|
+
"content_scripts": [
|
|
29
|
+
{
|
|
30
|
+
"matches": [
|
|
31
|
+
"<all_urls>"
|
|
32
|
+
],
|
|
33
|
+
"js": [
|
|
34
|
+
"content.js"
|
|
35
|
+
],
|
|
36
|
+
"run_at": "document_idle",
|
|
37
|
+
"all_frames": true
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"action": {
|
|
41
|
+
"default_title": "Chocolatito Code",
|
|
42
|
+
"default_popup": "popup.html",
|
|
43
|
+
"default_icon": {
|
|
44
|
+
"16": "iconos/16.png",
|
|
45
|
+
"32": "iconos/32.png",
|
|
46
|
+
"48": "iconos/48.png"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"web_accessible_resources": [
|
|
50
|
+
{
|
|
51
|
+
"resources": [
|
|
52
|
+
"iconos/*",
|
|
53
|
+
"fuentes/*"
|
|
54
|
+
],
|
|
55
|
+
"matches": [
|
|
56
|
+
"<all_urls>"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|