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 +41 -12
- package/dist/ui/marco.js +47 -15
- 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.js +21 -3
- package/dist/ui/selector.d.ts +37 -0
- package/dist/ui/selector.js +81 -0
- package/dist/ui/spinner.js +5 -16
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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 (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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 <=
|
|
141
|
-
|
|
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,
|
|
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 =
|
package/dist/ui/modes.d.ts
CHANGED
|
@@ -22,7 +22,15 @@ export interface ModeLook {
|
|
|
22
22
|
color: string;
|
|
23
23
|
}
|
|
24
24
|
export declare function describeMode(mode?: InteractiveMode): ModeLook;
|
|
25
|
-
/**
|
|
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
|
-
/**
|
|
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)(
|
|
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)(
|
|
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
|
-
|
|
466
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/ui/spinner.js
CHANGED
|
@@ -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
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
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.
|
package/extension/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"minimum_chrome_version": "102",
|
|
4
4
|
"name": "Chocolatito Code",
|
|
5
|
-
"version": "1.3.
|
|
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