primocode 10.4.1 → 10.4.3

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
@@ -1,4 +1,4 @@
1
- # PrimoCode v10.4.1
1
+ # PrimoCode v10.4.3
2
2
 
3
3
  Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
4
4
  Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
9
9
  está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
10
10
 
11
11
  ```
12
- ▐▛███▜▌ Primo Code v10.4.1
12
+ ▐▛███▜▌ Primo Code v10.4.3
13
13
  ▝▜█████▛▘ Bem-vindo de volta, Joel
14
14
  ▘▘ ▝▝ ~/primocode · /dir <pasta> muda
15
15
  ```
package/bin/primocode.js CHANGED
@@ -305,6 +305,8 @@ function sliderEffort(atual) {
305
305
  let anima = null;
306
306
 
307
307
  const pintar = (primeira) => {
308
+ // Na tela cheia o slider desenha no painel dela, acima da caixa.
309
+ if (rl && rl.cheia) return rl.painel(effort.desenharEffort(idx, fase));
308
310
  if (!primeira) process.stdout.write(`\x1b[${ALTURA}A\r`);
309
311
  process.stdout.write(effort.desenharEffort(idx, fase).split('\n').map((l) => '\x1b[2K' + l).join('\n') + '\n');
310
312
  };
@@ -466,7 +468,7 @@ function barraDeStatus() {
466
468
  : uso.pct >= 70 ? c.warn(`${uso.pct}% ctx`)
467
469
  : c.dim(`${uso.pct}% ctx`));
468
470
  }
