chocolatito-code 1.6.10 → 1.6.12

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 CHANGED
@@ -262,7 +262,9 @@ mételo en un kit y tu equipo entero trabaja así.
262
262
  Viene uno hecho, y no es de programación: `kits/diseno` trae lotes de imágenes con
263
263
  ImageMagick, las medidas de todas las redes (con las zonas seguras de historias, que es
264
264
  lo que nadie recuerda), y variantes de una misma pieza desde un CSV —cuarenta
265
- certificados con su nombre, sin una sola errata—.
265
+ certificados con su nombre, sin una sola errata—. Y carruseles: partir una panorámica en
266
+ diapositivas que deslizan como una sola imagen, numeradas con ceros para que se suban en
267
+ orden (`slide-1, slide-10, slide-2` es el error que aparece con el carrusel ya publicado).
266
268
 
267
269
  ```bash
268
270
  /kit instalar ./kits/diseno
@@ -56,3 +56,27 @@ export declare function liberarHuecos(): void;
56
56
  * que hacer nada: es lo unico que se arregla solo con esperar.
57
57
  */
58
58
  export declare function mensajeDeEspera(segundos: number): string;
59
+ /**
60
+ * Espera a que se libere el hueco, en vez de devolverle la pared al usuario.
61
+ *
62
+ * DE DONDE SALE
63
+ *
64
+ * La misma sesion, un paso mas alla. Ya no se gastaba una peticion por intento
65
+ * -eso lo arreglo lo de arriba-, pero el usuario seguia atrapado:
66
+ *
67
+ * > sigue
68
+ * ⏸ Faltan 144s.
69
+ * > sigoe
70
+ * ⏸ Faltan 133s.
71
+ *
72
+ * Tecleando contra una pared que el programa sabia medir. Si sabemos que faltan
73
+ * 133 segundos, esperarlos es trabajo del programa, no del que paga. Se avisa,
74
+ * se cuenta hacia atras y se sigue solo; con Esc se deja.
75
+ *
76
+ * Vuelve `true` si ya hay hueco y `false` si el usuario corto.
77
+ */
78
+ export declare function esperarAlHueco(opciones: {
79
+ signal?: AbortSignal;
80
+ alPasarUnSegundo?: (quedan: number) => void;
81
+ dormir?: (ms: number) => Promise<void>;
82
+ }): Promise<boolean>;
@@ -79,3 +79,37 @@ export function mensajeDeEspera(segundos) {
79
79
  "No llamo al motor porque la respuesta seria la misma y cada intento puede alargar la espera. " +
80
80
  "No hace falta que hagas nada: el hueco se libera solo.");
81
81
  }
82
+ /**
83
+ * Espera a que se libere el hueco, en vez de devolverle la pared al usuario.
84
+ *
85
+ * DE DONDE SALE
86
+ *
87
+ * La misma sesion, un paso mas alla. Ya no se gastaba una peticion por intento
88
+ * -eso lo arreglo lo de arriba-, pero el usuario seguia atrapado:
89
+ *
90
+ * > sigue
91
+ * ⏸ Faltan 144s.
92
+ * > sigoe
93
+ * ⏸ Faltan 133s.
94
+ *
95
+ * Tecleando contra una pared que el programa sabia medir. Si sabemos que faltan
96
+ * 133 segundos, esperarlos es trabajo del programa, no del que paga. Se avisa,
97
+ * se cuenta hacia atras y se sigue solo; con Esc se deja.
98
+ *
99
+ * Vuelve `true` si ya hay hueco y `false` si el usuario corto.
100
+ */
101
+ export async function esperarAlHueco(opciones) {
102
+ const { signal, alPasarUnSegundo } = opciones;
103
+ const dormir = opciones.dormir || ((ms) => new Promise((r) => setTimeout(r, ms)));
104
+ // Se relee en cada vuelta en vez de contar desde un numero fijo: si algo
105
+ // libera los huecos antes -otra respuesta del motor-, se sale en el acto.
106
+ let quedan = segundosDeEspera();
107
+ while (quedan > 0) {
108
+ if (signal?.aborted)
109
+ return false;
110
+ alPasarUnSegundo?.(quedan);
111
+ await dormir(1000);
112
+ quedan = segundosDeEspera();
113
+ }
114
+ return !signal?.aborted;
115
+ }
@@ -8,7 +8,7 @@ import { CONTEXT_WINDOW, MAX_OUTPUT_TOKENS, COMPACT_THRESHOLD_TOKENS, } from "..
8
8
  import { withRetry, isContextOverflow, esDemasiadasEnCurso } from "./retry.js";
9
9
  import { canRunInParallel } from "../tools/safety.js";
10
10
  import { createEngineClient, explicarErrorDelMotor, motivoDeParadaDelError, } from "../config/engine.js";
11
- import { anotarHuecosOcupados, liberarHuecos, mensajeDeEspera, segundosDeEspera } from "./huecos.js";
11
+ import { anotarHuecosOcupados, esperarAlHueco, liberarHuecos, mensajeDeEspera, segundosDeEspera, } from "./huecos.js";
12
12
  import { MarkdownStream } from "../ui/markdownStream.js";
13
13
  import { checkQuota, recordUsage, mensajeDeCorte, quotaBadge } from "../config/quota.js";
14
14
  import { ReasoningStream } from "../ui/reasoningStream.js";
@@ -461,9 +461,43 @@ export class AgentLoop {
461
461
  // llamada es justo la que puede refrescar la reserva y alargar la espera.
462
462
  const espera = segundosDeEspera();
463
463
  if (espera > 0) {
464
+ // ESPERAR ES TRABAJO DEL PROGRAMA, NO DEL QUE PAGA
465
+ //
466
+ // Antes esto devolvia el aviso y cortaba el turno. El usuario escribia
467
+ // "sigue", se llevaba "faltan 144s", escribia otra vez y se llevaba
468
+ // "faltan 133s": tecleando contra una pared que el programa sabia medir.
469
+ // Si sabemos cuanto falta, se espera y se sigue solo. Con Esc se deja.
464
470
  const aviso = mensajeDeEspera(espera);
465
- salida.linea(chalk.yellow(`\n⏸ ${aviso}\n`));
466
- return parada("ocupado", aviso);
471
+ // SOLO SE ESPERA SI HAY ALGUIEN DELANTE.
472
+ //
473
+ // Esperar tres minutos es una pausa cuando se ve la cuenta atras y se
474
+ // puede cortar con Esc. Sin terminal -CI, `chocolatito "haz X"`, una
475
+ // tuberia- es un cuelgue mudo de tres minutos que nadie puede parar, y
476
+ // ademas ahi no hay nadie a quien ahorrarle el teclear. Se dice y se
477
+ // corta, como antes.
478
+ if (!process.stdin.isTTY) {
479
+ salida.linea(chalk.yellow(`\n⏸ ${aviso}\n`));
480
+ return parada("ocupado", aviso);
481
+ }
482
+ salida.linea(chalk.yellow(`\n⏸ ${aviso}`));
483
+ salida.linea(chalk.gray(" Espero y sigo solo cuando se libere. Esc para dejarlo.\n"));
484
+ const vigilante = new InterruptWatcher();
485
+ const senal = vigilante.start();
486
+ const spinnerEspera = new DynamicSpinner();
487
+ spinnerEspera.start();
488
+ const hayHueco = await esperarAlHueco({
489
+ signal: senal,
490
+ alPasarUnSegundo: (quedan) => {
491
+ spinnerEspera.setPrefijo(`Esperando hueco de la licencia (quedan ${quedan}s)`);
492
+ },
493
+ });
494
+ spinnerEspera.stop();
495
+ vigilante.stop();
496
+ if (!hayHueco) {
497
+ salida.linea(chalk.yellow("\n⏸ Interrumpido. Dime como seguir."));
498
+ return parada("interrumpido");
499
+ }
500
+ salida.linea(chalk.green(" ✔ Hueco libre. Sigo.\n"));
467
501
  }
468
502
  // Cuota ANTES de gastar. Si no queda margen, se dice y no se llama al motor:
469
503
  // avisar despues de haber gastado no sirve de nada.
@@ -742,6 +776,18 @@ export class AgentLoop {
742
776
  // porque la conexion estaba bien.
743
777
  if (!llegoElFinal) {
744
778
  watcher.stop();
779
+ // SE CIERRA LA PETICION MUERTA, Y ESTO NO ES LIMPIEZA: ES EL HUECO.
780
+ //
781
+ // El cuerpo se corto, pero por lo que respecta al proxy la peticion
782
+ // sigue en curso y sigue ocupando uno de los cuatro huecos de la
783
+ // licencia hasta que ALGO la cierre. Por eso el mensaje del proxy dice
784
+ // "si acabas de interrumpir una, el hueco se libera solo en 206s": son
785
+ // tres minutos y medio de servicio perdido por una conexion que ya
786
+ // estaba muerta de este lado. Abortando aqui se suelta en el momento.
787
+ try {
788
+ stream?.controller?.abort();
789
+ }
790
+ catch { }
745
791
  // Lo que llego se guarda, pero MARCADO. Sin la marca, en el turno
746
792
  // siguiente el modelo lee su propia frase a medias como algo que decidio
747
793
  // dejar asi, y responde a un texto que nadie escribio.
@@ -7,6 +7,12 @@ export declare class DynamicSpinner {
7
7
  private prefix;
8
8
  tokenCount: number;
9
9
  constructor(prefix?: string);
10
+ /**
11
+ * Cambia el texto mientras gira. Hace falta para las esperas con cuenta
12
+ * atras: el prefijo se fijaba al arrancar y el numero de segundos que faltan
13
+ * cambia cada vuelta.
14
+ */
15
+ setPrefijo(prefijo: string): void;
10
16
  start(initialPrefix?: string): void;
11
17
  private tick;
12
18
  /**
@@ -42,6 +42,14 @@ export class DynamicSpinner {
42
42
  constructor(prefix = "") {
43
43
  this.prefix = prefix;
44
44
  }
45
+ /**
46
+ * Cambia el texto mientras gira. Hace falta para las esperas con cuenta
47
+ * atras: el prefijo se fijaba al arrancar y el numero de segundos que faltan
48
+ * cambia cada vuelta.
49
+ */
50
+ setPrefijo(prefijo) {
51
+ this.prefix = prefijo;
52
+ }
45
53
  start(initialPrefix) {
46
54
  if (this.isRunning)
47
55
  return;
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "minimum_chrome_version": "102",
4
4
  "name": "Chocolatito Code",
5
- "version": "1.6.10",
5
+ "version": "1.6.12",
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",
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: carruseles-y-flyers
3
+ description: Usar para carruseles de Instagram o LinkedIn y para flyers comerciales — partir una panorámica en diapositivas, sacar el mismo flyer en varias versiones (precio, sucursal, fecha, idioma), numerar para que suban en orden y preparar el envío al cliente.
4
+ ---
5
+
6
+ # Carruseles y flyers
7
+
8
+ El trabajo repetido de una pieza comercial: **el diseñador hace una buena, y aquí
9
+ salen las otras treinta y nueve y todos los formatos.** Nada de esto decide la
10
+ composición ni elige la tipografía — eso es suyo.
11
+
12
+ ## Medidas
13
+
14
+ | | |
15
+ | --- | --- |
16
+ | Carrusel Instagram | 1080 × 1350 (4:5, ocupa más pantalla que el cuadrado) |
17
+ | Carrusel LinkedIn | 1080 × 1350, se sube como **PDF** de varias páginas |
18
+ | Máximo de diapositivas | 20 en Instagram; 10 es lo que la gente aguanta |
19
+ | Flyer digital | 1080 × 1350 feed · 1080 × 1920 historia |
20
+ | Flyer imprenta A5 | 148 × 210 mm a 300 ppp = 1748 × 2480 px, con 3 mm de sangre |
21
+
22
+ ## Partir una panorámica en diapositivas, sin costuras
23
+
24
+ El carrusel que se desliza como una sola imagen. La panorámica tiene que medir
25
+ `1080 × n` de ancho exacto: para 5 diapositivas, **5400 × 1350**.
26
+
27
+ Con ImageMagick, de una vez:
28
+
29
+ ```bash
30
+ magick pano.png -crop 1080x1350 +repage slide-%02d.png
31
+ ```
32
+
33
+ Sin ImageMagick, con ffmpeg (comprobado: salen las 5 a 1080×1350 clavadas):
34
+
35
+ ```bash
36
+ for i in 0 1 2 3 4; do
37
+ ffmpeg -y -i pano.png -vf "crop=1080:1350:$((i*1080)):0" "slide-$(printf %02d $((i+1))).png"
38
+ done
39
+ ```
40
+
41
+ **Antes de cortar, comprueba el ancho.** Si la panorámica no es múltiplo exacto de
42
+ 1080, la última diapositiva sale corta y se nota: reescala o recorta primero, y
43
+ dilo.
44
+
45
+ ## Los ceros de la numeración no son estética
46
+
47
+ `slide-1, slide-2 … slide-10` se sube en el orden `1, 10, 2, 3…` porque el
48
+ selector de archivos ordena por texto. **Siempre `%02d`:** `slide-01`,
49
+ `slide-02`. Este es el error que aparece con el carrusel ya publicado.
50
+
51
+ ## El mismo flyer en varias versiones
52
+
53
+ Precio distinto, sucursal distinta, fecha distinta, idioma distinto. Es el mismo
54
+ método que `variantes-de-pieza`: una tabla CSV con lo que cambia, una plantilla
55
+ SVG con `{{PRECIO}}`, `{{SUCURSAL}}`, y una pieza por fila.
56
+
57
+ Dos cosas que rompen el lote comercial en particular:
58
+
59
+ - **El precio largo.** `$9` y `$1.299.990` no ocupan lo mismo. Mira el más largo
60
+ del CSV antes de lanzar, y ajusta el tamaño de letra a ese.
61
+ - **La fecha.** Escríbela en el CSV tal y como tiene que verse. No la formatees
62
+ tú: "5 de marzo", "05/03" y "Marzo 5" no son intercambiables en una pieza.
63
+
64
+ ## Colores que se apagan al subir
65
+
66
+ Instagram convierte todo a **sRGB**. Si la pieza sale en Adobe RGB o P3, los
67
+ colores se apagan al publicar y el cliente lo nota en el rojo de su logo:
68
+
69
+ ```bash
70
+ magick pieza.png -colorspace sRGB -quality 90 pieza-feed.jpg
71
+ ```
72
+
73
+ Para imprenta es al revés: **no toques el color y no recomprimas**. Si el cliente
74
+ pide CMYK, avisa de que una conversión sin el perfil de la imprenta cambia el
75
+ tono, y que el archivo bueno sale del programa de maquetación, no de aquí.
76
+
77
+ ## Zona segura del carrusel
78
+
79
+ Los puntitos del paso de página y la flecha de "siguiente" se comen los bordes:
80
+ deja **60 px libres abajo** y unos 40 a los lados. La primera diapositiva es la
81
+ que decide si alguien desliza, y la última es la llamada a la acción — si el CSV
82
+ no las trae aparte, pregunta en vez de repetir la plantilla.
83
+
84
+ ## Revisar y entregar
85
+
86
+ Todo de un vistazo antes de mandarlo:
87
+
88
+ ```bash
89
+ magick montage slide-*.png -tile 5x -geometry 300x300+8+8 revision.jpg
90
+ ```
91
+
92
+ Carrusel de LinkedIn (va en PDF, una página por diapositiva):
93
+
94
+ ```bash
95
+ magick slide-*.png -quality 95 carrusel.pdf
96
+ ```
97
+
98
+ Y el envío: una carpeta por formato, nombres sin acentos ni espacios, y un ZIP.
99
+ Al terminar di cuántos archivos salieron y cuánto pesa el ZIP — si pasa de 25 MB
100
+ no entra en un correo, y más vale saberlo antes de mandarlo.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chocolatito-code",
3
- "version": "1.6.10",
3
+ "version": "1.6.12",
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": {