primocode 10.3.0 → 10.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -2
- package/bin/primocode.js +111 -41
- package/lib/act.js +19 -5
- package/lib/catalogo.js +10 -4
- package/lib/entrada.js +261 -67
- package/lib/estudio/acervo.js +21 -1
- package/lib/estudio/estilo.js +12 -5
- package/lib/estudio/gerativo.js +18 -0
- package/lib/estudio/index.js +35 -3
- package/lib/estudio/kit/gerativo.jsx +62 -14
- package/lib/estudio/kit/index.jsx +2 -1
- package/lib/estudio/kit/personagem.jsx +582 -0
- package/lib/estudio/kit.js +3 -1
- package/lib/estudio/lancamento.js +5 -2
- package/lib/estudio/margem.js +235 -0
- package/lib/estudio/personagem.js +232 -0
- package/lib/estudio/print-site.js +190 -1
- package/lib/tela.js +223 -0
- package/lib/ui.js +70 -88
- package/package.json +2 -2
package/lib/tela.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
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
|
+
|
|
37
|
+
/* ── A conversa como texto ───────────────────────────────────────────────
|
|
38
|
+
O que o programa escreve (console.log, o fluxo da resposta) chega aqui
|
|
39
|
+
como texto cru, com cor. A tela guarda LINHAS lógicas; quebrar na largura
|
|
40
|
+
é coisa do desenho, porque a largura muda quando a janela muda. */
|
|
41
|
+
|
|
42
|
+
/** Junta um pedaço escrito às linhas que já existem. */
|
|
43
|
+
function acrescentar(linhas, texto) {
|
|
44
|
+
// Tirar o que não é cor: mover cursor, limpar linha, título. Na tela
|
|
45
|
+
// cheia quem decide onde cada coisa vai é o desenho, não quem escreve.
|
|
46
|
+
const t = String(texto == null ? '' : texto)
|
|
47
|
+
.replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, '')
|
|
48
|
+
.replace(/\x1b\[[0-9;?]*[A-Za-ln-z]/g, '');
|
|
49
|
+
if (!linhas.length) linhas.push('');
|
|
50
|
+
const partes = t.split('\n');
|
|
51
|
+
partes.forEach((p, i) => {
|
|
52
|
+
if (i > 0) linhas.push('');
|
|
53
|
+
// "\r" volta ao começo da linha: o que vem depois toma o lugar dela
|
|
54
|
+
// (é como o spinner antigo e as barras de progresso se redesenhavam).
|
|
55
|
+
const k = p.lastIndexOf('\r');
|
|
56
|
+
if (k >= 0) linhas[linhas.length - 1] = p.slice(k + 1);
|
|
57
|
+
else linhas[linhas.length - 1] += p;
|
|
58
|
+
});
|
|
59
|
+
// Guarda as últimas 5.000: uma sessão longa não pode crescer sem fim.
|
|
60
|
+
if (linhas.length > 5000) linhas.splice(0, linhas.length - 5000);
|
|
61
|
+
return linhas;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Quebra uma linha com cor em pedaços de no máximo `w` colunas: no ESPAÇO
|
|
66
|
+
* quando dá (palavra cortada ao meio é o que faz texto de terminal parecer
|
|
67
|
+
* quebrado), no caractere só quando a palavra sozinha é maior que a linha.
|
|
68
|
+
* Não corta código de cor ao meio e religa a cor ativa em cada pedaço.
|
|
69
|
+
*/
|
|
70
|
+
function quebrarComCor(linha, w) {
|
|
71
|
+
const out = [];
|
|
72
|
+
let atual = '', larg = 0, ativo = '';
|
|
73
|
+
// Códigos de cor não têm espaço, então partir no espaço não os corta.
|
|
74
|
+
const palavras = String(linha).split(/(?<= )/);
|
|
75
|
+
const corDe = (t) => {
|
|
76
|
+
for (const m of t.match(/\x1b\[[0-9;]*m/g) || []) ativo = (m === RESET || m === '\x1b[m') ? '' : ativo + m;
|
|
77
|
+
};
|
|
78
|
+
const novaLinha = () => { out.push(atual + (ativo ? RESET : '')); atual = ativo; larg = 0; };
|
|
79
|
+
for (const p of palavras) {
|
|
80
|
+
const pw = visibleWidth(p);
|
|
81
|
+
const semEspaco = visibleWidth(p.replace(/ +$/, ''));
|
|
82
|
+
if (larg + semEspaco <= w) { atual += p; larg += pw; corDe(p); continue; }
|
|
83
|
+
if (semEspaco <= w && larg > 0) { novaLinha(); atual += p; larg = pw; corDe(p); continue; }
|
|
84
|
+
// A palavra não cabe nem sozinha: vai caractere a caractere.
|
|
85
|
+
const re = /\x1b\[[0-9;]*m/y;
|
|
86
|
+
for (let i = 0; i < p.length;) {
|
|
87
|
+
re.lastIndex = i;
|
|
88
|
+
const m = re.exec(p);
|
|
89
|
+
if (m) { atual += m[0]; corDe(m[0]); i += m[0].length; continue; }
|
|
90
|
+
const ch = String.fromCodePoint(p.codePointAt(i));
|
|
91
|
+
const cw = visibleWidth(ch);
|
|
92
|
+
if (larg + cw > w && larg > 0) novaLinha();
|
|
93
|
+
atual += ch; larg += cw; i += ch.length;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
out.push(atual + (ativo ? RESET : ''));
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Preenche até `w` colunas (ou corta), com o fundo opcional até o fim. */
|
|
101
|
+
function caber(s, w, fundo) {
|
|
102
|
+
const v = visibleWidth(s);
|
|
103
|
+
if (v > w) s = truncate(s, w);
|
|
104
|
+
const falta = Math.max(0, w - visibleWidth(s));
|
|
105
|
+
if (!fundo) return s + ' '.repeat(falta);
|
|
106
|
+
return fundo + String(s).split(RESET).join(RESET + fundo) + ' '.repeat(falta) + RESET;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ── A barra da direita ──────────────────────────────────────────────── */
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {object} lat { titulo, secoes: [{titulo, linhas}], rodape: [] }
|
|
113
|
+
* @param {number} alto quantas linhas a tela tem
|
|
114
|
+
*/
|
|
115
|
+
function lateral(lat, alto) {
|
|
116
|
+
const w = LATERAL_W - 4; // 2 de margem de cada lado
|
|
117
|
+
const corpo = [''];
|
|
118
|
+
for (const l of quebrarComCor(c.bold(c.white(lat.titulo || 'Nova sessão')), w)) corpo.push(l);
|
|
119
|
+
for (const s of (lat.secoes || [])) {
|
|
120
|
+
corpo.push('');
|
|
121
|
+
corpo.push(c.bold(c.white(s.titulo)));
|
|
122
|
+
for (const l of s.linhas) corpo.push(...quebrarComCor(l, w));
|
|
123
|
+
}
|
|
124
|
+
const rodape = (lat.rodape || []).slice();
|
|
125
|
+
rodape.push('');
|
|
126
|
+
const linhas = [];
|
|
127
|
+
for (let i = 0; i < alto; i++) {
|
|
128
|
+
const deBaixo = alto - i - 1;
|
|
129
|
+
let l = '';
|
|
130
|
+
if (deBaixo < rodape.length) l = rodape[rodape.length - 1 - deBaixo];
|
|
131
|
+
else if (i < corpo.length) l = corpo[i];
|
|
132
|
+
linhas.push(caber(' ' + caber(l, w) + ' ', LATERAL_W, FUNDO_LATERAL));
|
|
133
|
+
}
|
|
134
|
+
return linhas;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ── O quadro inteiro ─────────────────────────────────────────────────── */
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {object} e
|
|
141
|
+
* @param {number} e.colunas, e.linhas o tamanho do terminal
|
|
142
|
+
* @param {string[]} e.conversa as linhas lógicas escritas até aqui
|
|
143
|
+
* @param {number} e.rolagem quantas linhas acima do fim (0 = colado embaixo)
|
|
144
|
+
* @param {boolean} e.home ainda não houve pedido: logo no meio
|
|
145
|
+
* @param {(colunas:number, centro:boolean)=>{linhas:string[], cursorLinha:number, cursorColuna:number}} e.caixa
|
|
146
|
+
* @param {object} e.lateral o conteúdo da barra da direita
|
|
147
|
+
* @param {string} e.versao, e.pasta o rodapé da home
|
|
148
|
+
* @returns {{linhas: string[], cursor: {linha:number, coluna:number}, rolagemMax: number}}
|
|
149
|
+
*/
|
|
150
|
+
function quadro(e) {
|
|
151
|
+
const W = Math.max(30, e.colunas || 80);
|
|
152
|
+
const H = Math.max(10, e.linhas || 24);
|
|
153
|
+
|
|
154
|
+
if (e.home) {
|
|
155
|
+
const caixa = e.caixa(W, true);
|
|
156
|
+
const lg = W >= 50 ? logo() : [c.bold(c.white('primocode'))];
|
|
157
|
+
/* O painel fica PARADO no lugar dele: o menu, quando abre, cobre o
|
|
158
|
+
que está em cima (o logo), em vez de empurrar tudo para cima. */
|
|
159
|
+
const menu = Math.max(0, caixa.linhas.findIndex((l) => l.includes('┃')) - (/┃/.test(caixa.linhas[0]) ? 0 : 0));
|
|
160
|
+
const bloco = lg.length + 2 + caixa.linhas.length - menu;
|
|
161
|
+
const topo = Math.max(0, Math.floor((H - bloco) / 2) - 1);
|
|
162
|
+
const linhas = [];
|
|
163
|
+
for (let i = 0; i < topo; i++) linhas.push('');
|
|
164
|
+
for (const l of lg) linhas.push(' '.repeat(Math.max(0, Math.floor((W - visibleWidth(l)) / 2))) + l);
|
|
165
|
+
linhas.push('', '');
|
|
166
|
+
const inicioCaixa = Math.max(0, linhas.length - menu);
|
|
167
|
+
linhas.length = inicioCaixa;
|
|
168
|
+
linhas.push(...caixa.linhas);
|
|
169
|
+
while (linhas.length < H - 1) linhas.push('');
|
|
170
|
+
linhas.length = H - 1;
|
|
171
|
+
const esq = ' ' + c.dim(e.pasta || '');
|
|
172
|
+
const dir = c.dim(e.versao || '') + ' ';
|
|
173
|
+
linhas.push(esq + ' '.repeat(Math.max(1, W - visibleWidth(esq) - visibleWidth(dir))) + dir);
|
|
174
|
+
return {
|
|
175
|
+
linhas: linhas.map((l) => caber(l, W)),
|
|
176
|
+
cursor: { linha: inicioCaixa + caixa.cursorLinha, coluna: caixa.cursorColuna },
|
|
177
|
+
rolagemMax: 0,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const comLateral = W >= LATERAL_MIN;
|
|
182
|
+
const mainW = comLateral ? W - LATERAL_W : W;
|
|
183
|
+
/* Uma coluna de margem dos dois lados, para a conversa e para a caixa: o
|
|
184
|
+
pedido mandado (um painel) e a caixa de digitar ficam na MESMA borda. */
|
|
185
|
+
const caixa0 = e.caixa(mainW - 1, false);
|
|
186
|
+
const caixa = { ...caixa0, linhas: caixa0.linhas.map((l) => ' ' + l), cursorColuna: caixa0.cursorColuna + 1 };
|
|
187
|
+
const altoConversa = Math.max(1, H - caixa.linhas.length);
|
|
188
|
+
|
|
189
|
+
// A conversa, quebrada na largura da coluna, com 2 colunas de margem.
|
|
190
|
+
const visiveis = [];
|
|
191
|
+
for (const l of (e.conversa || [])) visiveis.push(...quebrarComCor(l, mainW - 2));
|
|
192
|
+
while (visiveis.length && !temTexto(visiveis[visiveis.length - 1])) visiveis.pop();
|
|
193
|
+
const rolagemMax = Math.max(0, visiveis.length - altoConversa);
|
|
194
|
+
const rolagem = Math.max(0, Math.min(e.rolagem | 0, rolagemMax));
|
|
195
|
+
const fim = visiveis.length - rolagem;
|
|
196
|
+
const janela = visiveis.slice(Math.max(0, fim - altoConversa), fim);
|
|
197
|
+
// A conversa encosta EMBAIXO, logo acima da caixa — como num chat.
|
|
198
|
+
const conversa = [];
|
|
199
|
+
for (let i = janela.length; i < altoConversa; i++) conversa.push('');
|
|
200
|
+
for (const l of janela) conversa.push(' ' + l);
|
|
201
|
+
if (rolagem > 0) {
|
|
202
|
+
conversa[0] = ' ' + c.dim(`↑ ${rolagem} linha${rolagem > 1 ? 's' : ''} abaixo · PgDn ou a roda do mouse para voltar`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const esquerda = [...conversa, ...caixa.linhas].map((l) => caber(l, mainW));
|
|
206
|
+
const direita = comLateral ? lateral(e.lateral || {}, H) : null;
|
|
207
|
+
const linhas = esquerda.map((l, i) => (direita ? l + direita[i] : l));
|
|
208
|
+
return {
|
|
209
|
+
linhas,
|
|
210
|
+
cursor: { linha: altoConversa + caixa.cursorLinha, coluna: caixa.cursorColuna },
|
|
211
|
+
rolagemMax,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function temTexto(l) { return visibleWidth(l) > 0 && /\S/.test(l.replace(/\x1b\[[0-9;]*m/g, '')); }
|
|
216
|
+
|
|
217
|
+
/** A largura que o texto da conversa tem, numa tela de `colunas`. */
|
|
218
|
+
function larguraDaConversa(colunas) {
|
|
219
|
+
const W = Math.max(30, colunas || 80);
|
|
220
|
+
return (W >= LATERAL_MIN ? W - LATERAL_W : W) - 2;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = { larguraDaConversa, quadro, acrescentar, quebrarComCor, lateral, caber, LATERAL_W, LATERAL_MIN };
|
package/lib/ui.js
CHANGED
|
@@ -298,103 +298,85 @@ function truncate(s, max) {
|
|
|
298
298
|
|
|
299
299
|
const padRight = (s, w) => s + ' '.repeat(Math.max(0, w - visibleWidth(s)));
|
|
300
300
|
|
|
301
|
-
// ──
|
|
302
|
-
//
|
|
301
|
+
// ── O convite da caixa ───────────────────────────────────────────────────
|
|
302
|
+
// Como no OpenCode ("Ask anything… \"Fix a TODO\""): a caixa vazia mostra um
|
|
303
|
+
// exemplo de pedido. Um por abertura, sorteado, para quem chega aprender o
|
|
304
|
+
// que dá para pedir sem ler lista nenhuma.
|
|
305
|
+
const EXEMPLOS = [
|
|
306
|
+
'um vídeo 3D de dois personagens conversando',
|
|
307
|
+
'um site de portfólio com animação no scroll',
|
|
308
|
+
'um motion de 20 segundos explicando o que é API',
|
|
309
|
+
'clona github.com/vercel/next.js e me explica a pasta app',
|
|
310
|
+
'tira o logo do site x.com com o fundo transparente',
|
|
311
|
+
];
|
|
312
|
+
function convite() {
|
|
313
|
+
return `Peça qualquer coisa… "${EXEMPLOS[Math.floor(Math.random() * EXEMPLOS.length)]}"`;
|
|
314
|
+
}
|
|
303
315
|
|
|
304
|
-
|
|
316
|
+
// ── Cabeçalho ────────────────────────────────────────────────────────────
|
|
317
|
+
// O desenho do OpenCode, que é a referência pedida: o nome grande no meio da
|
|
318
|
+
// tela, em letra de pixel, com a primeira metade apagada e a segunda acesa —
|
|
319
|
+
// e nada mais. Dicas, novidades e o mascote saíram da abertura: ao lado da
|
|
320
|
+
// caixa de digitar no centro, qualquer texto a mais é o que o olho lê
|
|
321
|
+
// primeiro, e o que se quer que ele leia é a caixa.
|
|
322
|
+
//
|
|
323
|
+
// A letra é um bitmap de 8 linhas de pixel, juntadas de duas em duas com os
|
|
324
|
+
// meios-blocos ▀ ▄ █: 4 linhas de terminal, largura 1 garantida em qualquer
|
|
325
|
+
// fonte. Linha 0 é a haste de cima (d, pingo do i), 1–5 o corpo, 6 a perna
|
|
326
|
+
// do p.
|
|
327
|
+
const LETRAS = {
|
|
328
|
+
p: [' ', '####', '# #', '# #', '####', '# ', '# ', ' '],
|
|
329
|
+
r: [' ', '####', '# ', '# ', '# ', '# ', ' ', ' '],
|
|
330
|
+
i: ['#', ' ', '#', '#', '#', '#', ' ', ' '],
|
|
331
|
+
m: [' ', '#####', '# # #', '# # #', '# # #', '# # #', ' ', ' '],
|
|
332
|
+
o: [' ', '####', '# #', '# #', '# #', '####', ' ', ' '],
|
|
333
|
+
c: [' ', '####', '# ', '# ', '# ', '####', ' ', ' '],
|
|
334
|
+
d: [' #', '####', '# #', '# #', '# #', '####', ' ', ' '],
|
|
335
|
+
e: [' ', '####', '# #', '####', '# ', '####', ' ', ' '],
|
|
336
|
+
};
|
|
305
337
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
];
|
|
338
|
+
/** Uma palavra em letra de pixel: 4 linhas de texto, sem cor. */
|
|
339
|
+
function letreiro(palavra) {
|
|
340
|
+
const px = [];
|
|
341
|
+
for (let y = 0; y < 8; y++) px.push([...palavra].map((ch) => LETRAS[ch][y]).join(' '));
|
|
342
|
+
const out = [];
|
|
343
|
+
for (let y = 0; y < 8; y += 2) {
|
|
344
|
+
let l = '';
|
|
345
|
+
for (let x = 0; x < px[y].length; x++) {
|
|
346
|
+
const a = px[y][x] === '#', b = px[y + 1][x] === '#';
|
|
347
|
+
l += a && b ? '█' : a ? '▀' : b ? '▄' : ' ';
|
|
348
|
+
}
|
|
349
|
+
out.push(l);
|
|
350
|
+
}
|
|
351
|
+
return out;
|
|
352
|
+
}
|
|
311
353
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
];
|
|
354
|
+
/** O logo: "primo" apagado e "code" aceso, lado a lado. */
|
|
355
|
+
function logo() {
|
|
356
|
+
const a = letreiro('primo'), b = letreiro('code');
|
|
357
|
+
return a.map((l, i) => `\x1b[38;5;244m${l}${PALETTE.reset} ${PALETTE.bold}\x1b[38;5;255m${b[i]}${PALETTE.reset}`);
|
|
358
|
+
}
|
|
359
|
+
const LOGO_W = visibleWidth(logo()[0]);
|
|
319
360
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
361
|
+
/** Centraliza uma linha (com cor) em `cols` colunas. */
|
|
362
|
+
function centro(s, cols) {
|
|
363
|
+
return ' '.repeat(Math.max(0, Math.floor((cols - visibleWidth(s)) / 2))) + s;
|
|
364
|
+
}
|
|
324
365
|
|
|
325
366
|
function welcomeBox(version, contexto, opts = {}) {
|
|
326
|
-
const {
|
|
327
|
-
const
|
|
328
|
-
const cols = Math.max(32, Math.min(process.stdout.columns || 100, 120));
|
|
329
|
-
|
|
330
|
-
// O bordão do mascote vem na mesma linha da saudação: é ele quem cumprimenta.
|
|
331
|
-
// Em terminal estreito o truncate corta o bordão antes do nome.
|
|
332
|
-
const info = mascoteInfo(style);
|
|
333
|
-
const saudacao = (userName
|
|
334
|
-
? `${c.muted('Bem-vindo de volta, ')}${c.bold(c.white(userName))}`
|
|
335
|
-
: c.muted('Bem-vindo de volta'))
|
|
336
|
-
+ c.dim(` · ${info.nome}: "${info.frase}"`);
|
|
337
|
-
|
|
338
|
-
// As três linhas de identidade — uma por linha do mascote, na ordem em que
|
|
339
|
-
// se lê: quem é, quem chegou, onde estamos.
|
|
340
|
-
// A pasta é o que mais confundia quem usa, então ela avisa de si mesma — na
|
|
341
|
-
// MESMA linha de sempre, sem mudar o desenho do cabeçalho. Amber quando é a
|
|
342
|
-
// pasta padrão (é um aviso, não uma falha: dá para trabalhar em outra), e a
|
|
343
|
-
// primária quando foi você que escolheu — aí não há nada a avisar.
|
|
344
|
-
const linhaPasta = !contexto
|
|
345
|
-
? c.dim('nenhuma pasta ativa')
|
|
346
|
-
: pastaPadrao
|
|
347
|
-
? `${c.warn(contexto)}${c.dim(' · /dir <pasta> muda')}`
|
|
348
|
-
: c.brandDim(contexto);
|
|
349
|
-
|
|
350
|
-
const ident = [
|
|
351
|
-
`${c.bold(c.brand(NOME))} ${c.dim('v' + version)}`,
|
|
352
|
-
saudacao,
|
|
353
|
-
linhaPasta,
|
|
354
|
-
];
|
|
355
|
-
|
|
367
|
+
const { pastaPadrao } = opts;
|
|
368
|
+
const cols = Math.max(32, Math.min(process.stdout.columns || 100, 200));
|
|
356
369
|
const out = [''];
|
|
357
370
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (largo) {
|
|
361
|
-
const textW = cols - ART_W - 2;
|
|
362
|
-
for (let i = 0; i < ART_H; i++) {
|
|
363
|
-
out.push(art[i] + ' ' + truncate(ident[i] || '', textW));
|
|
364
|
-
}
|
|
365
|
-
} else {
|
|
366
|
-
out.push(...art);
|
|
367
|
-
out.push('');
|
|
368
|
-
out.push(...ident.map((l) => truncate(l, cols)));
|
|
369
|
-
}
|
|
371
|
+
if (cols >= LOGO_W + 2) out.push(...logo().map((l) => centro(l, cols)));
|
|
372
|
+
else out.push(centro(`${c.dim('primo')}${c.bold(c.white('code'))}`, cols));
|
|
370
373
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
out.push('');
|
|
375
|
-
out.push(' ' + c.bold(c.white(titulo)));
|
|
376
|
-
itens.forEach((t, i) => {
|
|
377
|
-
out.push(` ${c.dim(marca(i))} ${truncate(c.muted(t), cols - 5)}`);
|
|
378
|
-
});
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
secao('Dicas para começar', TIPS, (i) => `${i + 1}.`);
|
|
382
|
-
secao('Novidades', NEWS, () => ' ·');
|
|
383
|
-
|
|
384
|
-
// Rodapé: em que modo você está agora, não a lista dos que existem. Em
|
|
385
|
-
// terminal apertado a dica do /help sai antes de empurrar a linha para
|
|
386
|
-
// baixo — cabeçalho que quebra parece defeito.
|
|
387
|
-
// Primária para o modo ativo, secundária quando ele tem poder de escrever:
|
|
388
|
-
// é a mesma leitura de "apurado" × "aviso que não é falha" do sistema.
|
|
389
|
-
const rotulos = {
|
|
390
|
-
auto: `${c.brand('●')} ${c.muted('Auto')}${c.dim(' — conversa vai pro chat, tarefa vai pro agente')}`,
|
|
391
|
-
primocode: `${c.warn('●')} ${c.muted('Primo Code')}${c.dim(' — agente completo, com ferramentas')}`,
|
|
392
|
-
chat: `${c.brandDim('●')} ${c.muted('Chat')}${c.dim(' — conversa pura, sem ferramentas')}`,
|
|
393
|
-
};
|
|
394
|
-
const linhaModo = ' ' + (rotulos[modo] || rotulos.auto);
|
|
374
|
+
/* A pasta só fala quando é a padrão. Era o que mais confundia quem usa
|
|
375
|
+
("onde ele salvou?"), e é aviso, não falha: âmbar, uma linha. Quando foi
|
|
376
|
+
a pessoa que escolheu, a pasta já está no rodapé da caixa. */
|
|
395
377
|
out.push('');
|
|
396
|
-
out.push(
|
|
397
|
-
|
|
378
|
+
if (contexto && pastaPadrao) out.push(centro(`${c.warn(contexto)}${c.dim(' · /dir <pasta> muda')}`, cols));
|
|
379
|
+
out.push(centro(c.dim('v' + version), cols));
|
|
398
380
|
out.push('');
|
|
399
381
|
return out.join('\n');
|
|
400
382
|
}
|
|
@@ -651,4 +633,4 @@ function friendlyError(raw) {
|
|
|
651
633
|
|
|
652
634
|
|
|
653
635
|
module.exports = {
|
|
654
|
-
titulo, c, mascot, MASCOTES, resolverMascote, mascoteInfo, galeriaMascotes, reacao, welcomeBox, Spinner, PENSANDO, actionHeader, friendlyError, visibleWidth, stripAnsi, truncate, padRight };
|
|
636
|
+
titulo, c, logo, 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.
|
|
3
|
+
"version": "10.4.1",
|
|
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"
|
|
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"
|