chocolatito-code 1.3.1 → 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/index.js CHANGED
@@ -13,6 +13,8 @@ import { buildSystemPrompt } from "./prompts/systemPrompt.js";
13
13
  import { AgentLoop } from "./agent/loop.js";
14
14
  import { codigoDeSalida, EXIT_OK, EXIT_FALLO, EXIT_USO, EXIT_LICENCIA } from "./agent/result.js";
15
15
  import { showBanner, animateStartupBanner, showHelp, showEffortMenu } from "./ui/banner.js";
16
+ import { fijarMotor } from "./ui/motorActual.js";
17
+ import { elegirDeLista } from "./ui/selector.js";
16
18
  import { askInteractivePrompt, SLASH_COMMANDS } from "./ui/prompt.js";
17
19
  import { changeDir } from "./tools/changeDir.js";
18
20
  import { gitDiff, gitCommit } from "./tools/gitTools.js";
@@ -233,6 +235,9 @@ ${nodeViejo}
233
235
  const sessionManager = new SessionManager();
234
236
  const sessionId = `session_${Date.now()}`;
235
237
  let currentModel = DEFAULT_ESPECTRO;
238
+ // La barra de estado lo lee de aqui. Fijarlo al arrancar y no solo al
239
+ // cambiarlo: si no, hasta el primer /effort la barra no dice nada.
240
+ fijarMotor(currentModel.name);
236
241
  let isPlanMode = false;
237
242
  let systemPrompt = buildSystemPrompt(context, currentModel, skillManager, memoryManager, isPlanMode);
238
243
  const agent = new AgentLoop(apiKey, getEngineBaseUrl(), currentModel, systemPrompt);
@@ -429,6 +434,7 @@ ${sessionData}
429
434
  const restauracion = prepararRestauracion(loaded, { cwd: agent.currentCwd, modelId: currentModel.id });
430
435
  agent.currentCwd = restauracion.cwd;
431
436
  currentModel = ESPECTRO_MODELS[restauracion.modelId] || currentModel;
437
+ fijarMotor(currentModel.name);
432
438
  const refreshedContext = await getProjectContext(agent.currentCwd);
433
439
  systemPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
434
440
  agent.messages = [{ role: "system", content: systemPrompt }, ...restauracion.conversacion];
@@ -580,20 +586,43 @@ Directivas de ejecución:
580
586
  }
581
587
  if (trimmed.startsWith("/effort") || trimmed.startsWith("/espectro") || trimmed === "/model") {
582
588
  const parts = trimmed.split(/\s+/);
583
- const targetId = parts[1]?.trim();
589
+ let targetId = parts[1]?.trim();
590
+ // Sin argumento se ABRE UN MENU, no se imprime una lista.
591
+ //
592
+ // Antes se volcaban las cuatro opciones con console.log y una frase al
593
+ // final que decia "selecciona con /effort 2.0". En pantalla se veia una
594
+ // lista, la gente pulsaba las flechas -que es lo unico razonable- y no
595
+ // pasaba nada.
584
596
  if (!targetId || !ESPECTRO_MODELS[targetId]) {
585
- showEffortMenu(currentModel.id);
586
- }
587
- else {
588
- currentModel = ESPECTRO_MODELS[targetId];
589
- const refreshedContext = await getProjectContext(agent.currentCwd);
590
- const updatedPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
591
- agent.setEspectroModel(currentModel, updatedPrompt);
592
- console.log(chalk.green(`\n✔ Nivel actualizado a `) +
593
- chalk.bold.hex("#A855F7")(currentModel.name) +
594
- chalk.gray(` (${currentModel.effortLevel})`) +
595
- `\n ${chalk.italic.gray(currentModel.description)}\n`);
597
+ const opciones = Object.values(ESPECTRO_MODELS).map((m) => ({
598
+ id: m.id,
599
+ titulo: `${m.name} ${m.id === currentModel.id ? "· activo" : ""}`.trimEnd(),
600
+ detalle: `Esfuerzo ${m.effortLevel} — ${m.description}`,
601
+ }));
602
+ const indiceActual = Object.values(ESPECTRO_MODELS).findIndex((m) => m.id === currentModel.id);
603
+ const elegido = await elegirDeLista("POTENCIA DEL MOTOR — CHOCOLATITO CODE", opciones, Math.max(0, indiceActual));
604
+ // Sin terminal el menu no puede abrirse: ahi se cae a la lista de
605
+ // siempre, que al menos dice como elegir escribiendolo.
606
+ if (elegido === null) {
607
+ if (!process.stdin.isTTY)
608
+ showEffortMenu(currentModel.id);
609
+ continue;
610
+ }
611
+ targetId = elegido;
596
612
  }
613
+ currentModel = ESPECTRO_MODELS[targetId];
614
+ // Sin esto la barra de abajo sigue anunciando el motor anterior.
615
+ fijarMotor(currentModel.name);
616
+ const refreshedContext = await getProjectContext(agent.currentCwd);
617
+ const updatedPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
618
+ agent.setEspectroModel(currentModel, updatedPrompt);
619
+ console.log(chalk.green(`
620
+ ✔ Nivel actualizado a `) +
621
+ chalk.bold.hex("#A855F7")(currentModel.name) +
622
+ chalk.gray(` (${currentModel.effortLevel})`) +
623
+ `
624
+ ${chalk.italic.gray(currentModel.description)}
625
+ `);
597
626
  continue;
598
627
  }
