primocode 10.4.2 → 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 +2 -2
- package/bin/primocode.js +9 -1
- package/lib/entrada.js +40 -7
- package/lib/tela.js +11 -0
- package/lib/ui.js +73 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v10.4.
|
|
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.
|
|
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,6 +2763,8 @@ 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,
|
|
2764
2769
|
logo: ui.logoComMascote(state.config.mascot),
|
|
2765
2770
|
pasta: () => {
|
|
@@ -2852,6 +2857,9 @@ async function main() {
|
|
|
2852
2857
|
const text = queue.shift().trim();
|
|
2853
2858
|
if (!text) continue;
|
|
2854
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));
|
|
2855
2863
|
try {
|
|
2856
2864
|
if (ehBash(text)) await rodarBash(text);
|
|
2857
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
|
|
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.
|
|
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
|
|
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,14 +450,36 @@ 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 || '',
|
|
452
484
|
logo: this.op.logo,
|
|
453
485
|
pasta: this.op.pasta ? this.op.pasta() : '',
|
|
@@ -455,7 +487,7 @@ class Entrada extends EventEmitter {
|
|
|
455
487
|
}
|
|
456
488
|
|
|
457
489
|
_pintar() {
|
|
458
|
-
if (!this.cheia || this.
|
|
490
|
+
if (!this.cheia || this.fechado || !this._naTela) return;
|
|
459
491
|
const q = this._quadroCheio();
|
|
460
492
|
this.rolagem = Math.min(this.rolagem, q.rolagemMax);
|
|
461
493
|
let out = '\x1b[?25l';
|
|
@@ -477,6 +509,7 @@ class Entrada extends EventEmitter {
|
|
|
477
509
|
trabalhando(sim) {
|
|
478
510
|
if (this.ocupado === !!sim) return;
|
|
479
511
|
this.ocupado = !!sim;
|
|
512
|
+
this._desde = sim ? Date.now() : 0;
|
|
480
513
|
if (this.desenhado) this._redesenhar();
|
|
481
514
|
}
|
|
482
515
|
|
package/lib/tela.js
CHANGED
|
@@ -116,6 +116,8 @@ function caber(s, w, fundo) {
|
|
|
116
116
|
function lateral(lat, alto) {
|
|
117
117
|
const w = LATERAL_W - 4; // 2 de margem de cada lado
|
|
118
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, '');
|
|
119
121
|
for (const l of quebrarComCor(c.bold(c.white(lat.titulo || 'Nova sessão')), w)) corpo.push(l);
|
|
120
122
|
for (const s of (lat.secoes || [])) {
|
|
121
123
|
corpo.push('');
|
|
@@ -179,6 +181,10 @@ function quadro(e) {
|
|
|
179
181
|
};
|
|
180
182
|
}
|
|
181
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') : []);
|
|
182
188
|
const comLateral = W >= LATERAL_MIN;
|
|
183
189
|
const mainW = comLateral ? W - LATERAL_W : W;
|
|
184
190
|
/* Uma coluna de margem dos dois lados, para a conversa e para a caixa: o
|
|
@@ -207,6 +213,11 @@ function quadro(e) {
|
|
|
207
213
|
const conversa = [];
|
|
208
214
|
for (let i = janela.length; i < altoConversa; i++) conversa.push('');
|
|
209
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
|
+
}
|
|
210
221
|
if (rolagem > 0) {
|
|
211
222
|
conversa[0] = ' ' + c.dim(`↑ ${rolagem} linha${rolagem > 1 ? 's' : ''} abaixo · PgDn ou a roda do mouse para voltar`);
|
|
212
223
|
}
|
package/lib/ui.js
CHANGED
|
@@ -358,12 +358,75 @@ function logo() {
|
|
|
358
358
|
}
|
|
359
359
|
const LOGO_W = visibleWidth(logo()[0]);
|
|
360
360
|
|
|
361
|
-
|
|
362
|
-
|
|
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. */
|
|
363
396
|
function logoComMascote(style) {
|
|
364
|
-
const m =
|
|
397
|
+
const m = mascoteGrande(style);
|
|
365
398
|
const l = logo();
|
|
366
|
-
|
|
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]];
|
|
367
430
|
}
|
|
368
431
|
|
|
369
432
|
/** Centraliza uma linha (com cor) em `cols` colunas. */
|
|
@@ -467,6 +530,8 @@ function titulo(texto) {
|
|
|
467
530
|
try { escrever(`\x1b]0;${limpo}\x07`); } catch { /* terminal fechado */ }
|
|
468
531
|
}
|
|
469
532
|
|
|
533
|
+
const agora = { texto: '', desde: 0 };
|
|
534
|
+
|
|
470
535
|
// O rótulo de cada tool na tela — o que a pessoa lê no lugar do nome interno.
|
|
471
536
|
const ROTULOS = {
|
|
472
537
|
read_file:'Read', write_file:'Write', edit_file:'Edit', multi_edit:'MultiEdit',
|
|
@@ -508,6 +573,9 @@ function actionHeader(name, args) {
|
|
|
508
573
|
// virava "[object Object]" na tela.
|
|
509
574
|
if (argStr && typeof argStr === 'object') argStr = JSON.stringify(argStr).slice(0, 60);
|
|
510
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();
|
|
511
579
|
// Uma tool sem argumento nenhum caía no JSON.stringify e desenhava "{}"
|
|
512
580
|
// ao lado do nome — ruído que não diz nada.
|
|
513
581
|
if (argStr === '{}' || argStr === 'null' || argStr === 'undefined') argStr = '';
|
|
@@ -641,4 +709,4 @@ function friendlyError(raw) {
|
|
|
641
709
|
|
|
642
710
|
|
|
643
711
|
module.exports = {
|
|
644
|
-
titulo, c, logo, logoComMascote, 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.
|
|
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": {
|