primocode 10.4.0 → 10.4.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/README.md +10 -2
- package/bin/primocode.js +58 -11
- package/lib/entrada.js +157 -9
- package/lib/tela.js +232 -0
- package/lib/ui.js +9 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v10.4.
|
|
1
|
+
# PrimoCode v10.4.2
|
|
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.2
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
|
@@ -19,6 +19,14 @@ veja `/mascot` e `/name`.
|
|
|
19
19
|
|
|
20
20
|
## O que mudou na 10.4
|
|
21
21
|
|
|
22
|
+
- **A tela do OpenCode.** O PrimoCode abre em tela cheia: a conversa rola em
|
|
23
|
+
cima, a caixa de digitar fica presa no pé, e a barra da direita mostra a
|
|
24
|
+
sessão, o contexto (tokens e % usado), o modo, a pasta e a versão. Sem
|
|
25
|
+
conversa, a home põe o logo e a caixa no meio. `Tab` troca o modo, `Ctrl+P`
|
|
26
|
+
abre os comandos (o menu abre em cima da caixa), a roda do mouse e o
|
|
27
|
+
`PgUp`/`PgDn` rolam a conversa. `PRIMOCODE_TELA=linha` volta ao jeito antigo.
|
|
28
|
+
- **Motion sem encostar na borda.** Margem de 6% nas laterais e 7% em cima e
|
|
29
|
+
embaixo, e 4% entre elementos — garantido em código antes do render.
|
|
22
30
|
- **Vídeo gravado em 3D (`estilo: personagem`).** Personagens modelados — gente
|
|
23
31
|
ou robô — conversando num set (podcast, estúdio, escritório). A boca segue a
|
|
24
32
|
voz letra por letra, eles piscam, gesticulam e viram a cabeça para quem fala.
|
package/bin/primocode.js
CHANGED
|
@@ -482,6 +482,36 @@ function barraDeStatus() {
|
|
|
482
482
|
return { modo, cor, esquerda: partes.join(c.dim(' · ')), direita: direita.join(c.dim(' ')) };
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
+
/* A barra da direita da tela cheia, como a do OpenCode: o contexto (quantos
|
|
486
|
+
tokens a conversa já ocupa e quanto isso é do teto), o modo e o esforço, e
|
|
487
|
+
no pé a pasta com o ramo e a versão. */
|
|
488
|
+
function barraLateral() {
|
|
489
|
+
const uso = memoria.usoDoContexto(state.memoria, state.history);
|
|
490
|
+
const dir = state.workDir || process.cwd();
|
|
491
|
+
const ramo = ramoDoGit(dir);
|
|
492
|
+
const m = state.mode === 'auto' && state.modoEfetivo ? state.modoEfetivo : state.mode;
|
|
493
|
+
const secoes = [
|
|
494
|
+
{ titulo: 'Contexto', linhas: [
|
|
495
|
+
c.muted(`${uso.tokens.toLocaleString('pt-BR')} tokens`),
|
|
496
|
+
(uso.pct >= 100 ? c.err : uso.pct >= 70 ? c.warn : c.muted)(`${uso.pct}% usado`),
|
|
497
|
+
] },
|
|
498
|
+
{ titulo: 'Modo', linhas: [
|
|
499
|
+
`${COR_MODO[m] || COR_MODO.auto}${NOME_MODO[m] || m}${c.reset}` + c.muted(` · esforço ${effort.normalizar(state.config.effort)}`),
|
|
500
|
+
] },
|
|
501
|
+
];
|
|
502
|
+
if (state.attachedFiles.length) {
|
|
503
|
+
secoes.push({ titulo: 'Anexos', linhas: state.attachedFiles.map((f) => c.muted(path.basename(f.path))) });
|
|
504
|
+
}
|
|
505
|
+
if (state.controle && state.controle.liberado) secoes.push({ titulo: 'Controle do PC', linhas: [c.warn('liberado até fechar')] });
|
|
506
|
+
return {
|
|
507
|
+
secoes,
|
|
508
|
+
rodape: [
|
|
509
|
+
c.muted(shortPath(dir)) + (ramo ? c.white(':' + ramo) : ''),
|
|
510
|
+
c.brand('●') + ' ' + c.bold(c.white('Primo')) + c.white('Code') + ' ' + c.dim(VERSION),
|
|
511
|
+
],
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
485
515
|
/* O Tab com a caixa vazia gira o modo, como o "tab agents" do OpenCode:
|
|
486
516
|
auto → primo code → chat → auto. A escolha fica salva, como a do /mode. */
|
|
487
517
|
function trocarModo() {
|
|
@@ -2707,23 +2737,40 @@ async function main() {
|
|
|
2707
2737
|
process.exit(0);
|
|
2708
2738
|
}
|
|
2709
2739
|
|
|
2710
|
-
/* A
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2740
|
+
/* A TELA CHEIA do OpenCode (lib/tela.js) é o padrão: a conversa rola em
|
|
2741
|
+
cima, a caixa fica presa no pé, a barra da direita mostra a sessão e o
|
|
2742
|
+
contexto, e sem conversa a home põe o logo e a caixa no meio da tela.
|
|
2743
|
+
PRIMOCODE_TELA=linha (ou "tela": "linha" no config) volta ao jeito
|
|
2744
|
+
antigo, que escreve em linha pelo terminal. */
|
|
2745
|
+
const telaCheia = process.stdout.isTTY && process.stdout.rows >= 16
|
|
2746
|
+
&& (process.env.PRIMOCODE_TELA || state.config.tela || 'cheia') !== 'linha';
|
|
2747
|
+
if (!telaCheia) {
|
|
2748
|
+
if (process.stdout.isTTY && process.stdout.rows >= 20) {
|
|
2749
|
+
const rows = process.stdout.rows;
|
|
2750
|
+
const alto = 8 + 6; // o logo com a versão + a caixa
|
|
2751
|
+
process.stdout.write('\n'.repeat(rows) + '\x1b[H' + '\n'.repeat(Math.max(0, Math.floor((rows - alto) / 2) - 1)));
|
|
2752
|
+
}
|
|
2753
|
+
console.log(welcomeBox(VERSION, state.workDir ? shortPath(state.workDir) : null, {
|
|
2754
|
+
pastaPadrao: state.workDirOrigem === 'padrao',
|
|
2755
|
+
}));
|
|
2718
2756
|
}
|
|
2719
|
-
console.log(welcomeBox(VERSION, state.workDir ? shortPath(state.workDir) : null, {
|
|
2720
|
-
pastaPadrao: state.workDirOrigem === 'padrao',
|
|
2721
|
-
}));
|
|
2722
2757
|
rl = entrada.criar({
|
|
2723
2758
|
status: barraDeStatus,
|
|
2724
2759
|
comandos: comandosDoMenu,
|
|
2725
2760
|
vazio: ui.convite(),
|
|
2761
|
+
telaCheia,
|
|
2762
|
+
lateral: barraLateral,
|
|
2763
|
+
versao: VERSION,
|
|
2764
|
+
logo: ui.logoComMascote(state.config.mascot),
|
|
2765
|
+
pasta: () => {
|
|
2766
|
+
const dir = state.workDir || process.cwd();
|
|
2767
|
+
const ramo = ramoDoGit(dir);
|
|
2768
|
+
return shortPath(dir) + (ramo ? ':' + ramo : '');
|
|
2769
|
+
},
|
|
2726
2770
|
});
|
|
2771
|
+
if (telaCheia && state.workDirOrigem === 'padrao') {
|
|
2772
|
+
console.log(c.warn(` pasta padrão: ${shortPath(state.workDir)}`) + c.dim(' · /dir <pasta> para trabalhar numa sua'));
|
|
2773
|
+
}
|
|
2727
2774
|
// O spinner desenha dentro da caixa (lib/entrada.js mostrarSpinner).
|
|
2728
2775
|
if (rl.tty) Spinner.caixa = rl;
|
|
2729
2776
|
ui.titulo('PrimoCode');
|
package/lib/entrada.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
const readline = require('readline');
|
|
41
41
|
const { EventEmitter } = require('events');
|
|
42
42
|
const { c, visibleWidth, truncate } = require('./ui.js');
|
|
43
|
+
const tela = require('./tela.js');
|
|
43
44
|
|
|
44
45
|
// ── O desenho ────────────────────────────────────────────────────────────
|
|
45
46
|
|
|
@@ -200,8 +201,13 @@ function montar(e) {
|
|
|
200
201
|
// campo de digitar para baixo a cada tecla, e o texto que a pessoa
|
|
201
202
|
// escreve saltaria de lugar enquanto ela escreve. O desenho é o do
|
|
202
203
|
// OpenCode: fundo de painel, e o escolhido numa faixa pêssego.
|
|
204
|
+
/* NA TELA CHEIA (`menuAcima`) ele abre EM CIMA do painel, como no
|
|
205
|
+
OpenCode: lá a caixa é presa no pé da tela e nada salta — o menu só
|
|
206
|
+
cobre a conversa. */
|
|
207
|
+
const menu = [];
|
|
203
208
|
if (e.menu && e.menu.itens.length) {
|
|
204
|
-
const
|
|
209
|
+
const out = menu;
|
|
210
|
+
const cabem = Math.max(1, Math.min(e.menu.itens.length, e.menuAcima ? 10 : 8));
|
|
205
211
|
// A janela acompanha a seleção: descer na lista rola, em vez de o
|
|
206
212
|
// item escolhido sumir embaixo.
|
|
207
213
|
let topo = Math.max(0, Math.min(e.menu.sel - Math.floor(cabem / 2),
|
|
@@ -221,10 +227,14 @@ function montar(e) {
|
|
|
221
227
|
}
|
|
222
228
|
}
|
|
223
229
|
|
|
230
|
+
let acima = 0;
|
|
231
|
+
if (menu.length && e.menuAcima) { out.splice(e.spinner ? 1 : 0, 0, ...menu); acima = menu.length; }
|
|
232
|
+
else out.push(...menu);
|
|
233
|
+
|
|
224
234
|
const margem = ' '.repeat(recuo);
|
|
225
235
|
return {
|
|
226
236
|
linhas: recuo ? out.map((l) => margem + l) : out,
|
|
227
|
-
cursorLinha: (e.spinner ? 1 : 0) + 1 + cursorLinha, // +1 pelo respiro de cima
|
|
237
|
+
cursorLinha: acima + (e.spinner ? 1 : 0) + 1 + cursorLinha, // +1 pelo respiro de cima
|
|
228
238
|
/* No modo bash o "!" não é desenhado, então tudo depois dele anda uma
|
|
229
239
|
coluna para a esquerda — e o cursor tem de andar junto, ou ele
|
|
230
240
|
aparece um caractere à frente de onde o texto realmente está. */
|
|
@@ -268,6 +278,12 @@ class Entrada extends EventEmitter {
|
|
|
268
278
|
this.oculto = false; // a próxima resposta é senha: ver `ocultarProxima`
|
|
269
279
|
this.tty = !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
270
280
|
this.escreverCru = process.stdout.write.bind(process.stdout);
|
|
281
|
+
/* A TELA CHEIA (lib/tela.js): a conversa vive aqui, em linhas, e a
|
|
282
|
+
tela inteira é redesenhada a cada mudança — em vez de o texto subir
|
|
283
|
+
pelo terminal com a caixa atrás. */
|
|
284
|
+
this.cheia = !!(op.telaCheia && this.tty);
|
|
285
|
+
this.conversa = [];
|
|
286
|
+
this.rolagem = 0;
|
|
271
287
|
}
|
|
272
288
|
|
|
273
289
|
// ── ciclo de vida ────────────────────────────────────────────────────
|
|
@@ -290,7 +306,7 @@ class Entrada extends EventEmitter {
|
|
|
290
306
|
this._aoTeclar = (ch, key) => this._tecla(ch, key);
|
|
291
307
|
process.stdin.on('keypress', this._aoTeclar);
|
|
292
308
|
|
|
293
|
-
this._aoRedimensionar = () => { if (this.desenhado) this._redesenhar(); };
|
|
309
|
+
this._aoRedimensionar = () => { if (this.cheia) this._pintar(); else if (this.desenhado) this._redesenhar(); };
|
|
294
310
|
process.stdout.on('resize', this._aoRedimensionar);
|
|
295
311
|
|
|
296
312
|
/* Todo `console.log` do programa passa a apagar a caixa antes de
|
|
@@ -300,8 +316,20 @@ class Entrada extends EventEmitter {
|
|
|
300
316
|
por cima da caixa. */
|
|
301
317
|
this._stdoutOriginal = process.stdout.write;
|
|
302
318
|
const eu = this;
|
|
319
|
+
if (this.cheia) this._entrarNaTela();
|
|
303
320
|
process.stdout.write = function (texto, ...resto) {
|
|
304
321
|
if (eu._pintando) return eu._stdoutOriginal.call(this, texto, ...resto);
|
|
322
|
+
/* Na tela cheia, o que o programa escreve vira linha da conversa,
|
|
323
|
+
e quem põe na tela é o próximo quadro. Suspensa (o painel do
|
|
324
|
+
/effort desenhando por cima), passa direto. */
|
|
325
|
+
if (eu.cheia) {
|
|
326
|
+
if (eu.suspenso) return eu._stdoutOriginal.call(this, texto, ...resto);
|
|
327
|
+
tela.acrescentar(eu.conversa, texto);
|
|
328
|
+
eu._agendar();
|
|
329
|
+
const fim = resto.find((r) => typeof r === 'function');
|
|
330
|
+
if (fim) fim();
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
305
333
|
eu._apagar();
|
|
306
334
|
const r = eu._stdoutOriginal.call(this, texto, ...resto);
|
|
307
335
|
/* ── O TEXTO QUE CHEGA EM PEDAÇOS ────────────────────────────
|
|
@@ -346,6 +374,7 @@ class Entrada extends EventEmitter {
|
|
|
346
374
|
|
|
347
375
|
parar() {
|
|
348
376
|
if (!this.tty) { if (this._rlPipe) this._rlPipe.close(); return; }
|
|
377
|
+
if (this.cheia) this._sairDaTela();
|
|
349
378
|
this._apagar();
|
|
350
379
|
this.escreverCru('\x1b[?2004l');
|
|
351
380
|
if (this._stdoutOriginal) process.stdout.write = this._stdoutOriginal;
|
|
@@ -359,8 +388,89 @@ class Entrada extends EventEmitter {
|
|
|
359
388
|
/* Enquanto outro desenho é dono da tela — o painel do /effort, uma tela
|
|
360
389
|
do marketplace — a caixa sai da frente e larga o teclado. Sem isso, os
|
|
361
390
|
dois desenhariam no mesmo lugar e as setas moveriam os dois. */
|
|
362
|
-
suspender() { this.suspenso = true; this._apagar(); }
|
|
363
|
-
retomar() {
|
|
391
|
+
suspender() { this.suspenso = true; if (this.cheia) this._sairDaTela(); else this._apagar(); }
|
|
392
|
+
retomar() {
|
|
393
|
+
this.suspenso = false;
|
|
394
|
+
if (this.cheia && this.ativo && !this.fechado) { this._entrarNaTela(); return this._pintar(); }
|
|
395
|
+
if (this.ativo && !this.fechado) this._desenhar();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ── a tela cheia ─────────────────────────────────────────────────────
|
|
399
|
+
|
|
400
|
+
/* A tela alternativa (a do vim): o que estava no terminal fica guardado e
|
|
401
|
+
volta inteiro na saída. Com ela, a roda do mouse — senão o terminal
|
|
402
|
+
transformaria a roda em seta para cima e a seta é o histórico. */
|
|
403
|
+
_entrarNaTela() {
|
|
404
|
+
if (this._naTela) return;
|
|
405
|
+
this._naTela = true;
|
|
406
|
+
/* O resto do programa (o markdown, a fala) quebra linha pela largura
|
|
407
|
+
do terminal. Na tela cheia a conversa é só a coluna da esquerda —
|
|
408
|
+
sem a barra da direita — então, enquanto ela está no ar, é ESSA a
|
|
409
|
+
largura que o programa vê. A real fica guardada para o desenho. */
|
|
410
|
+
if (!this._colunasReais) {
|
|
411
|
+
const eu = this;
|
|
412
|
+
this._colunasReais = process.stdout.columns || 80;
|
|
413
|
+
Object.defineProperty(process.stdout, 'columns', {
|
|
414
|
+
configurable: true,
|
|
415
|
+
get() { return eu._naTela && !eu.suspenso ? tela.larguraDaConversa(eu._colunasReais) : eu._colunasReais; },
|
|
416
|
+
set(v) { eu._colunasReais = v; }, // o Node escreve aqui quando a janela muda
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
this.escreverCru('\x1b[?1049h\x1b[?1000h\x1b[?1006h\x1b[2J');
|
|
420
|
+
if (!this._aoSairDoProcesso) {
|
|
421
|
+
// Um erro que derruba o programa não pode deixar o terminal na
|
|
422
|
+
// tela alternativa, sem cursor e capturando o mouse.
|
|
423
|
+
this._aoSairDoProcesso = () => this._sairDaTela();
|
|
424
|
+
process.on('exit', this._aoSairDoProcesso);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
_sairDaTela() {
|
|
429
|
+
if (!this._naTela) return;
|
|
430
|
+
this._naTela = false;
|
|
431
|
+
this.escreverCru('\x1b[?1000l\x1b[?1006l\x1b[?25h\x1b[?1049l');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Junta as mudanças de um mesmo instante num quadro só. */
|
|
435
|
+
_agendar() {
|
|
436
|
+
if (this._agendado) return;
|
|
437
|
+
this._agendado = setTimeout(() => { this._agendado = null; this._pintar(); }, 16);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** O estado do quadro, para a tela cheia (função pura em lib/tela.js). */
|
|
441
|
+
_quadroCheio() {
|
|
442
|
+
const conversa = this.conversa.some((l) => /\S/.test(l));
|
|
443
|
+
return tela.quadro({
|
|
444
|
+
colunas: this._colunasReais || 80,
|
|
445
|
+
linhas: process.stdout.rows || 24,
|
|
446
|
+
conversa: this.conversa,
|
|
447
|
+
rolagem: this.rolagem,
|
|
448
|
+
home: !this.mandou && !conversa,
|
|
449
|
+
caixa: (colunas, centro) => montar({ ...this._estado(), colunas, centro, menuAcima: true }),
|
|
450
|
+
lateral: { titulo: this.titulo, ...(this.op.lateral ? this.op.lateral() : {}) },
|
|
451
|
+
versao: this.op.versao || '',
|
|
452
|
+
logo: this.op.logo,
|
|
453
|
+
pasta: this.op.pasta ? this.op.pasta() : '',
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
_pintar() {
|
|
458
|
+
if (!this.cheia || this.suspenso || this.fechado || !this._naTela) return;
|
|
459
|
+
const q = this._quadroCheio();
|
|
460
|
+
this.rolagem = Math.min(this.rolagem, q.rolagemMax);
|
|
461
|
+
let out = '\x1b[?25l';
|
|
462
|
+
q.linhas.forEach((l, i) => { out += `\x1b[${i + 1};1H${l}\x1b[0m\x1b[K`; });
|
|
463
|
+
out += `\x1b[${q.cursor.linha + 1};${q.cursor.coluna + 1}H\x1b[?25h`;
|
|
464
|
+
this._pintando = true;
|
|
465
|
+
this.escreverCru(out);
|
|
466
|
+
this._pintando = false;
|
|
467
|
+
this.desenhado = { linhas: q.linhas, cursorLinha: q.cursor.linha };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
_rolar(passo) {
|
|
471
|
+
this.rolagem = Math.max(0, this.rolagem + passo);
|
|
472
|
+
this._pintar();
|
|
473
|
+
}
|
|
364
474
|
|
|
365
475
|
/** O agente está trabalhando: a caixa esmaece, e o Enter manda um RECADO
|
|
366
476
|
* que entra na tarefa em curso (a caixa de entrada de lib/act.js). */
|
|
@@ -378,11 +488,13 @@ class Entrada extends EventEmitter {
|
|
|
378
488
|
const novo = texto || '';
|
|
379
489
|
if (novo === (this.spinnerTexto || '')) return;
|
|
380
490
|
this.spinnerTexto = novo;
|
|
491
|
+
if (this.cheia) return this._agendar();
|
|
381
492
|
if (this.desenhado && !this._meiaLinha) this._redesenhar();
|
|
382
493
|
}
|
|
383
494
|
|
|
384
495
|
/** Escreve texto ACIMA da caixa — é por onde todo o resto do programa fala. */
|
|
385
496
|
escreverAcima(texto) {
|
|
497
|
+
if (this.cheia) { tela.acrescentar(this.conversa, texto); return this._agendar(); }
|
|
386
498
|
if (!this.desenhado) { this.escreverCru(texto); return; }
|
|
387
499
|
this._apagar();
|
|
388
500
|
this.escreverCru(texto);
|
|
@@ -441,8 +553,10 @@ class Entrada extends EventEmitter {
|
|
|
441
553
|
|
|
442
554
|
// ── desenho ──────────────────────────────────────────────────────────
|
|
443
555
|
|
|
444
|
-
_quadro() {
|
|
445
|
-
|
|
556
|
+
_quadro() { return montar(this._estado()); }
|
|
557
|
+
|
|
558
|
+
_estado() {
|
|
559
|
+
return ({
|
|
446
560
|
texto: this.oculto ? '•'.repeat(this.line.length) : this.line,
|
|
447
561
|
cursor: this.cursor,
|
|
448
562
|
colunas: process.stdout.columns || 80,
|
|
@@ -457,6 +571,7 @@ class Entrada extends EventEmitter {
|
|
|
457
571
|
}
|
|
458
572
|
|
|
459
573
|
_desenhar() {
|
|
574
|
+
if (this.cheia) return this._pintar();
|
|
460
575
|
if (!this.tty || this.suspenso || this.fechado) return;
|
|
461
576
|
/* Já desenhada (um `prompt()` de quem não sabe que ela está no ar):
|
|
462
577
|
apaga antes. Sem isso o quadro novo começava na linha do cursor —
|
|
@@ -482,6 +597,7 @@ class Entrada extends EventEmitter {
|
|
|
482
597
|
}
|
|
483
598
|
|
|
484
599
|
_apagar() {
|
|
600
|
+
if (this.cheia) return;
|
|
485
601
|
if (!this.desenhado || !this.tty) return;
|
|
486
602
|
this._pintando = true;
|
|
487
603
|
// Sobe até a primeira linha do quadro e limpa daí para baixo.
|
|
@@ -538,6 +654,19 @@ class Entrada extends EventEmitter {
|
|
|
538
654
|
return;
|
|
539
655
|
}
|
|
540
656
|
|
|
657
|
+
/* A roda do mouse chega como "\x1b[<64;x;yM", e o readline a entrega
|
|
658
|
+
picada, tecla por tecla. Daqui até o M (ou m), tudo é do mouse. */
|
|
659
|
+
if (key && key.code === '[<') { this._mouse = ''; return; }
|
|
660
|
+
if (this._mouse != null) {
|
|
661
|
+
if (/^[mM]$/.test(seq)) {
|
|
662
|
+
const botao = parseInt(this._mouse, 10);
|
|
663
|
+
this._mouse = null;
|
|
664
|
+
if (botao === 64) this._rolar(3);
|
|
665
|
+
else if (botao === 65) this._rolar(-3);
|
|
666
|
+
} else this._mouse += seq;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
541
670
|
if (!key) { if (ch) this.write(ch); return; }
|
|
542
671
|
|
|
543
672
|
// Esc Esc e o resto das teclas de fora: quem escuta decide. A caixa
|
|
@@ -607,6 +736,8 @@ class Entrada extends EventEmitter {
|
|
|
607
736
|
if (this.cursor < this.line.length) { this.cursor++; this._redesenhar(); }
|
|
608
737
|
return;
|
|
609
738
|
}
|
|
739
|
+
case 'pageup': return this._rolar(Math.max(3, (process.stdout.rows || 24) - 8));
|
|
740
|
+
case 'pagedown': return this._rolar(-Math.max(3, (process.stdout.rows || 24) - 8));
|
|
610
741
|
case 'home': this.cursor = 0; this._redesenhar(); return;
|
|
611
742
|
case 'end': this.cursor = this.line.length; this._redesenhar(); return;
|
|
612
743
|
|
|
@@ -647,7 +778,9 @@ class Entrada extends EventEmitter {
|
|
|
647
778
|
this.cursor = antes.length;
|
|
648
779
|
return this._mudou();
|
|
649
780
|
}
|
|
650
|
-
case 'l':
|
|
781
|
+
case 'l':
|
|
782
|
+
if (this.cheia) { this.conversa = []; this.rolagem = 0; return this._pintar(); }
|
|
783
|
+
this._apagar(); this.escreverCru('\x1b[2J\x1b[H'); return this._desenhar();
|
|
651
784
|
case 'j': return this._novaLinha();
|
|
652
785
|
// Ctrl+P abre os comandos, como no OpenCode: o mesmo menu do "/".
|
|
653
786
|
case 'p':
|
|
@@ -711,11 +844,26 @@ class Entrada extends EventEmitter {
|
|
|
711
844
|
rola para cima não acha o que perguntou. */
|
|
712
845
|
this._apagar();
|
|
713
846
|
if (texto.trim()) this.mandou = true; // a caixa sai do centro e ocupa a largura
|
|
847
|
+
// O título da sessão, na barra da direita: o primeiro pedido.
|
|
848
|
+
if (texto.trim() && !oculto && !this.titulo && !texto.trim().startsWith('/')) {
|
|
849
|
+
const t = visto.replace(/\s+/g, ' ').trim();
|
|
850
|
+
this.titulo = t.length > 70 ? t.slice(0, 69).replace(/\s+\S*$/, '') + '…' : t;
|
|
851
|
+
}
|
|
714
852
|
if (texto.trim() && !oculto) {
|
|
715
853
|
// Na tela fica a etiqueta (o que se viu ao mandar); quem vai ao agente
|
|
716
854
|
// é o texto inteiro. O desenho é o do pedido no OpenCode: o mesmo
|
|
717
855
|
// painel da caixa, com a barra, e um respiro em cima e embaixo.
|
|
718
|
-
this.
|
|
856
|
+
if (this.cheia) {
|
|
857
|
+
const W = this._colunasReais || 80;
|
|
858
|
+
const mainW = W >= tela.LATERAL_MIN ? W - tela.LATERAL_W : W;
|
|
859
|
+
this.rolagem = 0;
|
|
860
|
+
/* Uma linha em branco antes e outra depois do pedido: cada
|
|
861
|
+
troca (pedido, resposta) é um bloco separado na tela. */
|
|
862
|
+
const temAlgo = this.conversa.some((l) => /\S/.test(l));
|
|
863
|
+
const ultima = this.conversa[this.conversa.length - 1] || '';
|
|
864
|
+
const antes = !temAlgo ? '' : /\S/.test(ultima) ? '\n\n' : '\n';
|
|
865
|
+
tela.acrescentar(this.conversa, antes + pedidoNaTela(visto, mainW - 1) + '\n\n');
|
|
866
|
+
} else this.escreverCru(pedidoNaTela(visto, process.stdout.columns || 80) + '\n');
|
|
719
867
|
if (this.historico[this.historico.length - 1] !== texto) this.historico.push(texto);
|
|
720
868
|
}
|
|
721
869
|
this._desenhar();
|
package/lib/tela.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tela.js — a TELA CHEIA do PrimoCode, no desenho do OpenCode.
|
|
3
|
+
*
|
|
4
|
+
* O jeito antigo escrevia em linha, como qualquer programa de terminal: o
|
|
5
|
+
* texto subia e a caixa ia junto, no pé do que já tinha saído. O OpenCode,
|
|
6
|
+
* que é a referência pedida (com os prints), é outra coisa: ocupa a tela
|
|
7
|
+
* inteira (a "tela alternativa" do terminal, a mesma do vim), e cada parte
|
|
8
|
+
* tem lugar fixo:
|
|
9
|
+
*
|
|
10
|
+
* ┌──────────────────────────────────────────┬──────────────────┐
|
|
11
|
+
* │ a conversa, rolando │ título da sessão │
|
|
12
|
+
* │ │ Contexto │
|
|
13
|
+
* │ │ 36.216 tokens │
|
|
14
|
+
* │ [menu dos comandos, quando aberto] │ 4% usado │
|
|
15
|
+
* │ ┃ caixa de digitar, presa no pé │ │
|
|
16
|
+
* │ ┃ Auto · low PrimoCode │ ~/pasta:main │
|
|
17
|
+
* │ ~/pasta 36k (4%) ctrl+p… │ ● PrimoCode 10.4 │
|
|
18
|
+
* └──────────────────────────────────────────┴──────────────────┘
|
|
19
|
+
*
|
|
20
|
+
* Sem conversa ainda, a HOME: o logo e a caixa no meio da tela, a pasta no
|
|
21
|
+
* canto de baixo à esquerda e a versão à direita.
|
|
22
|
+
*
|
|
23
|
+
* Tudo aqui é FUNÇÃO PURA: recebe o estado e devolve as linhas da tela. Quem
|
|
24
|
+
* escreve no terminal é lib/entrada.js. Assim o teste confere o quadro
|
|
25
|
+
* inteiro — com barra lateral, sem ela, home, menu — sem terminal nenhum.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
const { c, visibleWidth, truncate, logo } = require('./ui.js');
|
|
31
|
+
|
|
32
|
+
const RESET = '\x1b[0m';
|
|
33
|
+
const FUNDO_LATERAL = '\x1b[48;5;234m';
|
|
34
|
+
const LATERAL_W = 42; // a largura da barra da direita
|
|
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
|
|
37
|
+
|
|
38
|
+
/* ── A conversa como texto ───────────────────────────────────────────────
|
|
39
|
+
O que o programa escreve (console.log, o fluxo da resposta) chega aqui
|
|
40
|
+
como texto cru, com cor. A tela guarda LINHAS lógicas; quebrar na largura
|
|
41
|
+
é coisa do desenho, porque a largura muda quando a janela muda. */
|
|
42
|
+
|
|
43
|
+
/** Junta um pedaço escrito às linhas que já existem. */
|
|
44
|
+
function acrescentar(linhas, texto) {
|
|
45
|
+
// Tirar o que não é cor: mover cursor, limpar linha, título. Na tela
|
|
46
|
+
// cheia quem decide onde cada coisa vai é o desenho, não quem escreve.
|
|
47
|
+
const t = String(texto == null ? '' : texto)
|
|
48
|
+
.replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, '')
|
|
49
|
+
.replace(/\x1b\[[0-9;?]*[A-Za-ln-z]/g, '');
|
|
50
|
+
if (!linhas.length) linhas.push('');
|
|
51
|
+
const partes = t.split('\n');
|
|
52
|
+
partes.forEach((p, i) => {
|
|
53
|
+
if (i > 0) linhas.push('');
|
|
54
|
+
// "\r" volta ao começo da linha: o que vem depois toma o lugar dela
|
|
55
|
+
// (é como o spinner antigo e as barras de progresso se redesenhavam).
|
|
56
|
+
const k = p.lastIndexOf('\r');
|
|
57
|
+
if (k >= 0) linhas[linhas.length - 1] = p.slice(k + 1);
|
|
58
|
+
else linhas[linhas.length - 1] += p;
|
|
59
|
+
});
|
|
60
|
+
// Guarda as últimas 5.000: uma sessão longa não pode crescer sem fim.
|
|
61
|
+
if (linhas.length > 5000) linhas.splice(0, linhas.length - 5000);
|
|
62
|
+
return linhas;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Quebra uma linha com cor em pedaços de no máximo `w` colunas: no ESPAÇO
|
|
67
|
+
* quando dá (palavra cortada ao meio é o que faz texto de terminal parecer
|
|
68
|
+
* quebrado), no caractere só quando a palavra sozinha é maior que a linha.
|
|
69
|
+
* Não corta código de cor ao meio e religa a cor ativa em cada pedaço.
|
|
70
|
+
*/
|
|
71
|
+
function quebrarComCor(linha, w) {
|
|
72
|
+
const out = [];
|
|
73
|
+
let atual = '', larg = 0, ativo = '';
|
|
74
|
+
// Códigos de cor não têm espaço, então partir no espaço não os corta.
|
|
75
|
+
const palavras = String(linha).split(/(?<= )/);
|
|
76
|
+
const corDe = (t) => {
|
|
77
|
+
for (const m of t.match(/\x1b\[[0-9;]*m/g) || []) ativo = (m === RESET || m === '\x1b[m') ? '' : ativo + m;
|
|
78
|
+
};
|
|
79
|
+
const novaLinha = () => { out.push(atual + (ativo ? RESET : '')); atual = ativo; larg = 0; };
|
|
80
|
+
for (const p of palavras) {
|
|
81
|
+
const pw = visibleWidth(p);
|
|
82
|
+
const semEspaco = visibleWidth(p.replace(/ +$/, ''));
|
|
83
|
+
if (larg + semEspaco <= w) { atual += p; larg += pw; corDe(p); continue; }
|
|
84
|
+
if (semEspaco <= w && larg > 0) { novaLinha(); atual += p; larg = pw; corDe(p); continue; }
|
|
85
|
+
// A palavra não cabe nem sozinha: vai caractere a caractere.
|
|
86
|
+
const re = /\x1b\[[0-9;]*m/y;
|
|
87
|
+
for (let i = 0; i < p.length;) {
|
|
88
|
+
re.lastIndex = i;
|
|
89
|
+
const m = re.exec(p);
|
|
90
|
+
if (m) { atual += m[0]; corDe(m[0]); i += m[0].length; continue; }
|
|
91
|
+
const ch = String.fromCodePoint(p.codePointAt(i));
|
|
92
|
+
const cw = visibleWidth(ch);
|
|
93
|
+
if (larg + cw > w && larg > 0) novaLinha();
|
|
94
|
+
atual += ch; larg += cw; i += ch.length;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
out.push(atual + (ativo ? RESET : ''));
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Preenche até `w` colunas (ou corta), com o fundo opcional até o fim. */
|
|
102
|
+
function caber(s, w, fundo) {
|
|
103
|
+
const v = visibleWidth(s);
|
|
104
|
+
if (v > w) s = truncate(s, w);
|
|
105
|
+
const falta = Math.max(0, w - visibleWidth(s));
|
|
106
|
+
if (!fundo) return s + ' '.repeat(falta);
|
|
107
|
+
return fundo + String(s).split(RESET).join(RESET + fundo) + ' '.repeat(falta) + RESET;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ── A barra da direita ──────────────────────────────────────────────── */
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {object} lat { titulo, secoes: [{titulo, linhas}], rodape: [] }
|
|
114
|
+
* @param {number} alto quantas linhas a tela tem
|
|
115
|
+
*/
|
|
116
|
+
function lateral(lat, alto) {
|
|
117
|
+
const w = LATERAL_W - 4; // 2 de margem de cada lado
|
|
118
|
+
const corpo = [''];
|
|
119
|
+
for (const l of quebrarComCor(c.bold(c.white(lat.titulo || 'Nova sessão')), w)) corpo.push(l);
|
|
120
|
+
for (const s of (lat.secoes || [])) {
|
|
121
|
+
corpo.push('');
|
|
122
|
+
corpo.push(c.bold(c.white(s.titulo)));
|
|
123
|
+
for (const l of s.linhas) corpo.push(...quebrarComCor(l, w));
|
|
124
|
+
}
|
|
125
|
+
const rodape = (lat.rodape || []).slice();
|
|
126
|
+
rodape.push('');
|
|
127
|
+
const linhas = [];
|
|
128
|
+
for (let i = 0; i < alto; i++) {
|
|
129
|
+
const deBaixo = alto - i - 1;
|
|
130
|
+
let l = '';
|
|
131
|
+
if (deBaixo < rodape.length) l = rodape[rodape.length - 1 - deBaixo];
|
|
132
|
+
else if (i < corpo.length) l = corpo[i];
|
|
133
|
+
linhas.push(caber(' ' + caber(l, w) + ' ', LATERAL_W, FUNDO_LATERAL));
|
|
134
|
+
}
|
|
135
|
+
return linhas;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* ── O quadro inteiro ─────────────────────────────────────────────────── */
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {object} e
|
|
142
|
+
* @param {number} e.colunas, e.linhas o tamanho do terminal
|
|
143
|
+
* @param {string[]} e.conversa as linhas lógicas escritas até aqui
|
|
144
|
+
* @param {number} e.rolagem quantas linhas acima do fim (0 = colado embaixo)
|
|
145
|
+
* @param {boolean} e.home ainda não houve pedido: logo no meio
|
|
146
|
+
* @param {(colunas:number, centro:boolean)=>{linhas:string[], cursorLinha:number, cursorColuna:number}} e.caixa
|
|
147
|
+
* @param {object} e.lateral o conteúdo da barra da direita
|
|
148
|
+
* @param {string} e.versao, e.pasta o rodapé da home
|
|
149
|
+
* @returns {{linhas: string[], cursor: {linha:number, coluna:number}, rolagemMax: number}}
|
|
150
|
+
*/
|
|
151
|
+
function quadro(e) {
|
|
152
|
+
const W = Math.max(30, e.colunas || 80);
|
|
153
|
+
const H = Math.max(10, e.linhas || 24);
|
|
154
|
+
|
|
155
|
+
if (e.home) {
|
|
156
|
+
const caixa = e.caixa(W, true);
|
|
157
|
+
const lg = W >= 60 && e.logo ? e.logo : W >= 50 ? logo() : [c.bold(c.white('primocode'))];
|
|
158
|
+
/* O painel fica PARADO no lugar dele: o menu, quando abre, cobre o
|
|
159
|
+
que está em cima (o logo), em vez de empurrar tudo para cima. */
|
|
160
|
+
const menu = Math.max(0, caixa.linhas.findIndex((l) => l.includes('┃')) - (/┃/.test(caixa.linhas[0]) ? 0 : 0));
|
|
161
|
+
const bloco = lg.length + 2 + caixa.linhas.length - menu;
|
|
162
|
+
const topo = Math.max(0, Math.floor((H - bloco) / 2) - 1);
|
|
163
|
+
const linhas = [];
|
|
164
|
+
for (let i = 0; i < topo; i++) linhas.push('');
|
|
165
|
+
for (const l of lg) linhas.push(' '.repeat(Math.max(0, Math.floor((W - visibleWidth(l)) / 2))) + l);
|
|
166
|
+
linhas.push('', '');
|
|
167
|
+
const inicioCaixa = Math.max(0, linhas.length - menu);
|
|
168
|
+
linhas.length = inicioCaixa;
|
|
169
|
+
linhas.push(...caixa.linhas);
|
|
170
|
+
while (linhas.length < H - 1) linhas.push('');
|
|
171
|
+
linhas.length = H - 1;
|
|
172
|
+
const esq = ' ' + c.dim(e.pasta || '');
|
|
173
|
+
const dir = c.dim(e.versao || '') + ' ';
|
|
174
|
+
linhas.push(esq + ' '.repeat(Math.max(1, W - visibleWidth(esq) - visibleWidth(dir))) + dir);
|
|
175
|
+
return {
|
|
176
|
+
linhas: linhas.map((l) => caber(l, W)),
|
|
177
|
+
cursor: { linha: inicioCaixa + caixa.cursorLinha, coluna: caixa.cursorColuna },
|
|
178
|
+
rolagemMax: 0,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const comLateral = W >= LATERAL_MIN;
|
|
183
|
+
const mainW = comLateral ? W - LATERAL_W : W;
|
|
184
|
+
/* Uma coluna de margem dos dois lados, para a conversa e para a caixa: o
|
|
185
|
+
pedido mandado (um painel) e a caixa de digitar ficam na MESMA borda. */
|
|
186
|
+
const caixa0 = e.caixa(mainW - 1, false);
|
|
187
|
+
const caixa = { ...caixa0, linhas: caixa0.linhas.map((l) => ' ' + l), cursorColuna: caixa0.cursorColuna + 1 };
|
|
188
|
+
const altoConversa = Math.max(1, H - caixa.linhas.length);
|
|
189
|
+
|
|
190
|
+
/* A conversa, quebrada na largura da coluna. Os PAINÉIS (o pedido que se
|
|
191
|
+
mandou) ficam na borda da caixa; o resto — a resposta, as ações, o
|
|
192
|
+
fecho "▣" — entra RECUADO, como no OpenCode. Sem o recuo, tudo saía
|
|
193
|
+
colado na borda e um bloco não se separava do outro. */
|
|
194
|
+
const visiveis = [];
|
|
195
|
+
for (const l of (e.conversa || [])) {
|
|
196
|
+
const painel = /^(\x1b\[[0-9;]*m)*┃/.test(l);
|
|
197
|
+
const margem = painel ? ' ' : ' '.repeat(RECUO + 1);
|
|
198
|
+
for (const p of quebrarComCor(l, painel ? mainW - 2 : mainW - 2 - RECUO)) visiveis.push(margem + p);
|
|
199
|
+
}
|
|
200
|
+
while (visiveis.length && !temTexto(visiveis[visiveis.length - 1])) visiveis.pop();
|
|
201
|
+
if (visiveis.length) visiveis.push(''); // um respiro entre a conversa e a caixa
|
|
202
|
+
const rolagemMax = Math.max(0, visiveis.length - altoConversa);
|
|
203
|
+
const rolagem = Math.max(0, Math.min(e.rolagem | 0, rolagemMax));
|
|
204
|
+
const fim = visiveis.length - rolagem;
|
|
205
|
+
const janela = visiveis.slice(Math.max(0, fim - altoConversa), fim);
|
|
206
|
+
// A conversa encosta EMBAIXO, logo acima da caixa — como num chat.
|
|
207
|
+
const conversa = [];
|
|
208
|
+
for (let i = janela.length; i < altoConversa; i++) conversa.push('');
|
|
209
|
+
for (const l of janela) conversa.push(l);
|
|
210
|
+
if (rolagem > 0) {
|
|
211
|
+
conversa[0] = ' ' + c.dim(`↑ ${rolagem} linha${rolagem > 1 ? 's' : ''} abaixo · PgDn ou a roda do mouse para voltar`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const esquerda = [...conversa, ...caixa.linhas].map((l) => caber(l, mainW));
|
|
215
|
+
const direita = comLateral ? lateral(e.lateral || {}, H) : null;
|
|
216
|
+
const linhas = esquerda.map((l, i) => (direita ? l + direita[i] : l));
|
|
217
|
+
return {
|
|
218
|
+
linhas,
|
|
219
|
+
cursor: { linha: altoConversa + caixa.cursorLinha, coluna: caixa.cursorColuna },
|
|
220
|
+
rolagemMax,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function temTexto(l) { return visibleWidth(l) > 0 && /\S/.test(l.replace(/\x1b\[[0-9;]*m/g, '')); }
|
|
225
|
+
|
|
226
|
+
/** A largura que o texto da conversa tem, numa tela de `colunas`. */
|
|
227
|
+
function larguraDaConversa(colunas) {
|
|
228
|
+
const W = Math.max(30, colunas || 80);
|
|
229
|
+
return (W >= LATERAL_MIN ? W - LATERAL_W : W) - 2 - RECUO;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = { RECUO, larguraDaConversa, quadro, acrescentar, quebrarComCor, lateral, caber, LATERAL_W, LATERAL_MIN };
|
package/lib/ui.js
CHANGED
|
@@ -358,6 +358,14 @@ function logo() {
|
|
|
358
358
|
}
|
|
359
359
|
const LOGO_W = visibleWidth(logo()[0]);
|
|
360
360
|
|
|
361
|
+
/** O logo com o mascote do PrimoCode na frente — o desenho de 3 linhas
|
|
362
|
+
* alinhado às 3 linhas do corpo das letras (a 4ª é só a perna do p). */
|
|
363
|
+
function logoComMascote(style) {
|
|
364
|
+
const m = mascot(style);
|
|
365
|
+
const l = logo();
|
|
366
|
+
return l.map((linha, i) => (m[i] || ' '.repeat(ART_W)) + ' ' + linha);
|
|
367
|
+
}
|
|
368
|
+
|
|
361
369
|
/** Centraliza uma linha (com cor) em `cols` colunas. */
|
|
362
370
|
function centro(s, cols) {
|
|
363
371
|
return ' '.repeat(Math.max(0, Math.floor((cols - visibleWidth(s)) / 2))) + s;
|
|
@@ -633,4 +641,4 @@ function friendlyError(raw) {
|
|
|
633
641
|
|
|
634
642
|
|
|
635
643
|
module.exports = {
|
|
636
|
-
titulo, c, logo, letreiro, convite, EXEMPLOS, mascot, MASCOTES, resolverMascote, mascoteInfo, galeriaMascotes, reacao, welcomeBox, Spinner, PENSANDO, actionHeader, friendlyError, visibleWidth, stripAnsi, truncate, padRight };
|
|
644
|
+
titulo, c, logo, logoComMascote, 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.2",
|
|
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": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
|
-
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/margem.test.js"
|
|
11
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|