primocode 7.14.0 → 7.14.1

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
@@ -758,6 +758,38 @@ async function main() {
758
758
  let closed = false;
759
759
  let saidBye = false;
760
760
 
761
+ // ── Sugestão fantasma: o autopreenchimento VISÍVEL enquanto digita ──
762
+ // "/str" mostra "eaming" esmaecido na frente do cursor, como o Claude
763
+ // Code. Seta → (ou Tab) aceita; Enter com o pedaço já executa. Desenhada
764
+ // DEPOIS de o readline processar a tecla (setImmediate), por cima do que
765
+ // sobrou da linha (\\x1b[0K limpa), e o cursor volta para o lugar.
766
+ if (process.stdin.isTTY && process.stdout.isTTY) {
767
+ let fantasma = '';
768
+ const desenharFantasma = () => {
769
+ if (busy || pendingAsk || !rl) return;
770
+ const linha = rl.line;
771
+ const noFim = rl.cursor === linha.length;
772
+ fantasma = '';
773
+ if (noFim && linha.startsWith('/') && !/\s/.test(linha) && linha.length > 1) {
774
+ const baixa = linha.toLowerCase();
775
+ const hit = SLASH_COMANDOS.map(([n]) => n).find((n) => n.startsWith(baixa) && n.length > linha.length);
776
+ if (hit) fantasma = hit.slice(linha.length);
777
+ }
778
+ if (!noFim) return; // cursor no meio: não mexe no que vem depois dele
779
+ // Limpa resto da linha (fantasma antigo) e desenha o novo, voltando o cursor.
780
+ process.stdout.write('\x1b[0K' + (fantasma ? `\x1b[2m${fantasma}\x1b[0m\x1b[${fantasma.length}D` : ''));
781
+ };
782
+ process.stdin.on('keypress', (ch, key) => {
783
+ // Seta → no fim da linha aceita a sugestão inteira.
784
+ if (key && key.name === 'right' && fantasma && rl && rl.cursor === rl.line.length) {
785
+ const resto = fantasma;
786
+ fantasma = '';
787
+ rl.write(resto);
788
+ }
789
+ setImmediate(desenharFantasma);
790
+ });
791
+ }
792
+
761
793
  function sayBye() {
762
794
  if (saidBye) return;
763
795
  saidBye = true;
package/lib/streaming.js CHANGED
@@ -50,6 +50,8 @@ const state = {
50
50
  liveAt: 0, // quando o último frame de vídeo foi capturado
51
51
  viewerAt: 0, // último sinal de que alguém está com a página aberta
52
52
  liveTimer: null,
53
+ liveBusy: false, // uma captura em voo por vez — sem isso cada poll da página
54
+ // abria OUTRA cadeia de captura e elas se multiplicavam
53
55
  };
54
56
 
55
57
  const LIVE = () => path.join(DIR, 'live.png');
@@ -69,25 +71,30 @@ function viuAgora() {
69
71
  }
70
72
 
71
73
  function agendarVideo(ms) {
72
- if (!state.on || state.liveTimer) return;
74
+ if (!state.on || state.liveTimer || state.liveBusy) return;
73
75
  state.liveTimer = setTimeout(cicloVideo, ms);
74
76
  state.liveTimer.unref();
75
77
  }
76
78
 
77
79
  function cicloVideo() {
78
80
  state.liveTimer = null;
79
- if (!state.on) return;
81
+ if (!state.on || state.liveBusy) return;
80
82
  if (Date.now() - state.viewerAt > 8000) return; // ninguém olhando — o próximo /state religa
81
83
 
82
84
  const cmd = desktop.screenshotCommand(LIVE_TMP());
83
85
  if (!cmd) { if (!state.erroFoto) state.erroFoto = 'screenshot indisponível nesta máquina'; return; }
84
86
 
85
87
  const t0 = Date.now();
88
+ state.liveBusy = true;
86
89
  exec(cmd, { timeout: 8000 }, (err) => {
90
+ state.liveBusy = false;
87
91
  const dur = Date.now() - t0;
88
92
  if (!err) {
89
93
  try {
90
- fs.renameSync(LIVE_TMP(), LIVE());
94
+ // Rename é atômico; no Windows ele pode falhar se o navegador
95
+ // estiver lendo o arquivo NAQUELE instante — aí copia por cima.
96
+ try { fs.renameSync(LIVE_TMP(), LIVE()); }
97
+ catch { fs.copyFileSync(LIVE_TMP(), LIVE()); try { fs.unlinkSync(LIVE_TMP()); } catch {} }
91
98
  state.liveSeq++;
92
99
  state.liveAt = Date.now();
93
100
  state.erroFoto = null;
@@ -424,25 +431,35 @@ let ultimoEstado = '';
424
431
 
425
432
  function esc(s) { return String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
426
433
 
427
- // ── o vídeo: troca o quadro assim que um novo existe, sem piscar ──
428
- let quadroNaTela = -1;
434
+ // ── o vídeo: busca o quadro direto, desde o primeiro segundo ──
435
+ // Não espera o /state confirmar nada: enquanto não há seq, tenta com
436
+ // timestamp (um 404 só significa "primeiro quadro ainda não saiu" — tenta de
437
+ // novo). Assim a tela aparece assim que o painel abre, sem depender de o
438
+ // agente ter feito qualquer coisa.
439
+ let quadroNaTela = '';
440
+ let buscandoQuadro = false;
429
441
  function modoVideo() { return abaSel === VIVO && fotoSel == null; }
430
442
  function videoTick() {
431
- if (modoVideo() && dados.liveSeq > 0 && dados.liveSeq !== quadroNaTela) {
432
- const seq = dados.liveSeq;
433
- const pre = new Image(); // pré-carrega fora da tela:
434
- pre.onload = () => { // o quadro só entra inteiro
435
- if (!modoVideo()) return;
436
- const img = document.getElementById('frame');
437
- img.src = pre.src; img.style.display = 'block';
438
- document.getElementById('aviso').style.display = 'none';
439
- document.getElementById('badge').style.display = 'flex';
440
- document.getElementById('carimbo').style.display = 'none';
441
- quadroNaTela = seq;
442
- };
443
- pre.src = '/live?' + seq;
443
+ if (modoVideo() && !buscandoQuadro) {
444
+ const marca = dados.liveSeq > 0 ? 's' + dados.liveSeq : 't' + Date.now();
445
+ if (marca !== quadroNaTela) {
446
+ buscandoQuadro = true;
447
+ const pre = new Image(); // pré-carrega fora da tela:
448
+ pre.onload = () => { // o quadro só entra inteiro
449
+ buscandoQuadro = false;
450
+ if (!modoVideo()) return;
451
+ const img = document.getElementById('frame');
452
+ img.src = pre.src; img.style.display = 'block';
453
+ document.getElementById('aviso').style.display = 'none';
454
+ document.getElementById('badge').style.display = 'flex';
455
+ document.getElementById('carimbo').style.display = 'none';
456
+ quadroNaTela = marca;
457
+ };
458
+ pre.onerror = () => { buscandoQuadro = false; };
459
+ pre.src = '/live?' + marca;
460
+ }
444
461
  }
445
- setTimeout(videoTick, 280);
462
+ setTimeout(videoTick, 300);
446
463
  }
447
464
 
448
465
  // ── a instrução autopreenchendo, letra por letra ──
@@ -616,6 +633,10 @@ function start() {
616
633
  state.server = server;
617
634
  state.on = true;
618
635
  state.url = `http://127.0.0.1:${server.address().port}`;
636
+ // O vídeo começa JÁ: quando o navegador terminar de abrir, o
637
+ // primeiro quadro está no ar — nada de tela vazia esperando ação.
638
+ state.viewerAt = Date.now();
639
+ agendarVideo(50);
619
640
  resolve(state.url);
620
641
  });
621
642
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "7.14.0",
3
+ "version": "7.14.1",
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": {