primocode 7.12.0 → 7.12.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/lib/act.js CHANGED
@@ -15,7 +15,10 @@ const tools = require('./tools');
15
15
  const api = require('./api');
16
16
  const cfg = require('./config');
17
17
  const catalogo = require('./catalogo');
18
- const streaming = require('./streaming');
18
+ // "painel", não "streaming": dentro do runAct existe um `let streaming` (a
19
+ // flag de texto chegando em stream) que SOMBREARIA este módulo — e o step()
20
+ // viraria TypeError engolido pelo catch. Foi um bug real, pego em teste.
21
+ const painel = require('./streaming');
19
22
 
20
23
  const MAX_ITERATIONS = 15;
21
24
  const MAX_PROFUNDIDADE = 2; // principal + 1 nível de subagente
@@ -282,7 +285,7 @@ async function runAct(userPrompt, opts) {
282
285
 
283
286
  // Painel ao vivo (/streaming): registra o passo e fotografa a tela.
284
287
  // Nunca pode derrubar o loop — falha de foto é problema do painel.
285
- try { streaming.step(tc.name, tc.arguments, result); } catch {}
288
+ try { painel.step(tc.name, tc.arguments, result); } catch {}
286
289
 
287
290
  // Uma escrita bem-sucedida limpa a dívida: o agente se corrigiu.
288
291
  if (result && result.ok === false) falhasPendentes.push(`${tc.name}(${tc.arguments?.path || ''}): ${result.error}`);
package/lib/desktop.js CHANGED
@@ -407,8 +407,11 @@ function listWindows() {
407
407
  return m ? `|${m[1]}` : '';
408
408
  });
409
409
  } else if (TOOLS.xdotool) {
410
+ // O xdotool sai com código ≠ 0 se UMA janela da lista não tiver nome
411
+ // — mas o stdout com as outras continua valendo. Jogar a saída fora
412
+ // por causa do código fazia o total dar 0 com janelas na cara.
410
413
  const r = runOut(`xdotool search --onlyvisible --name '.*' getwindowname %@ 2>/dev/null`);
411
- if (r.ok) linhas = r.out.split('\n').map((t) => (t.trim() ? `|${t.trim()}` : ''));
414
+ if (r.out) linhas = r.out.split('\n').map((t) => (t.trim() ? `|${t.trim()}` : ''));
412
415
  } else {
413
416
  return { ok: false, error: 'Instale wmctrl ou xdotool para listar janelas no Linux.' };
414
417
  }
@@ -464,8 +467,13 @@ Write-Output $p.MainWindowTitle`,
464
467
  const busca = runOut(`xdotool search --onlyvisible --name ${sh1(t)} 2>/dev/null`);
465
468
  const id = (busca.out || '').trim().split('\n')[0];
466
469
  if (!id) return naoAchouJanela(t);
470
+ // windowactivate depende do window manager (EWMH). Sem ele — ou com um
471
+ // WM teimoso — raise+focus fazem o mesmo serviço.
467
472
  const r = run(`xdotool windowactivate --sync ${id}`);
468
- return r.ok ? { ok: true, janela: t } : r;
473
+ if (r.ok) return { ok: true, janela: t };
474
+ const raise = run(`xdotool windowraise ${id}`);
475
+ const foco = run(`xdotool windowfocus ${id}`);
476
+ return (raise.ok || foco.ok) ? { ok: true, janela: t } : r;
469
477
  }
470
478
  return { ok: false, error: 'Focar janela não disponível: instale xdotool (Linux).' };
471
479
  }
package/lib/streaming.js CHANGED
@@ -188,65 +188,74 @@ const MASCOTE_SVG = `<svg viewBox="0 0 9 3.6" aria-hidden="true">
188
188
  <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>
189
189
  </svg>`;
190
190
 
191
+ // A página segue o desenho do dono, traço a traço: fundo grafite, mascote
192
+ // inclinado no canto, "Primocode" branco no alto; a janela de borda branca
193
+ // grossa e canto bem redondo; barra #232323 com as três bolinhas e as abas em
194
+ // texto branco centralizado, separadas por "|"; dentro, a tela preta com a
195
+ // imagem ao vivo. O histórico (miniaturas + passos) fica ABAIXO da janela,
196
+ // discreto — a moldura fica idêntica ao desenho.
191
197
  const PAGINA = `<!doctype html>
192
198
  <html lang="pt-BR"><head><meta charset="utf-8">