599
628
  if (trimmed === "/clear") {
package/dist/ui/marco.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import chalk from "chalk";
2
- import { describeMode, getMode } from "./modes.js";
2
+ import { describeMode, getMode, TRIANGULOS } from "./modes.js";
3
+ import { motorActual } from "./motorActual.js";
3
4
  /**
4
5
  * EL MARCO DE LA BARRA, EN UN SOLO SITIO
5
6
  *
@@ -121,27 +122,58 @@ export function buildPromptFrame(opts) {
121
122
  // ancha que el terminal. En una consola de 80 columnas el pie media 87 y se
122
123
  // partia en dos filas desde el primer dibujado.
123
124
  const modeLook = describeMode(getMode());
124
- const hintPlain = "? /help para atajos · shift+tab modo · esc cancelar";
125
- const modePlain = `⇥ modo ${modeLook.label}`;
126
- const enginePlain = Espectro 1.0 (Ágil)";
127
- const hint = chalk.gray(hintPlain);
125
+ // 3. La linea de estado, con POSICIONES FIJAS.
126
+ //
127
+ // Antes iban los atajos a la izquierda y el modo y el motor apelotonados a la
128
+ // derecha, y encima la linea se reordenaba sola en cuatro variantes segun el
129
+ // ancho: al estrecharse la ventana, el modo saltaba de sitio. El usuario lo
130
+ // pidio asi: "el boton del modo debe ir a la izquierda... y el modelo a la
131
+ // derecha, pero deben ser fijos siempre y nunca cambiar".
132
+ //
133
+ // Ahora el modo esta SIEMPRE pegado a la izquierda y el motor SIEMPRE pegado a
134
+ // la derecha. Lo unico que aparece y desaparece es la pista del medio, que es
135
+ // lo prescindible: quien ya se sabe los atajos no la necesita, y quien no, la
136
+ // tiene en /help.
137
+ const modePlain = `${TRIANGULOS} modo ${modeLook.label}`;
138
+ // Estaba escrito a mano y por eso no cambiaba nunca. Vacio si nadie lo ha
139
+ // fijado: mejor un hueco que un nombre que no es el que se esta usando.
140
+ const enginePlain = motorActual();
141
+ const hintPlain = "shift+tab cambia · esc cancela · ? /help";
128
142
  const modeTag = chalk.hex(modeLook.color)(modePlain);
129
143
  const engine = chalk.gray(enginePlain);
144
+ // Dos columnas de respiro. Los triangulos son un glifo de anchura ambigua y
145
+ // hay terminales que lo pintan doble: si la linea llegara justa al ancho, el
146
+ // terminal la partiria en dos filas y el repintado del marco se descuadraria
147
+ // entero. Ese fallo ya costo una barra huerfana por pulsacion.
148
+ const util = Math.max(0, cols - 2);
130
149
  let footerLine;
131
- if (hintPlain.length + 1 + modePlain.length + 1 + enginePlain.length <= cols) {
132
- const padLen = cols - hintPlain.length - modePlain.length - 1 - enginePlain.length;
133
- footerLine = `${hint}${" ".repeat(padLen)}${modeTag} ${engine}`;
150
+ const huecoConPista = util - modePlain.length - 1 - hintPlain.length - enginePlain.length;
151
+ const hueco = util - modePlain.length - enginePlain.length;
152
+ if (!enginePlain) {
153
+ // Todavia nadie ha dicho que motor hay. Se enseña el modo y ya, pero SIN
154
+ // saltarse el recorte: "⏵⏵ modo acepta ediciones" son 24 columnas y en una
155
+ // consola de 20 se partiria en dos filas, que es lo que descuadra el marco.
156
+ const conPista = util - modePlain.length - 1 - hintPlain.length;
157
+ if (modePlain.length > util) {
158
+ footerLine = chalk.hex(modeLook.color)(truncatePlain(modePlain, util));
159
+ }
160
+ else {
161
+ footerLine = conPista >= 0 ? `${modeTag} ${chalk.gray(hintPlain)}` : modeTag;
162
+ }
134
163
  }
135
- else if (modePlain.length + 1 + enginePlain.length <= cols) {
136
- // Los atajos son opcionales; el modo activo no, que es lo que decide si te
137
- // van a preguntar antes de tocar tus archivos.
138
- footerLine = `${" ".repeat(cols - modePlain.length - 1 - enginePlain.length)}${modeTag} ${engine}`;
164
+ else if (huecoConPista >= 1) {
165
+ footerLine = `${modeTag} ${chalk.gray(hintPlain)}${" ".repeat(huecoConPista)}${engine}`;
166
+ }
167
+ else if (hueco >= 1) {
168
+ footerLine = `${modeTag}${" ".repeat(hueco)}${engine}`;
139
169
  }
140
- else if (modePlain.length <= cols) {
141
- footerLine = `${" ".repeat(cols - modePlain.length)}${modeTag}`;
170
+ else if (modePlain.length <= util) {
171
+ // Si hay que sacrificar uno, se va el motor: el modo es lo que decide si te
172
+ // van a preguntar antes de tocar tus archivos.
173
+ footerLine = modeTag;
142
174
  }
143
175
  else {
144
- footerLine = chalk.hex(modeLook.color)(truncatePlain(modePlain, cols));
176
+ footerLine = chalk.hex(modeLook.color)(truncatePlain(modePlain, util));
145
177
  }
146
178
  const lines = [bar, inputLine, ...dropdownLines, bar, footerLine];
147
179
  // 4. Donde queda el cursor, en filas fisicas. Antes se hacia cursorCol =
@@ -22,7 +22,15 @@ export interface ModeLook {
22
22
  color: string;
23
23
  }
24
24
  export declare function describeMode(mode?: InteractiveMode): ModeLook;
25
- /** Etiqueta de una linea para el pie del prompt y para el spinner. */
25
+ /**
26
+ * El simbolo del modo.
27
+ *
28
+ * Dos triangulos, no la flecha de tabulacion. El "⇥" de antes es el simbolo del
29
+ * tabulador: dice COMO se cambia de modo, no QUE modo hay puesto. Y como ademas
30
+ * el atajo es shift+tab, se leia como si el modo fuera un atajo mas.
31
+ */
32
+ export declare const TRIANGULOS = "\u23F5\u23F5";
33
+ /** Etiqueta de una linea del modo activo. */
26
34
  export declare function modeBadge(mode?: InteractiveMode): string;
27
35
  /** Aviso completo, para cuando el usuario acaba de cambiar de modo. */
28
36
  export declare function modeAnnouncement(mode?: InteractiveMode): string;
package/dist/ui/modes.js CHANGED
@@ -43,15 +43,23 @@ const LOOKS = {
43
43
  export function describeMode(mode = current) {
44
44
  return LOOKS[mode];
45
45
  }
46
- /** Etiqueta de una linea para el pie del prompt y para el spinner. */
46
+ /**
47
+ * El simbolo del modo.
48
+ *
49
+ * Dos triangulos, no la flecha de tabulacion. El "⇥" de antes es el simbolo del
50
+ * tabulador: dice COMO se cambia de modo, no QUE modo hay puesto. Y como ademas
51
+ * el atajo es shift+tab, se leia como si el modo fuera un atajo mas.
52
+ */
53
+ export const TRIANGULOS = "⏵⏵";
54
+ /** Etiqueta de una linea del modo activo. */
47
55
  export function modeBadge(mode = current) {
48
56
  if (mode === "default")
49
57
  return "";
50
58
  const look = LOOKS[mode];
51
- return chalk.hex(look.color)(`⇥ modo ${look.label}`);
59
+ return chalk.hex(look.color)(`${TRIANGULOS} modo ${look.label}`);
52
60
  }
53
61
  /** Aviso completo, para cuando el usuario acaba de cambiar de modo. */
54
62
  export function modeAnnouncement(mode = current) {
55
63
  const look = LOOKS[mode];
56
- return `${chalk.bold.hex(look.color)(`⇥ modo ${look.label}`)} ${chalk.gray(`· ${look.hint}`)} ${chalk.gray("· shift+tab para cambiar")}`;
64
+ return `${chalk.bold.hex(look.color)(`${TRIANGULOS} modo ${look.label}`)} ${chalk.gray(`· ${look.hint}`)} ${chalk.gray("· shift+tab para cambiar")}`;
57
65
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * QUE MOTOR ESTA PUESTO
3
+ *
4
+ * La linea de estado del marco lo llevaba escrito a mano:
5
+ *
6
+ * const enginePlain = "Espectro 1.0 (Ágil)";
7
+ *
8
+ * Asi que decia siempre lo mismo pasara lo que pasara. Cambiabas de motor con
9
+ * /effort, el agente empezaba a usar otro de verdad, y la barra seguia
10
+ * anunciando el primero. Lo vio el usuario: "al poner el modelo, la parte de
11
+ * abajo que es fija no se actualiza con el modelo que se pone".
12
+ *
13
+ * Un indicador que miente es peor que no tener indicador: te hace tomar
14
+ * decisiones -esperar mas, gastar mas- creyendo que estas en otro sitio.
15
+ *
16
+ * Vive aparte y no en el marco por lo mismo que el modo (ver modes.ts): lo
17
+ * escribe quien cambia de motor y lo lee quien pinta, y ninguno de los dos tiene
18
+ * por que conocer al otro.
19
+ */
20
+ /** Lo pone index.ts al arrancar y cada vez que se cambia de motor. */
21
+ export declare function fijarMotor(nuevo: string): void;
22
+ /**
23
+ * El nombre del motor activo.
24
+ *
25
+ * Vacio hasta que alguien lo fije. Quien pinta decide que hacer con eso: mejor
26
+ * no enseñar nada que enseñar un nombre inventado.
27
+ */
28
+ export declare function motorActual(): string;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * QUE MOTOR ESTA PUESTO
3
+ *
4
+ * La linea de estado del marco lo llevaba escrito a mano:
5
+ *
6
+ * const enginePlain = "Espectro 1.0 (Ágil)";
7
+ *
8
+ * Asi que decia siempre lo mismo pasara lo que pasara. Cambiabas de motor con
9
+ * /effort, el agente empezaba a usar otro de verdad, y la barra seguia
10
+ * anunciando el primero. Lo vio el usuario: "al poner el modelo, la parte de
11
+ * abajo que es fija no se actualiza con el modelo que se pone".
12
+ *
13
+ * Un indicador que miente es peor que no tener indicador: te hace tomar
14
+ * decisiones -esperar mas, gastar mas- creyendo que estas en otro sitio.
15
+ *
16
+ * Vive aparte y no en el marco por lo mismo que el modo (ver modes.ts): lo
17
+ * escribe quien cambia de motor y lo lee quien pinta, y ninguno de los dos tiene
18
+ * por que conocer al otro.
19
+ */
20
+ let nombre = "";
21
+ /** Lo pone index.ts al arrancar y cada vez que se cambia de motor. */
22
+ export function fijarMotor(nuevo) {
23
+ if (nuevo && nuevo.trim())
24
+ nombre = nuevo.trim();
25
+ }
26
+ /**
27
+ * El nombre del motor activo.
28
+ *
29
+ * Vacio hasta que alguien lo fije. Quien pinta decide que hacer con eso: mejor
30
+ * no enseñar nada que enseñar un nombre inventado.
31
+ */
32
+ export function motorActual() {
33
+ return nombre;
34
+ }
package/dist/ui/prompt.js CHANGED
@@ -462,10 +462,28 @@ export async function askInteractivePrompt(promptSymbol = "❯", cwd = process.c
462
462
  render();
463
463
  return;
464
464
  }
465
- if (items.length > 0 && (inputBuffer === "/" || getAtMention().active)) {
466
- if (applySelectedDropdownItem()) {
465
+ // EL DESPLEGABLE MANDA SOBRE LO TECLEADO
466
+ //
467
+ // Antes esto solo entraba con el buffer EXACTAMENTE en "/". Si habias
468
+ // escrito "/e" y el desplegable te estaba señalando "/effort", el Enter
469
+ // enviaba "/e" tal cual: un comando que no existe, mientras en pantalla
470
+ // ponia otra cosa. Lo conto el usuario asi: "me recomienda effort pero
471
+ // al dar enter se manda como /e".
472
+ const mencionActiva = getAtMention().active;
473
+ const escribiendoComando = inputBuffer.startsWith("/") && !inputBuffer.includes(" ");
474
+ const soloLaBarra = inputBuffer === "/";
475
+ if (items.length > 0 && (escribiendoComando || mencionActiva)) {
476
+ const elegido = items[selectedIndex];
477
+ const yaEstaEntero = Boolean(elegido) && inputBuffer === elegido.insertText;
478
+ if (!yaEstaEntero && applySelectedDropdownItem()) {
467
479
  render();
468
- return;
480
+ // Se completa Y SE ENVIA, que es lo que uno espera al ver el comando
481
+ // señalado y pulsar Enter. Dos excepciones que solo completan:
482
+ // - una mencion de archivo casi siempre lleva mas texto detras;
483
+ // - con solo "/" escrito no has elegido nada todavia, y enviar ahi
484
+ // ejecutaria el primero de la lista sin querer.
485
+ if (mencionActiva || soloLaBarra)
486
+ return;
469
487
  }
470
488
  }
471
489
  scheduleSubmit();
@@ -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
+ }
@@ -1,7 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import process from "node:process";
3
3
  import { typedText, queuedCount } from "./typeAhead.js";
4
- import { modeBadge } from "./modes.js";
5
4
  import { fijarSecciones, quitarSeccion } from "./pieFijo.js";
6
5
  import { buildPromptFrame } from "./marco.js";
7
6
  /**
@@ -32,9 +31,6 @@ const VERBS = [
32
31
  "Tejiendo",
33
32
  "Cocinando",
34
33
  ];
35
- function sinColores(texto) {
36
- return texto.replace(/\x1b\[[0-9;]*m/g, "");
37
- }
38
34
  export class DynamicSpinner {
39
35
  timer = null;
40
36
  glyphIdx = 0;
@@ -81,24 +77,17 @@ export class DynamicSpinner {
81
77
  * salian todas como "Rumiando…", indistinguibles de una peticion normal.
82
78
  */
83
79
  lineaDelSpinner(elapsedSec) {
84
- const cols = Math.max(20, process.stdout.columns || 80);
85
80
  const glyph = chalk.hex("#D97757")(GLYPHS[this.glyphIdx]);
86
81
  const verbo = this.prefix || VERBS[this.verbIdx];
87
82
  const verb = chalk.italic.hex("#D97757")(`${verbo}…`);
88
83
  // El contador va hacia abajo: son los tokens que ESTAN LLEGANDO del modelo.
89
84
  const tokenStr = this.tokenCount > 0 ? ` · ↓ ${(this.tokenCount / 1000).toFixed(1)}k tokens` : "";
90
85
  const cola = chalk.gray(`(${elapsedSec}s${tokenStr} · esc para pausar)`);
91
- const izquierda = `${glyph} ${verb} ${cola}`;
92
- const anchoIzq = 1 + 1 + verbo.length + 1 + 1 + `(${elapsedSec}s${tokenStr} · esc para pausar)`.length;
93
- // La chapa del modo se pega a la derecha, y solo si sobra sitio: antes se
94
- // concatenaba siempre y en una consola estrecha empujaba la linea a una
95
- // segunda fila, que es justo lo que descuadra el repintado.
96
- const badge = modeBadge();
97
- if (!badge)
98
- return izquierda;
99
- const anchoBadge = sinColores(badge).length;
100
- const hueco = cols - anchoIzq - anchoBadge;
101
- 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}`;
102
91
  }
103
92
  /**
104
93
  * LA BARRA. La MISMA que se ve al escribir una orden, no una parecida.
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "minimum_chrome_version": "102",
4
4
  "name": "Chocolatito Code",
5
- "version": "1.3.1",
5
+ "version": "1.3.2",
6
6
  "description": "Deja que Chocolatito Code trabaje dentro de tu Chrome, en segundo plano y con tus sesiones ya iniciadas.",
7
7
  "permissions": [
8
8
  "tabs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chocolatito-code",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Agente autónomo de programación para la terminal, con control real del ordenador y del navegador. Desarrollado por Chocolatito.",
5
5
  "type": "module",
6
6
  "bin": {