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 +2 -2
- package/bin/primocode.js +10 -1
- package/lib/entrada.js +47 -8
- package/lib/tela.js +26 -6
- package/lib/ui.js +77 -1
- 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,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
|
|
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,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.
|
|
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
|
-
|
|
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
|
-
|
|
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 || []))
|
|
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(
|
|
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.
|
|
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": {
|