193
199
  <meta name="viewport" content="width=device-width, initial-scale=1">
194
200
  <title>PrimoCode · Ao vivo</title>
195
201
  <style>
196
202
  * { box-sizing: border-box; margin: 0; }
197
- body { background: #17181a; color: #e2e8f0; font: 14px/1.5 ui-monospace, 'Cascadia Code', Consolas, monospace; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 18px 14px 26px; }
198
-
199
- /* Cabeçalho: mascote + nome */
200
- header { display: flex; align-items: center; gap: 16px; padding: 6px 0 18px; }
201
- header .mascote { width: 72px; height: 30px; transform: rotate(-8deg); }
202
- header .mascote svg { width: 100%; height: 100%; display: block; }
203
- header h1 { font-size: 34px; font-weight: 700; letter-spacing: .5px; }
204
- header h1 b { color: #22d3ee; font-weight: 700; }
205
- header .vivo { margin-left: 14px; display: inline-flex; align-items: center; gap: 7px; color: #64748b; font-size: 12px; }
206
- .dot { width: 9px; height: 9px; border-radius: 50%; background: #22d3ee; animation: pulsa 1.4s infinite; }
203
+ body { background: #262626; color: #fff; font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 0 16px 30px; }
204
+
205
+ /* Cabeçalho: mascote inclinado no canto, nome centrado */
206
+ #topo { position: relative; width: min(1100px, 100%); padding: 44px 0 30px; text-align: center; }
207
+ #topo .mascote { position: absolute; left: 6px; top: 30px; width: 96px; height: 40px; transform: rotate(-32deg); }
208
+ #topo .mascote svg { width: 100%; height: 100%; display: block; }
209
+ #topo h1 { font-size: clamp(34px, 6vw, 58px); font-weight: 600; letter-spacing: .5px; }
210
+ #topo .vivo { position: absolute; right: 6px; top: 46px; display: inline-flex; align-items: center; gap: 7px; color: #8a8a8a; font-size: 12px; }
211
+ .dot { width: 9px; height: 9px; border-radius: 50%; background: #46d160; animation: pulsa 1.4s infinite; }
207
212
  @keyframes pulsa { 50% { opacity: .25 } }
208
213
 
209
- /* A "janela" */
210
- #janela { width: min(1060px, 100%); background: #000; border: 4px solid #f4f4f4; border-radius: 26px; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 18px 60px rgba(0,0,0,.55); }
211
- #barra { display: flex; align-items: center; gap: 8px; background: #232323; padding: 10px 16px; border-bottom: 1px solid #101010; }
212
- #barra .b { width: 13px; height: 13px; border-radius: 50%; flex: none; }
213
- #barra .b.r { background: #e5484d; } #barra .b.y { background: #f5d90a; } #barra .b.g { background: #46d160; }
214
+ /* A janela — borda branca grossa, canto bem redondo */
215
+ #janela { width: min(1100px, 100%); background: #000; border: 10px solid #f5f5f5; border-radius: 46px; overflow: hidden; display: flex; flex-direction: column; }
216
+ #barra { position: relative; display: flex; align-items: center; background: #232323; padding: 14px 26px; min-height: 58px; }
217
+ #barra .b { flex: none; margin-right: 12px; }
218
+ #barra .b.r { width: 17px; height: 17px; border-radius: 50%; background: #d40000; }
219
+ #barra .b.y { width: 30px; height: 17px; border-radius: 50%; background: #ffdd00; }
220
+ #barra .b.g { width: 24px; height: 14px; border-radius: 50%; background: #33cc33; }
214
221
 
215
- #abas { display: flex; gap: 4px; margin-left: 14px; overflow-x: auto; scrollbar-width: none; }
222
+ /* Abas: texto branco centralizado na barra, separadas por | */
223
+ #abas { position: absolute; left: 50%; transform: translateX(-50%); max-width: 62%; display: flex; align-items: center; gap: 12px; overflow-x: auto; white-space: nowrap; scrollbar-width: none; font-size: clamp(16px, 2.4vw, 24px); font-weight: 500; }
216
224
  #abas::-webkit-scrollbar { display: none; }
217
- .aba { border: 0; cursor: pointer; white-space: nowrap; font: inherit; font-size: 13px; padding: 5px 14px; border-radius: 9px 9px 0 0; background: transparent; color: #94a3b8; }
218
- .aba:hover { color: #e2e8f0; }
219
- .aba.ativa { background: #000; color: #22d3ee; }
220
- .aba .qtd { color: #475569; font-size: 11px; margin-left: 6px; }
221
-
222
- #tela { position: relative; min-height: 380px; display: flex; align-items: center; justify-content: center; background: #000; }
223
- #tela img { width: 100%; max-height: 62vh; object-fit: contain; display: none; }
224
- #aviso { color: #94a3b8; text-align: center; max-width: 52ch; padding: 70px 20px; font-size: 15px; }
225
- #carimbo { position: absolute; left: 12px; bottom: 10px; background: rgba(11,18,32,.82); border: 1px solid #1e293b; color: #94a3b8; font-size: 12px; padding: 3px 10px; border-radius: 7px; display: none; max-width: 85%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
226
-
227
- /* Filmstrip + passos da aba */
228
- #rodape { background: #0b0c0e; border-top: 1px solid #1c1d20; }
229
- #filme { display: flex; gap: 6px; padding: 8px 12px 2px; overflow-x: auto; }
230
- .foto { flex: none; border: 1px solid #26272b; border-radius: 6px; background: #000; cursor: pointer; padding: 0; width: 84px; height: 50px; overflow: hidden; opacity: .75; }
225
+ .aba { border: 0; background: none; cursor: pointer; font: inherit; color: rgba(255,255,255,.5); padding: 2px 2px; }
226
+ .aba:hover { color: rgba(255,255,255,.8); }
227
+ .aba.ativa { color: #fff; }
228
+ .sep { color: rgba(255,255,255,.45); font-weight: 400; }
229
+
230
+ /* A tela */
231
+ #tela { position: relative; min-height: 470px; display: flex; align-items: center; justify-content: center; background: #000; }
232
+ #tela img { width: 100%; max-height: 64vh; object-fit: contain; display: none; }
233
+ #aviso { color: #fff; text-align: center; font-size: clamp(20px, 3.4vw, 34px); font-weight: 500; padding: 110px 20px; }
234
+ #carimbo { position: absolute; left: 16px; bottom: 12px; background: rgba(0,0,0,.75); border: 1px solid #2e2e2e; color: #bdbdbd; font-size: 12px; padding: 3px 10px; border-radius: 8px; display: none; max-width: 85%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
235
+
236
+ /* Histórico: fora da moldura, discreto, abaixo da janela */
237
+ #historico { width: min(1100px, 100%); padding-top: 14px; }
238
+ #filme { display: flex; gap: 6px; padding: 2px 2px 8px; overflow-x: auto; }
239
+ .foto { flex: none; border: 1px solid #3a3a3a; border-radius: 6px; background: #000; cursor: pointer; padding: 0; width: 84px; height: 50px; overflow: hidden; opacity: .7; }
231
240
  .foto img { width: 100%; height: 100%; object-fit: cover; display: block; }
232
- .foto.ativa { border-color: #22d3ee; opacity: 1; }
233
- #passos { max-height: 150px; overflow-y: auto; padding: 6px 4px 10px; }
234
- .item { padding: 3px 14px; display: flex; gap: 8px; font-size: 13px; }
235
- .item .n { color: #475569; min-width: 3ch; text-align: right; }
236
- .item .ok { color: #22d3ee; } .item .falha { color: #fb7185; }
237
- .item .txt { flex: 1; word-break: break-word; color: #cbd5e1; }
238
- .item .txt .erro { color: #fb7185; font-size: 12px; display: block; }
239
- .item .hora { color: #475569; font-size: 11px; }
240
- .item.temfoto { cursor: pointer; } .item.temfoto:hover .txt { color: #22d3ee; }
241
-
242
- footer { color: #475569; font-size: 12px; padding-top: 14px; }
241
+ .foto.ativa { border-color: #fff; opacity: 1; }
242
+ #passos { max-height: 170px; overflow-y: auto; }
243
+ .item { padding: 3px 6px; display: flex; gap: 8px; font-size: 13px; }
244
+ .item .n { color: #6f6f6f; min-width: 3ch; text-align: right; }
245
+ .item .ok { color: #46d160; } .item .falha { color: #e5484d; }
246
+ .item .txt { flex: 1; word-break: break-word; color: #d6d6d6; }
247
+ .item .txt .erro { color: #e5484d; font-size: 12px; display: block; }
248
+ .item .hora { color: #6f6f6f; font-size: 11px; }
249
+ .item.temfoto { cursor: pointer; } .item.temfoto:hover .txt { color: #fff; }
250
+
251
+ footer { color: #6f6f6f; font-size: 12px; padding-top: 14px; }
243
252
  </style></head>
244
253
  <body>
245
- <header>
254
+ <div id="topo">
246
255
  <div class="mascote">${MASCOTE_SVG}</div>
247
- <h1><b>Primo</b>Code</h1>
256
+ <h1>Primocode</h1>
248
257
  <span class="vivo"><span class="dot"></span><span id="st">conectando…</span></span>
249
- </header>
258
+ </div>
250
259
 
251
260
  <div id="janela">
252
261
  <div id="barra">
@@ -255,13 +264,14 @@ const PAGINA = `<!doctype html>
255
264
  </div>
256
265
  <section id="tela">
257
266
  <img id="frame" alt="tela que a IA está usando">
258
- <div id="aviso">Tela que a IA está usando aparece aqui, em tempo real.<br><br>Dê uma instrução no terminal do PrimoCode — cada app em que ela mexer vira uma aba, e você pode voltar para ver o que ela já fez.</div>
267
+ <div id="aviso">Tela que a IA está</div>
259
268
  <div id="carimbo"></div>
260
269
  </section>
261
- <div id="rodape">
262
- <div id="filme"></div>
263
- <div id="passos"></div>
264
- </div>
270
+ </div>
271
+
272
+ <div id="historico">
273
+ <div id="filme"></div>
274
+ <div id="passos"></div>
265
275
  </div>
266
276
  <footer>só nesta máquina (127.0.0.1) · /streaming desliga</footer>
267
277
 
@@ -282,14 +292,12 @@ function logDaAba(id) {
282
292
  }
283
293
 
284
294
  function render() {
285
- // Abas: "Ao vivo" + uma por app/janela
295
+ // Abas: "Ao vivo" + uma por app/janela, texto separado por |
286
296
  const nav = document.getElementById('abas');
287
297
  const abas = [{ id: VIVO, nome: 'Ao vivo' }, ...dados.abas];
288
298
  nav.innerHTML = abas.map((a) =>
289
- '<button class="aba' + (a.id === abaSel ? ' ativa' : '') + '" data-id="' + a.id + '">'
290
- + (a.id === VIVO ? '● ' : '') + esc(a.nome)
291
- + (a.passos ? '<span class="qtd">' + a.passos + '</span>' : '')
292
- + '</button>').join('');
299
+ '<button class="aba' + (a.id === abaSel ? ' ativa' : '') + '" data-id="' + a.id + '">' + esc(a.nome) + '</button>'
300
+ ).join('<span class="sep">|</span>');
293
301
  nav.querySelectorAll('.aba').forEach((b) => b.onclick = () => { abaSel = Number(b.dataset.id); fotoSel = null; render(); });
294
302
 
295
303
  // Imagem: a fixada no filmstrip, senão a mais nova da aba
@@ -306,7 +314,7 @@ function render() {
306
314
  carimbo.textContent = '#' + atual.id + ' · ' + atual.hora + ' · ' + atual.acao + (fotoSel != null ? ' (clique na miniatura de novo para voltar ao mais recente)' : '');
307
315
  } else {
308
316
  img.style.display = 'none'; aviso.style.display = 'block'; carimbo.style.display = 'none';
309
- if (dados.erroFoto) aviso.innerHTML = 'Sem foto da tela: ' + esc(dados.erroFoto);
317
+ if (dados.erroFoto) aviso.textContent = 'Sem foto da tela: ' + dados.erroFoto;
310
318
  }
311
319
 
312
320
  // Filmstrip da aba (as últimas 14)
@@ -336,7 +344,7 @@ async function tick() {
336
344
  try {
337
345
  const s = await (await fetch('/state')).json();
338
346
  dados = s;
339
- document.getElementById('st').textContent = 'ao vivo · porta ' + location.port;
347
+ document.getElementById('st').textContent = 'ao vivo';
340
348
  const assinatura = JSON.stringify([s.frames.length, s.log.length, s.abas.length, s.erroFoto]);
341
349
  if (assinatura !== ultimoEstado) { ultimoEstado = assinatura; render(); }
342
350
  } catch (e) {
@@ -348,6 +356,7 @@ tick();
348
356
  </script>
349
357
  </body></html>`;
350
358
 
359
+
351
360
  function start() {
352
361
  if (state.on) return Promise.resolve(state.url);
353
362
  try { fs.mkdirSync(DIR, { recursive: true }); } catch {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "7.12.0",
3
+ "version": "7.12.2",
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": {