469
- direita.push(state.trabalhando
471
+ direita.push(state.trabalhando && rl && rl.ocupado
470
472
  ? c.warn('trabalhando') + c.dim(' · ') + c.white('esc esc') + c.dim(' para')
471
473
  : c.white('tab') + c.dim(' modos ') + c.white('ctrl+p') + c.dim(' comandos'));
472
474
 
@@ -920,6 +922,7 @@ function abrirMarketplace() {
920
922
  const texto = marketplace.montar(estado, {
921
923
  colunas: process.stdout.columns || 80, altura: altura(),
922
924
  });
925
+ if (rl && rl.cheia) return rl.painel(texto);
923
926
  const linhas = texto.split('\n');
924
927
  // Redesenha POR CIMA do quadro anterior em vez de rolar a tela:
925
928
  // sem isso, cada seta empurraria a tela inteira para cima e o
@@ -2760,7 +2763,10 @@ async function main() {
2760
2763
  vazio: ui.convite(),
2761
2764
  telaCheia,
2762
2765
  lateral: barraLateral,
2766
+ logoAnimado: (quadro, trabalhando) => ui.logoAnimado(state.config.mascot, quadro, trabalhando),
2767
+ agora: () => (ui.agora.texto && Date.now() - ui.agora.desde < 10 * 60 * 1000 ? ui.agora.texto : ''),
2763
2768
  versao: VERSION,
2769
+ logo: ui.logoComMascote(state.config.mascot),
2764
2770
  pasta: () => {
2765
2771
  const dir = state.workDir || process.cwd();
2766
2772
  const ramo = ramoDoGit(dir);
@@ -2851,6 +2857,9 @@ async function main() {
2851
2857
  const text = queue.shift().trim();
2852
2858
  if (!text) continue;
2853
2859
  ui.titulo(`⠋ PrimoCode — ${text}`);
2860
+ // Comando (/effort, /help) não é trabalho do agente: a caixa e a
2861
+ // barra da direita só dizem "trabalhando" quando ele trabalha.
2862
+ if (rl.trabalhando) rl.trabalhando(!isSlash(text));
2854
2863
  try {
2855
2864
  if (ehBash(text)) await rodarBash(text);
2856
2865
  else if (isSlash(text)) await handleSlash(text);
package/lib/entrada.js CHANGED
@@ -41,6 +41,7 @@ const readline = require('readline');
41
41
  const { EventEmitter } = require('events');
42
42
  const { c, visibleWidth, truncate } = require('./ui.js');
43
43
  const tela = require('./tela.js');
44
+ const stripAnsiLocal = (s) => String(s).replace(/\x1b\[[0-9;]*m/g, '');
44
45
 
45
46
  // ── O desenho ────────────────────────────────────────────────────────────
46
47
 
@@ -323,7 +324,6 @@ class Entrada extends EventEmitter {
323
324
  e quem põe na tela é o próximo quadro. Suspensa (o painel do
324
325
  /effort desenhando por cima), passa direto. */
325
326
  if (eu.cheia) {
326
- if (eu.suspenso) return eu._stdoutOriginal.call(this, texto, ...resto);
327
327
  tela.acrescentar(eu.conversa, texto);
328
328
  eu._agendar();
329
329
  const fim = resto.find((r) => typeof r === 'function');
@@ -388,13 +388,23 @@ class Entrada extends EventEmitter {
388
388
  /* Enquanto outro desenho é dono da tela — o painel do /effort, uma tela
389
389
  do marketplace — a caixa sai da frente e larga o teclado. Sem isso, os
390
390
  dois desenhariam no mesmo lugar e as setas moveriam os dois. */
391
- suspender() { this.suspenso = true; if (this.cheia) this._sairDaTela(); else this._apagar(); }
391
+ /* Na tela cheia, suspender só LARGA O TECLADO: a tela continua no ar, e
392
+ quem suspendeu desenha pelo `painel()`. Sair da tela alternativa aqui
393
+ (o jeito antigo) jogava o /effort por cima do terminal velho, e a
394
+ volta deixava tudo bagunçado. */
395
+ suspender() { this.suspenso = true; if (this.cheia) this._agendar(); else this._apagar(); }
392
396
  retomar() {
393
397
  this.suspenso = false;
394
- if (this.cheia && this.ativo && !this.fechado) { this._entrarNaTela(); return this._pintar(); }
398
+ if (this.cheia && this.ativo && !this.fechado) { this.painelTexto = null; return this._pintar(); }
395
399
  if (this.ativo && !this.fechado) this._desenhar();
396
400
  }
397
401
 
402
+ /** Um comando com tela própria desenha aqui (na tela cheia). null fecha. */
403
+ painel(texto) {
404
+ this.painelTexto = texto == null ? null : String(texto).replace(/\n+$/, '');
405
+ this._pintar();
406
+ }
407
+
398
408
  // ── a tela cheia ─────────────────────────────────────────────────────
399
409
 
400
410
  /* A tela alternativa (a do vim): o que estava no terminal fica guardado e
@@ -412,7 +422,7 @@ class Entrada extends EventEmitter {
412
422
  this._colunasReais = process.stdout.columns || 80;
413
423
  Object.defineProperty(process.stdout, 'columns', {
414
424
  configurable: true,
415
- get() { return eu._naTela && !eu.suspenso ? tela.larguraDaConversa(eu._colunasReais) : eu._colunasReais; },
425
+ get() { return eu._naTela ? tela.larguraDaConversa(eu._colunasReais) : eu._colunasReais; },
416
426
  set(v) { eu._colunasReais = v; }, // o Node escreve aqui quando a janela muda
417
427
  });
418
428
  }
@@ -440,21 +450,44 @@ class Entrada extends EventEmitter {
440
450
  /** O estado do quadro, para a tela cheia (função pura em lib/tela.js). */
441
451
  _quadroCheio() {
442
452
  const conversa = this.conversa.some((l) => /\S/.test(l));
453
+ const trabalhando = !!(this.ocupado || this.spinnerTexto);
454
+ const quadro = Date.now() / 140;
455
+ /* O topo da barra da direita: o logo do PrimoCode (que anima enquanto
456
+ ele trabalha) e o ESTADO — trabalhando, há quanto tempo, no quê. */
457
+ const topo = this.op.logoAnimado ? this.op.logoAnimado(quadro, trabalhando) : [];
458
+ topo.push('');
459
+ if (trabalhando) {
460
+ const giro = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'][Math.floor(quadro) % 10];
461
+ topo.push(c.warn(giro + ' Trabalhando') + (this._desde ? c.dim(` · ${Math.floor((Date.now() - this._desde) / 1000)}s`) : ''));
462
+ const agora = this.op.agora ? this.op.agora() : '';
463
+ if (agora) topo.push(c.muted(agora));
464
+ if (this.spinnerTexto) topo.push(c.dim(stripAnsiLocal(this.spinnerTexto).replace(/^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s*/, '')));
465
+ } else {
466
+ topo.push(c.brand('● ') + c.muted('Pronto') + c.dim(' · esperando você'));
467
+ }
468
+ if (trabalhando && !this._animando) {
469
+ // Enquanto trabalha, a barra se redesenha sozinha (o logo anima
470
+ // e o relógio anda), mesmo que nada novo chegue na conversa.
471
+ this._animando = setInterval(() => this._pintar(), 140);
472
+ if (this._animando.unref) this._animando.unref();
473
+ } else if (!trabalhando && this._animando) { clearInterval(this._animando); this._animando = null; }
443
474
  return tela.quadro({
444
475
  colunas: this._colunasReais || 80,
445
476
  linhas: process.stdout.rows || 24,
446
477
  conversa: this.conversa,
447
478
  rolagem: this.rolagem,
448
- home: !this.mandou && !conversa,
479
+ home: !this.mandou && !conversa && !this.painelTexto,
449
480
  caixa: (colunas, centro) => montar({ ...this._estado(), colunas, centro, menuAcima: true }),
450
- lateral: { titulo: this.titulo, ...(this.op.lateral ? this.op.lateral() : {}) },
481
+ lateral: { titulo: this.titulo, topo, ...(this.op.lateral ? this.op.lateral() : {}) },
482
+ painel: this.painelTexto,
451
483
  versao: this.op.versao || '',
484
+ logo: this.op.logo,
452
485
  pasta: this.op.pasta ? this.op.pasta() : '',
453
486
  });
454
487
  }
455
488
 
456
489
  _pintar() {
457
- if (!this.cheia || this.suspenso || this.fechado || !this._naTela) return;
490
+ if (!this.cheia || this.fechado || !this._naTela) return;
458
491
  const q = this._quadroCheio();
459
492
  this.rolagem = Math.min(this.rolagem, q.rolagemMax);
460
493
  let out = '\x1b[?25l';
@@ -476,6 +509,7 @@ class Entrada extends EventEmitter {
476
509
  trabalhando(sim) {
477
510
  if (this.ocupado === !!sim) return;
478
511
  this.ocupado = !!sim;
512
+ this._desde = sim ? Date.now() : 0;
479
513
  if (this.desenhado) this._redesenhar();
480
514
  }
481
515
 
@@ -856,7 +890,12 @@ class Entrada extends EventEmitter {
856
890
  const W = this._colunasReais || 80;
857
891
  const mainW = W >= tela.LATERAL_MIN ? W - tela.LATERAL_W : W;
858
892
  this.rolagem = 0;
859
- tela.acrescentar(this.conversa, (this.conversa.some((l) => /\S/.test(l)) ? '\n' : '') + pedidoNaTela(visto, mainW - 1) + '\n');
893
+ /* Uma linha em branco antes e outra depois do pedido: cada
894
+ troca (pedido, resposta) é um bloco separado na tela. */
895
+ const temAlgo = this.conversa.some((l) => /\S/.test(l));
896
+ const ultima = this.conversa[this.conversa.length - 1] || '';
897
+ const antes = !temAlgo ? '' : /\S/.test(ultima) ? '\n\n' : '\n';
898
+ tela.acrescentar(this.conversa, antes + pedidoNaTela(visto, mainW - 1) + '\n\n');
860
899
  } else this.escreverCru(pedidoNaTela(visto, process.stdout.columns || 80) + '\n');
861
900
  if (this.historico[this.historico.length - 1] !== texto) this.historico.push(texto);
862
901
  }
package/lib/tela.js CHANGED
@@ -33,6 +33,7 @@ const RESET = '\x1b[0m';
33
33
  const FUNDO_LATERAL = '\x1b[48;5;234m';
34
34
  const LATERAL_W = 42; // a largura da barra da direita
35
35
  const LATERAL_MIN = 110; // abaixo disso a barra some: a conversa precisa do espaço
36
+ const RECUO = 3; // quanto a resposta entra em relação ao painel do pedido
36
37
 
37
38
  /* ── A conversa como texto ───────────────────────────────────────────────
38
39
  O que o programa escreve (console.log, o fluxo da resposta) chega aqui
@@ -115,6 +116,8 @@ function caber(s, w, fundo) {
115
116
  function lateral(lat, alto) {
116
117
  const w = LATERAL_W - 4; // 2 de margem de cada lado
117
118
  const corpo = [''];
119
+ // O topo: o logo (animado enquanto trabalha) e o estado do Primo agora.
120
+ if (lat.topo && lat.topo.length) corpo.push(...lat.topo, '');
118
121
  for (const l of quebrarComCor(c.bold(c.white(lat.titulo || 'Nova sessão')), w)) corpo.push(l);
119
122
  for (const s of (lat.secoes || [])) {
120
123
  corpo.push('');
@@ -153,7 +156,7 @@ function quadro(e) {
153
156
 
154
157
  if (e.home) {
155
158
  const caixa = e.caixa(W, true);
156
- const lg = W >= 50 ? logo() : [c.bold(c.white('primocode'))];
159
+ const lg = W >= 60 && e.logo ? e.logo : W >= 50 ? logo() : [c.bold(c.white('primocode'))];
157
160
  /* O painel fica PARADO no lugar dele: o menu, quando abre, cobre o
158
161
  que está em cima (o logo), em vez de empurrar tudo para cima. */
159
162
  const menu = Math.max(0, caixa.linhas.findIndex((l) => l.includes('┃')) - (/┃/.test(caixa.linhas[0]) ? 0 : 0));
@@ -178,6 +181,10 @@ function quadro(e) {
178
181
  };
179
182
  }
180
183
 
184
+ /* O PAINEL de um comando que desenha a própria tela (o slider do
185
+ /effort, o marketplace): fica no pé da conversa, logo acima da caixa,
186
+ por cima do que estiver ali — a tela cheia nunca sai do ar. */
187
+ const painel = (e.painel ? String(e.painel).split('\n') : []);
181
188
  const comLateral = W >= LATERAL_MIN;
182
189
  const mainW = comLateral ? W - LATERAL_W : W;
183
190
  /* Uma coluna de margem dos dois lados, para a conversa e para a caixa: o
@@ -186,10 +193,18 @@ function quadro(e) {
186
193
  const caixa = { ...caixa0, linhas: caixa0.linhas.map((l) => ' ' + l), cursorColuna: caixa0.cursorColuna + 1 };
187
194
  const altoConversa = Math.max(1, H - caixa.linhas.length);
188
195
 
189
- // A conversa, quebrada na largura da coluna, com 2 colunas de margem.
196
+ /* A conversa, quebrada na largura da coluna. Os PAINÉIS (o pedido que se
197
+ mandou) ficam na borda da caixa; o resto — a resposta, as ações, o
198
+ fecho "▣" — entra RECUADO, como no OpenCode. Sem o recuo, tudo saía
199
+ colado na borda e um bloco não se separava do outro. */
190
200
  const visiveis = [];
191
- for (const l of (e.conversa || [])) visiveis.push(...quebrarComCor(l, mainW - 2));
201
+ for (const l of (e.conversa || [])) {
202
+ const painel = /^(\x1b\[[0-9;]*m)*┃/.test(l);
203
+ const margem = painel ? ' ' : ' '.repeat(RECUO + 1);
204
+ for (const p of quebrarComCor(l, painel ? mainW - 2 : mainW - 2 - RECUO)) visiveis.push(margem + p);
205
+ }
192
206
  while (visiveis.length && !temTexto(visiveis[visiveis.length - 1])) visiveis.pop();
207
+ if (visiveis.length) visiveis.push(''); // um respiro entre a conversa e a caixa
193
208
  const rolagemMax = Math.max(0, visiveis.length - altoConversa);
194
209
  const rolagem = Math.max(0, Math.min(e.rolagem | 0, rolagemMax));
195
210
  const fim = visiveis.length - rolagem;
@@ -197,7 +212,12 @@ function quadro(e) {
197
212
  // A conversa encosta EMBAIXO, logo acima da caixa — como num chat.
198
213
  const conversa = [];
199
214
  for (let i = janela.length; i < altoConversa; i++) conversa.push('');
200
- for (const l of janela) conversa.push(' ' + l);
215
+ for (const l of janela) conversa.push(l);
216
+ if (painel.length) {
217
+ const inicio = Math.max(0, conversa.length - painel.length - 1);
218
+ conversa.splice(inicio, conversa.length - inicio, '', ...painel.map((l) => ' ' + l));
219
+ conversa.length = altoConversa;
220
+ }
201
221
  if (rolagem > 0) {
202
222
  conversa[0] = ' ' + c.dim(`↑ ${rolagem} linha${rolagem > 1 ? 's' : ''} abaixo · PgDn ou a roda do mouse para voltar`);
203
223
  }
@@ -217,7 +237,7 @@ function temTexto(l) { return visibleWidth(l) > 0 && /\S/.test(l.replace(/\x1b\[
217
237
  /** A largura que o texto da conversa tem, numa tela de `colunas`. */
218
238
  function larguraDaConversa(colunas) {
219
239
  const W = Math.max(30, colunas || 80);
220
- return (W >= LATERAL_MIN ? W - LATERAL_W : W) - 2;
240
+ return (W >= LATERAL_MIN ? W - LATERAL_W : W) - 2 - RECUO;
221
241
  }
222
242
 
223
- module.exports = { larguraDaConversa, quadro, acrescentar, quebrarComCor, lateral, caber, LATERAL_W, LATERAL_MIN };
243
+ module.exports = { RECUO, larguraDaConversa, quadro, acrescentar, quebrarComCor, lateral, caber, LATERAL_W, LATERAL_MIN };
package/lib/ui.js CHANGED
@@ -358,6 +358,77 @@ function logo() {
358
358
  }
359
359
  const LOGO_W = visibleWidth(logo()[0]);
360
360
 
361
+ /* ── O mascote GRANDE ─────────────────────────────────────────────────
362
+ O desenho de sempre tem 3 linhas — pequeno ao lado do logo de 4. Para a
363
+ home ele cresce 2×: cada quarto de bloco (▘ ▝ ▖ ▗) vira um bloco cheio, e
364
+ cada bloco cheio vira 2×2. Ficam 6 linhas × 18 colunas, com a cor de cada
365
+ pedaço preservada — o mesmo desenho, não um desenho novo. */
366
+ const QUARTOS = {
367
+ ' ': [0, 0, 0, 0], '█': [1, 1, 1, 1], '▀': [1, 1, 0, 0], '▄': [0, 0, 1, 1],
368
+ '▌': [1, 0, 1, 0], '▐': [0, 1, 0, 1], '▘': [1, 0, 0, 0], '▝': [0, 1, 0, 0],
369
+ '▖': [0, 0, 1, 0], '▗': [0, 0, 0, 1], '▛': [1, 1, 1, 0], '▜': [1, 1, 0, 1],
370
+ '▙': [1, 0, 1, 1], '▟': [0, 1, 1, 1], '▚': [1, 0, 0, 1], '▞': [0, 1, 1, 0],
371
+ };
372
+ function mascoteGrande(style) {
373
+ const saida = [];
374
+ for (const linha of mascot(style)) {
375
+ // [caractere, cor] de cada coluna da linha colorida
376
+ const cels = [];
377
+ let cor = '';
378
+ for (const m of linha.matchAll(/(\x1b\[[0-9;]*m)|([^\x1b])/gu)) {
379
+ if (m[1]) { if (m[1] !== PALETTE.reset) cor = m[1]; continue; }
380
+ cels.push([m[2], cor]);
381
+ }
382
+ const cima = [], baixo = [];
383
+ for (const [ch, cr] of cels) {
384
+ const q = QUARTOS[ch] || (ch === ' ' ? QUARTOS[' '] : QUARTOS['█']);
385
+ const bloco = (b) => (b ? `${cr}█${PALETTE.reset}` : ' ');
386
+ cima.push(bloco(q[0]) + bloco(q[1]));
387
+ baixo.push(bloco(q[2]) + bloco(q[3]));
388
+ }
389
+ saida.push(cima.join(''), baixo.join(''));
390
+ }
391
+ while (saida.length && !saida[saida.length - 1].trim()) saida.pop(); // a linha vazia de baixo
392
+ return saida;
393
+ }
394
+
395
+ /** O logo da home: o mascote grande e, centrado na altura dele, o letreiro. */
396
+ function logoComMascote(style) {
397
+ const m = mascoteGrande(style);
398
+ const l = logo();
399
+ const desce = Math.floor((m.length - l.length) / 2);
400
+ const linhas = m.map((linha, i) => linha + ' ' + (l[i - desce] || ''));
401
+ // Todas com a MESMA largura: a home centraliza linha por linha, e uma
402
+ // linha mais curta (a dos pés) sairia deslocada do resto do desenho.
403
+ const larg = Math.max(...linhas.map(visibleWidth));
404
+ return linhas.map((x) => x + ' '.repeat(larg - visibleWidth(x)));
405
+ }
406
+
407
+ /* ── O logo animado da barra da direita ──────────────────────────────────
408
+ Enquanto o Primo trabalha, o mascote pulsa (a cor viva respira entre três
409
+ tons de ciano), os pés andam, e um brilho corre pelo nome "PrimoCode".
410
+ Parado, fica quieto: animação sem trabalho é ruído. `quadro` é um número
411
+ que cresce com o tempo (o chamador passa Date.now()/120). */
412
+ const PULSO = ['\x1b[38;5;45m', '\x1b[38;5;51m', '\x1b[38;5;87m', '\x1b[38;5;51m'];
413
+ function logoAnimado(style, quadro, trabalhando) {
414
+ const f = Math.floor(quadro || 0);
415
+ const m = MASCOTS[style] || MASCOTS.primo;
416
+ // Os pés andam: a linha dos pés troca de passo a cada quadro.
417
+ const linhas = m.linhas.map((l) => (trabalhando && f % 2 && l === PES ? ' ▝▘ ▘▝ ' : l));
418
+ let art = linhas.map((l, i) => shade(l, m, m.mascara && m.mascara[i]));
419
+ if (trabalhando) art = art.map((l) => l.split(PALETTE.brand).join(PULSO[f % PULSO.length]));
420
+ const nome = 'PrimoCode';
421
+ const brilho = f % (nome.length + 8);
422
+ let texto = '';
423
+ [...nome].forEach((ch, i) => {
424
+ const base = i < 5 ? '\x1b[38;5;255m' : '\x1b[38;5;45m';
425
+ const aceso = trabalhando && Math.abs(i - brilho) <= 0;
426
+ const perto = trabalhando && Math.abs(i - brilho) === 1;
427
+ texto += (aceso ? '\x1b[1m\x1b[38;5;231m' : perto ? '\x1b[1m\x1b[38;5;159m' : '\x1b[1m' + base) + ch + PALETTE.reset;
428
+ });
429
+ return [art[0], art[1] + ' ' + texto, art[2]];
430
+ }
431
+
361
432
  /** Centraliza uma linha (com cor) em `cols` colunas. */
362
433
  function centro(s, cols) {
363
434
  return ' '.repeat(Math.max(0, Math.floor((cols - visibleWidth(s)) / 2))) + s;
@@ -459,6 +530,8 @@ function titulo(texto) {
459
530
  try { escrever(`\x1b]0;${limpo}\x07`); } catch { /* terminal fechado */ }
460
531
  }
461
532
 
533
+ const agora = { texto: '', desde: 0 };
534
+
462
535
  // O rótulo de cada tool na tela — o que a pessoa lê no lugar do nome interno.
463
536
  const ROTULOS = {
464
537
  read_file:'Read', write_file:'Write', edit_file:'Edit', multi_edit:'MultiEdit',
@@ -500,6 +573,9 @@ function actionHeader(name, args) {
500
573
  // virava "[object Object]" na tela.
501
574
  if (argStr && typeof argStr === 'object') argStr = JSON.stringify(argStr).slice(0, 60);
502
575
  argStr = String(argStr || '').replace(/\s+/g, ' ').trim();
576
+ // O que ele está fazendo AGORA — a barra da direita da tela cheia mostra.
577
+ agora.texto = label + (argStr ? ' · ' + argStr.slice(0, 60) : '');
578
+ agora.desde = Date.now();
503
579
  // Uma tool sem argumento nenhum caía no JSON.stringify e desenhava "{}"
504
580
  // ao lado do nome — ruído que não diz nada.
505
581
  if (argStr === '{}' || argStr === 'null' || argStr === 'undefined') argStr = '';
@@ -633,4 +709,4 @@ function friendlyError(raw) {
633
709
 
634
710
 
635
711
  module.exports = {
636
- titulo, c, logo, letreiro, convite, EXEMPLOS, mascot, MASCOTES, resolverMascote, mascoteInfo, galeriaMascotes, reacao, welcomeBox, Spinner, PENSANDO, actionHeader, friendlyError, visibleWidth, stripAnsi, truncate, padRight };
712
+ titulo, agora, c, logo, logoComMascote, mascoteGrande, logoAnimado, letreiro, convite, EXEMPLOS, mascot, MASCOTES, resolverMascote, mascoteInfo, galeriaMascotes, reacao, welcomeBox, Spinner, PENSANDO, actionHeader, friendlyError, visibleWidth, stripAnsi, truncate, padRight };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "10.4.1",
3
+ "version": "10.4.3",
4
4
  "description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). 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": {