primocode 7.14.1 → 7.15.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/bin/primocode.js CHANGED
@@ -19,7 +19,7 @@ const { escolherModo } = require('../lib/modo.js');
19
19
  const api = require('../lib/api');
20
20
  const tools = require('../lib/tools');
21
21
  const cfg = require('../lib/config');
22
- const { runAct } = require('../lib/act');
22
+ const { runAct, interromper } = require('../lib/act');
23
23
  const pasta = require('../lib/pasta');
24
24
  const catalogo = require('../lib/catalogo');
25
25
  const streaming = require('../lib/streaming');
@@ -779,7 +779,25 @@ async function main() {
779
779
  // Limpa resto da linha (fantasma antigo) e desenha o novo, voltando o cursor.
780
780
  process.stdout.write('\x1b[0K' + (fantasma ? `\x1b[2m${fantasma}\x1b[0m\x1b[${fantasma.length}D` : ''));
781
781
  };
782
+ // ── Esc Esc: parar o agente no meio do trabalho ──
783
+ // Um Esc avisa; o segundo (em até 2s) manda parar. Dois Escs tão
784
+ // rápidos que chegam colados num chunk só viram UMA tecla com
785
+ // sequence "\x1b\x1b" — vale como os dois.
786
+ let ultimoEsc = 0;
782
787
  process.stdin.on('keypress', (ch, key) => {
788
+ if (key && (key.name === 'escape' || key.sequence === '\x1b\x1b')) {
789
+ if (!busy) { ultimoEsc = 0; return; } // sem agente rodando, Esc é só Esc
790
+ const agora = Date.now();
791
+ if (key.sequence === '\x1b\x1b' || agora - ultimoEsc < 2000) {
792
+ ultimoEsc = 0;
793
+ interromper();
794
+ process.stdout.write('\n' + c.warn(' ⏹ parando o agente…') + '\n');
795
+ } else {
796
+ ultimoEsc = agora;
797
+ process.stdout.write('\n' + c.dim(' Esc de novo para parar o agente') + '\n');
798
+ }
799
+ return;
800
+ }
783
801
  // Seta → no fim da linha aceita a sugestão inteira.
784
802
  if (key && key.name === 'right' && fantasma && rl && rl.cursor === rl.line.length) {
785
803
  const resto = fantasma;
package/lib/act.js CHANGED
@@ -23,6 +23,14 @@ const painel = require('./streaming');
23
23
  const MAX_ITERATIONS = 15;
24
24
  const MAX_PROFUNDIDADE = 2; // principal + 1 nível de subagente
25
25
 
26
+ // ── Interrupção (Esc Esc) ─────────────────────────────────────────────────
27
+ // O usuário aperta Esc duas vezes e o agente PARA — entre uma rodada e outra
28
+ // e entre uma tool e a seguinte (uma tool já em execução termina; o que ela
29
+ // fez, ficou feito). A flag é do módulo de propósito: parar o principal para
30
+ // os subagentes junto.
31
+ let PARAR = false;
32
+ function interromper() { PARAR = true; }
33
+
26
34
  function ident(profundidade) {
27
35
  return ' '.repeat(profundidade);
28
36
  }
@@ -190,7 +198,10 @@ async function runAct(userPrompt, opts) {
190
198
  // instrução de gente.
191
199
  if (!subagent) { try { painel.instrucao(userPrompt); } catch {} }
192
200
 
193
- for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
201
+ // Pedido novo zera o pedido de parada anterior.
202
+ if (!subagent) PARAR = false;
203
+
204
+ for (let iteration = 0; iteration < MAX_ITERATIONS && !PARAR; iteration++) {
194
205
  const sp = new Spinner(subagent ? 'subagente trabalhando' : 'Primo Code pensando').start();
195
206
 
196
207
  const toolCalls = [];
@@ -251,6 +262,7 @@ async function runAct(userPrompt, opts) {
251
262
 
252
263
  let finished = null;
253
264
  for (const tc of toolCalls) {
265
+ if (PARAR) break;
254
266
  if (tc.name === 'finish') {
255
267
  // Encerrar com falha pendente é como o agente dizia "pronto!"
256
268
  // sem ter mudado nada. Devolvemos a tarefa para ele.
@@ -306,6 +318,11 @@ async function runAct(userPrompt, opts) {
306
318
  }
307
319
  }
308
320
 
321
+ if (PARAR && !finalText) {
322
+ finalText = 'Parado por você (Esc Esc). O que já foi feito, ficou feito.';
323
+ if (!subagent) console.log('\n' + c.warn('⏹ ' + finalText));
324
+ }
325
+
309
326
  // Nunca terminar em silêncio. Quando o modelo devolvia resposta vazia sem
310
327
  // chamar tool, o usuário via a tela parar e mais nada — pior que erro, porque
311
328
  // não dá nem para saber que acabou. Medido: um pedido de navegador morria assim
@@ -353,4 +370,4 @@ async function runSubagent(args, ctx) {
353
370
  return resumo;
354
371
  }
355
372
 
356
- module.exports = { runAct };
373
+ module.exports = { runAct, interromper };
package/lib/streaming.js CHANGED
@@ -274,10 +274,11 @@ function capturar(entrada) {
274
274
 
275
275
  // O mascote do CLI (▐▛███▜▌ …) redesenhado em SVG, célula a célula, na mesma
276
276
  // paleta: bloco cheio na cor viva, contorno na apagada.
277
+ // Olhos e pés têm classe própria: a página anima (piscada e passinhos) por CSS.
277
278
  const MASCOTE_SVG = `<svg viewBox="0 0 9 3.6" aria-hidden="true">
278
279
  <g fill="#0891b2"><rect x="1" y="0" width="1" height="1"/><rect x="7" y="0" width="1" height="1"/><rect x="0" y="1" width="1" height="1" opacity=".7"/><rect x="8" y="1" width="1" height="1" opacity=".7"/><rect x="1" y="1" width="1" height="1"/><rect x="7" y="1" width="1" height="1"/></g>
279
- <g fill="#22d3ee"><rect x="2" y="0" width="5" height="1" rx=".12"/><rect x="2" y="1" width="5" height="1"/><rect x="1.9" y="2.2" width=".9" height=".5" rx=".1"/><rect x="3.4" y="2.2" width=".9" height=".5" rx=".1"/><rect x="4.9" y="2.2" width=".9" height=".5" rx=".1"/><rect x="6.4" y="2.2" width=".9" height=".5" rx=".1"/></g>
280
- <g fill="#0b1220"><rect x="3" y=".35" width=".8" height=".5" rx=".1"/><rect x="5.2" y=".35" width=".8" height=".5" rx=".1"/><rect x="3.9" y="1.25" width="1.2" height=".28" rx=".14"/></g>
280
+ <g fill="#22d3ee"><rect x="2" y="0" width="5" height="1" rx=".12"/><rect x="2" y="1" width="5" height="1"/><rect class="pe pe1" x="1.9" y="2.2" width=".9" height=".5" rx=".1"/><rect class="pe pe2" x="3.4" y="2.2" width=".9" height=".5" rx=".1"/><rect class="pe pe1" x="4.9" y="2.2" width=".9" height=".5" rx=".1"/><rect class="pe pe2" x="6.4" y="2.2" width=".9" height=".5" rx=".1"/></g>
281
+ <g fill="#0b1220"><rect class="olho" x="3" y=".35" width=".8" height=".5" rx=".1"/><rect class="olho" x="5.2" y=".35" width=".8" height=".5" rx=".1"/><rect x="3.9" y="1.25" width="1.2" height=".28" rx=".14"/></g>
281
282
  </svg>`;
282
283
 
283
284
  // A tela do painel, na identidade do app — a MESMA paleta do design system do
@@ -307,9 +308,16 @@ const PAGINA = `<!doctype html>
307
308
 
308
309
  /* ── Cabeçalho: mascote inclinado + nome, como no desenho ── */
309
310
  #topo { position: relative; width: min(1120px, 100%); padding: 44px 0 26px; text-align: center; }
310
- #topo .mascote { position: absolute; left: 4px; top: 26px; width: 100px; height: 42px; transform: rotate(-32deg); filter: drop-shadow(0 8px 20px rgba(34,211,238,.4)); animation: flutua 3.2s ease-in-out infinite alternate; }
311
- @keyframes flutua { from { translate: 0 0 } to { translate: 0 5px } }
312
- #topo .mascote svg { width: 100%; height: 100%; display: block; }
311
+ #topo .mascote { position: absolute; left: 4px; top: 24px; width: 104px; height: 44px; filter: drop-shadow(0 8px 20px rgba(34,211,238,.4)); animation: flutua 2.8s ease-in-out infinite; }
312
+ @keyframes flutua { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(6px) } }
313
+ #topo .mascote svg { width: 100%; height: 100%; display: block; overflow: visible; }
314
+ /* vivo: pisca os olhos de vez em quando e dá passinhos com os pés */
315
+ .mascote .olho { transform-box: fill-box; transform-origin: center; animation: piscaOlho 3.6s infinite; }
316
+ @keyframes piscaOlho { 0%, 89%, 100% { transform: scaleY(1) } 93%, 95% { transform: scaleY(.12) } }
317
+ .mascote .pe { transform-box: fill-box; transform-origin: center; }
318
+ .mascote .pe1 { animation: passinho 1.4s ease-in-out infinite; }
319
+ .mascote .pe2 { animation: passinho 1.4s ease-in-out infinite; animation-delay: .7s; }
320
+ @keyframes passinho { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-.14px) } }
313
321
  #topo h1 { font-size: clamp(34px, 5.6vw, 54px); font-weight: 650; letter-spacing: .4px; color: #f1f5f9; text-shadow: 0 4px 28px rgba(34,211,238,.22); }
314
322
  #topo h1 span { color: #22d3ee; }
315
323
  #topo .vivo { position: absolute; right: 2px; top: 48px; display: inline-flex; align-items: center; gap: 8px; color: #94a3b8; font-size: 12.5px; background: rgba(15,23,42,.85); border: 1px solid #1e293b; border-radius: 999px; padding: 6px 14px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "7.14.1",
3
+ "version": "7.15.0",
4
4
  "description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
5
5
  "main": "bin/primocode.js",
6
6
  "bin": {