primocode 8.15.0 → 8.17.0

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/bin/primocode.js CHANGED
@@ -142,142 +142,6 @@ function completarSlash(line) {
142
142
  // O layout sai em texto PLANO, com as marcas de onde pintar. Assim os dois
143
143
  // pintores (o normal e o do ultracode animado) desenham exatamente a mesma
144
144
  // régua — sem duas contas de posição para manter em sincronia.
145
- function layoutEffort(idx) {
146
- const cols = Math.max(52, Math.min(process.stdout.columns || 100, 104));
147
- const rotulos = effort.NOMES;
148
- const larguras = rotulos.map((n) => Math.max(n.length + 3, 9));
149
- const posicoes = [];
150
- let x = 23; // recuo da régua, igual ao do mockup
151
- for (let i = 0; i < rotulos.length; i++) {
152
- posicoes.push(x + Math.floor(larguras[i] / 2));
153
- x += larguras[i];
154
- }
155
- const fimRegua = x;
156
- const marca = posicoes[idx];
157
-
158
- let trilha = '';
159
- for (let k = 23; k < fimRegua; k++) {
160
- if (k === marca) trilha += '▲';
161
- else if (k === fimRegua - 12) trilha += '┆'; // ultracode é um degrau à parte
162
- else trilha += '─';
163
- }
164
-
165
- let nomes = ' '.repeat(23);
166
- let deNome = 0, ateNome = 0;
167
- for (let i = 0; i < rotulos.length; i++) {
168
- const nome = rotulos[i];
169
- const pad = Math.floor((larguras[i] - nome.length) / 2);
170
- if (i === idx) { deNome = nomes.length + pad; ateNome = deNome + nome.length; }
171
- nomes += ' '.repeat(pad) + nome + ' '.repeat(larguras[i] - nome.length - pad);
172
- }
173
-
174
- const nivel = effort.nivelDe(rotulos[idx]);
175
- const vaoExtremos = Math.max(1, fimRegua - 23 - 6 - 7);
176
-
177
- return {
178
- cols,
179
- marca,
180
- destaque: { de: deNome, ate: ateNome },
181
- linhas: [
182
- { tipo: 'regua', texto: '▔'.repeat(cols) },
183
- { tipo: 'titulo', texto: ' Effort' },
184
- { tipo: 'vazio', texto: '' },
185
- { tipo: 'extremos', texto: ' '.repeat(23) + 'Faster' + ' '.repeat(vaoExtremos) + 'Smarter' },
186
- { tipo: 'trilha', texto: ' '.repeat(23) + trilha },
187
- { tipo: 'nomes', texto: nomes },
188
- { tipo: 'resumo', texto: ' '.repeat(23) + nivel.resumo },
189
- { tipo: 'vazio', texto: '' },
190
- { tipo: 'ajuda', texto: ' ←/→ ajusta · Enter confirma · Esc cancela' },
191
- ],
192
- };
193
- }
194
-
195
- // ── O brilho do ultracode ────────────────────────────────────────────────
196
- // No nível máximo o painel inteiro ganha um fundo roxo que ondula, como no
197
- // Claude Code. São faixas verticais de roxo em brilhos diferentes; a onda
198
- // anda para o lado a cada quadro, e o texto fica branco por cima.
199
- //
200
- // Roxos do cubo de 256 cores, ordenados pelo brilho REAL (luminância), do
201
- // mais escuro ao mais claro — a onda escolhe um deles por faixa. Fora da
202
- // lista ficaram os roxos escuros que puxam para o vinho (53-56): quebravam a
203
- // família violeta. Só 256 cores (não truecolor): funciona em qualquer
204
- // terminal, inclusive no Terminal.app do macOS.
205
- const ROXOS = [57, 92, 93, 63, 98, 99, 105, 141];
206
-
207
- function fundoRoxo(x, fase) {
208
- // Duas ondas de períodos diferentes: o encontro delas dá o desenho
209
- // irregular de faixas, em vez de listras repetidas e óbvias.
210
- const faixa = Math.floor(x / 2);
211
- const onda = Math.sin(faixa * 0.55 + fase) * 0.6 + Math.sin(faixa * 0.17 - fase * 0.7) * 0.4;
212
- const i = Math.round((onda + 1) / 2 * (ROXOS.length - 1));
213
- return ROXOS[Math.max(0, Math.min(ROXOS.length - 1, i))];
214
- }
215
-
216
- // Pinta a linha inteira (até a largura do painel) com a onda por baixo.
217
- // `destaque` é o trecho que fica branco e forte (o nível escolhido); o resto
218
- // da linha sai num lilás claro, para o escolhido saltar aos olhos.
219
- function pintarLinhaUltra(texto, largura, fase, opts = {}) {
220
- const base = opts.base || 231; // branco
221
- const apagado = opts.apagado || base;
222
- const destaque = opts.destaque;
223
- let saida = '';
224
- let corFundo = -1;
225
- let corTexto = -1;
226
- let forte = null;
227
-
228
- for (let x = 0; x < largura; x++) {
229
- const fundo = fundoRoxo(x, fase);
230
- if (fundo !== corFundo) { saida += `\x1b[48;5;${fundo}m`; corFundo = fundo; }
231
-
232
- const dentro = destaque && x >= destaque.de && x < destaque.ate;
233
- const querForte = opts.negrito || dentro;
234
- const querCor = dentro ? base : apagado;
235
- if (querForte !== forte) { saida += querForte ? '\x1b[1m' : '\x1b[22m'; forte = querForte; }
236
- if (querCor !== corTexto) { saida += `\x1b[38;5;${querCor}m`; corTexto = querCor; }
237
-
238
- saida += texto[x] || ' ';
239
- }
240
- return saida + '\x1b[0m';
241
- }
242
-
243
- function desenharEffort(idx, fase) {
244
- const L = layoutEffort(idx);
245
- const ultra = effort.NOMES[idx] === 'ultracode';
246
-
247
- if (ultra) {
248
- // No ultracode o painel todo vira fundo animado. Cada linha é pintada
249
- // com a MESMA onda, então as faixas ficam alinhadas de cima a baixo.
250
- const LILAS = 189; // lilás bem claro, para o que não é o escolhido
251
- return L.linhas.map((l) => {
252
- if (l.tipo === 'titulo') return pintarLinhaUltra(l.texto, L.cols, fase, { negrito: true });
253
- if (l.tipo === 'nomes') return pintarLinhaUltra(l.texto, L.cols, fase, { destaque: L.destaque, apagado: LILAS });
254
- if (l.tipo === 'resumo' || l.tipo === 'extremos') return pintarLinhaUltra(l.texto, L.cols, fase, { base: LILAS, apagado: LILAS });
255
- return pintarLinhaUltra(l.texto, L.cols, fase, {});
256
- }).join('\n');
257
- }
258
-
259
- return L.linhas.map((l) => {
260
- const t = l.texto;
261
- switch (l.tipo) {
262
- case 'regua': return c.dim(t);
263
- case 'titulo': return c.bold(c.white(t));
264
- case 'extremos': return c.muted(t);
265
- case 'trilha': {
266
- // O marcador aceso, o resto da trilha recuada.
267
- const antes = t.slice(0, L.marca);
268
- const depois = t.slice(L.marca + 1);
269
- return c.dim(antes) + c.brand('▲') + c.dim(depois);
270
- }
271
- case 'nomes': {
272
- const { de, ate } = L.destaque;
273
- return c.muted(t.slice(0, de)) + c.bold(c.brand(t.slice(de, ate))) + c.muted(t.slice(ate));
274
- }
275
- case 'resumo': return c.dim(t);
276
- case 'ajuda': return c.muted(t);
277
- default: return t;
278
- }
279
- }).join('\n');
280
- }
281
145
 
282
146
  // Desenha, escuta as setas, e devolve o nome escolhido (ou null se cancelou).
283
147
  function sliderEffort(atual) {
@@ -293,7 +157,7 @@ function sliderEffort(atual) {
293
157
 
294
158
  const pintar = (primeira) => {
295
159
  if (!primeira) process.stdout.write(`\x1b[${ALTURA}A\r`);
296
- process.stdout.write(desenharEffort(idx, fase).split('\n').map((l) => '\x1b[2K' + l).join('\n') + '\n');
160
+ process.stdout.write(effort.desenharEffort(idx, fase).split('\n').map((l) => '\x1b[2K' + l).join('\n') + '\n');
297
161
  };
298
162
 
299
163
  // O fundo do ultracode ondula; sair dele apaga a animação na hora, para
package/lib/effort.js CHANGED
@@ -48,4 +48,289 @@ function pesquisaDe(nome) {
48
48
  return nivelDe(nome).pesquisa;
49
49
  }
50
50
 
51
- module.exports = { NIVEIS, NOMES, normalizar, nivelDe, indiceDe, modeloDe, pesquisaDe };
51
+
52
+ const { c } = require('./ui.js');
53
+
54
+ /* ═══════════════════════════════════════════════════════════════════════
55
+ O painel do /effort — e o fundo que ondula no ultracode.
56
+
57
+ Isto morava dentro do bin/, junto com o loop do programa. Lá não dava
58
+ para desenhar um quadro e OLHAR: o bin sobe o CLI inteiro quando é
59
+ carregado. Aqui é função pura — recebe o índice e a fase, devolve texto
60
+ com cor. Dá para renderizar quadro a quadro num teste, que é como a
61
+ animação foi conferida.
62
+ ═══════════════════════════════════════════════════════════════════════ */
63
+
64
+ function layoutEffort(idx) {
65
+ // Sem teto de largura: o fundo do ultracode preenche o terminal inteiro.
66
+ // Travado em 104 colunas, o painel parava no meio de uma tela larga e o
67
+ // bloco roxo ficava com um degrau escuro do lado — parecia recorte, não
68
+ // fundo. A régua e os rótulos continuam no mesmo lugar; só o pano cresce.
69
+ const cols = Math.max(52, process.stdout.columns || 100);
70
+ const rotulos = NOMES;
71
+ const larguras = rotulos.map((n) => Math.max(n.length + 3, 9));
72
+ const posicoes = [];
73
+ let x = 23; // recuo da régua, igual ao do mockup
74
+ for (let i = 0; i < rotulos.length; i++) {
75
+ posicoes.push(x + Math.floor(larguras[i] / 2));
76
+ x += larguras[i];
77
+ }
78
+ const fimRegua = x;
79
+ const marca = posicoes[idx];
80
+
81
+ let trilha = '';
82
+ for (let k = 23; k < fimRegua; k++) {
83
+ if (k === marca) trilha += '▲';
84
+ else if (k === fimRegua - 12) trilha += '┆'; // ultracode é um degrau à parte
85
+ else trilha += '─';
86
+ }
87
+
88
+ let nomes = ' '.repeat(23);
89
+ let deNome = 0, ateNome = 0;
90
+ for (let i = 0; i < rotulos.length; i++) {
91
+ const nome = rotulos[i];
92
+ const pad = Math.floor((larguras[i] - nome.length) / 2);
93
+ if (i === idx) { deNome = nomes.length + pad; ateNome = deNome + nome.length; }
94
+ nomes += ' '.repeat(pad) + nome + ' '.repeat(larguras[i] - nome.length - pad);
95
+ }
96
+
97
+ const nivel = nivelDe(rotulos[idx]);
98
+ const vaoExtremos = Math.max(1, fimRegua - 23 - 6 - 7);
99
+
100
+ return {
101
+ cols,
102
+ marca,
103
+ destaque: { de: deNome, ate: ateNome },
104
+ linhas: [
105
+ { tipo: 'regua', texto: '▔'.repeat(cols) },
106
+ { tipo: 'titulo', texto: ' Effort' },
107
+ { tipo: 'vazio', texto: '' },
108
+ { tipo: 'extremos', texto: ' '.repeat(23) + 'Faster' + ' '.repeat(vaoExtremos) + 'Smarter' },
109
+ { tipo: 'trilha', texto: ' '.repeat(23) + trilha },
110
+ { tipo: 'nomes', texto: nomes },
111
+ { tipo: 'resumo', texto: ' '.repeat(23) + nivel.resumo },
112
+ { tipo: 'vazio', texto: '' },
113
+ { tipo: 'ajuda', texto: ' ←/→ ajusta · Enter confirma · Esc cancela' },
114
+ ],
115
+ };
116
+ }
117
+
118
+ // ── O brilho do ultracode ────────────────────────────────────────────────
119
+ // No nível máximo o painel inteiro ganha um fundo roxo que ondula, como no
120
+ // Claude Code. São faixas verticais de roxo em brilhos diferentes; a onda
121
+ // anda para o lado a cada quadro, e o texto fica branco por cima.
122
+ //
123
+ // Roxos do cubo de 256 cores, ordenados pelo brilho REAL (luminância), do
124
+ // mais escuro ao mais claro — a onda escolhe um deles por faixa. Fora da
125
+ // lista ficaram os roxos escuros que puxam para o vinho (53-56): quebravam a
126
+ // família violeta. Só 256 cores (não truecolor): funciona em qualquer
127
+ // terminal, inclusive no Terminal.app do macOS.
128
+ // A rampa é ESTREITA de propósito: só violetas vizinhos, do escuro ao médio.
129
+ //
130
+ // A lista antiga ia de 57 a 141 e passava por 63 (azul) e 92 (magenta
131
+ // escuro). Vizinhas na tela, essas cores não leem como uma superfície só:
132
+ // leem como listras — e listra clara demais apaga o texto branco por cima.
133
+ // O fundo tem de parecer um pano roxo respirando atrás do painel, não uma
134
+ // bandeira. Os tons claros ficam de fora por isso.
135
+ // A rampa do plasma, do fundo frio ao branco quente.
136
+ //
137
+ // Ela é LONGA de propósito: o degradê é desenhado em meia-célula (▀), então
138
+ // há o dobro de linhas para atravessar, e uma rampa curta viraria degrau
139
+ // visível. Começa quase preta e termina em branco — é a amplitude que faz o
140
+ // pico parecer luz, e não só "roxo mais claro".
141
+ // Começa em roxo PROFUNDO, não em preto.
142
+ //
143
+ // A primeira rampa abria em 16 e 17 (preto e azul-marinho). Os vales da onda
144
+ // caíam neles e viravam buracos escuros furando o pano — parecia defeito de
145
+ // renderização, não sombra. Agora o mais fundo ainda é roxo: o painel inteiro
146
+ // é uma coisa só, do vale ao pico.
147
+ const ROXOS = [53, 54, 54, 55, 56, 57, 93, 129, 135, 141, 177, 183, 219, 225, 231];
148
+
149
+ // Quanto o plasma é amortecido nas linhas que têm texto. Pico branco atrás
150
+ // de letra branca some com a letra, e o painel existe para ser lido — o
151
+ // brilho fica para as faixas abertas, logo acima e abaixo.
152
+ const TETO_SOB_TEXTO = 0.5;
153
+
154
+ /* O campo: o que decide a cor de cada meia-célula.
155
+ *
156
+ * Três coisas somadas, e é a soma que dá a sensação de energia em vez de
157
+ * listra:
158
+ *
159
+ * 1. uma ONDA DIAGONAL atravessando o painel, devagar;
160
+ * 2. um PULSO RADIAL saindo do próprio rótulo "ultracode" — é o detalhe
161
+ * que amarra a animação ao que ela significa: a potência irradia de
162
+ * onde você está;
163
+ * 3. uma segunda onda lenta, fora de fase, que impede o padrão de se
164
+ * repetir de forma óbvia.
165
+ *
166
+ * `x` é a coluna, `y` a meia-linha (duas por linha de texto), `foco` a
167
+ * coluna do rótulo selecionado.
168
+ */
169
+ function campoUltra(x, y, fase, foco, altura) {
170
+ // O terminal tem célula alta e estreita: sem corrigir, o pulso radial
171
+ // sairia como uma elipse achatada em vez de círculo.
172
+ const dx = (x - foco) * 0.5;
173
+ const dy = (y - altura / 2) * 1.1;
174
+ const raio = Math.sqrt(dx * dx + dy * dy);
175
+
176
+ const diagonal = Math.sin((x * 0.11) + (y * 0.30) + fase * 1.15);
177
+ const pulso = Math.sin(raio * 0.42 - fase * 2.1) * Math.exp(-raio * 0.035);
178
+ const lenta = Math.sin(x * 0.037 - fase * 0.5 + Math.cos(y * 0.19) * 0.8);
179
+
180
+ const v = diagonal * 0.42 + pulso * 0.78 + lenta * 0.34;
181
+ // O divisor maior que a amplitude puxa a média para baixo: o painel fica
182
+ // a maior parte do tempo em roxo fundo, e o branco vira PICO — evento,
183
+ // não estado. Centrado, o branco virava o normal e não impressionava
184
+ // mais nada.
185
+ return Math.max(0, Math.min(1, (v + 1.15) / 2.75));
186
+ }
187
+
188
+ // `escala` escurece a linha SEM achatá-la.
189
+ //
190
+ // A primeira versão usava Math.min(v, teto): tudo acima do teto virava a
191
+ // mesma cor, e as linhas de texto ganhavam platôs chapados — retângulos
192
+ // visíveis atravessando o plasma. Multiplicar preserva a forma da onda e só
193
+ // baixa a amplitude: a linha fica escura o bastante para ler e continua
194
+ // fazendo parte do mesmo pano.
195
+ function corDoCampo(v, escala) {
196
+ const t = escala ? v * escala : v;
197
+ const i = Math.round(t * (ROXOS.length - 1));
198
+ return ROXOS[Math.max(0, Math.min(ROXOS.length - 1, i))];
199
+ }
200
+
201
+ // Guardada para quem já usava a onda antiga (e para o teste da rampa).
202
+ function fundoRoxo(x, fase) {
203
+ return corDoCampo(campoUltra(x, 0, fase, 0, 2));
204
+ }
205
+
206
+ function pintarLinhaUltra(texto, largura, fase, opts = {}) {
207
+ const base = opts.base || 231; // branco
208
+ const apagado = opts.apagado || base;
209
+ const destaque = opts.destaque;
210
+ let saida = '';
211
+ let corFundo = -1;
212
+ let corTexto = -1;
213
+ let forte = null;
214
+
215
+ for (let x = 0; x < largura; x++) {
216
+ const fundo = fundoRoxo(x, fase);
217
+ if (fundo !== corFundo) { saida += `\x1b[48;5;${fundo}m`; corFundo = fundo; }
218
+
219
+ const dentro = destaque && x >= destaque.de && x < destaque.ate;
220
+ const querForte = opts.negrito || dentro;
221
+ const querCor = dentro ? base : apagado;
222
+ if (querForte !== forte) { saida += querForte ? '\x1b[1m' : '\x1b[22m'; forte = querForte; }
223
+ if (querCor !== corTexto) { saida += `\x1b[38;5;${querCor}m`; corTexto = querCor; }
224
+
225
+ saida += texto[x] || ' ';
226
+ }
227
+ return saida + '\x1b[0m';
228
+ }
229
+
230
+ /* O painel do ultracode, desenhado em MEIA-CÉLULA.
231
+ *
232
+ * O caractere "▀" ocupa a metade de cima da célula. Pintando a frente dele
233
+ * com uma cor e o fundo com outra, cada linha do terminal carrega DUAS
234
+ * linhas de imagem. É o que separa um degradê em blocos de um degradê que
235
+ * parece luz — e é como demo de terminal sempre fez.
236
+ *
237
+ * Onde há letra, a célula deixa de ser meia e vira fundo liso: não dá para
238
+ * ter o glifo do texto e o do meio-bloco na mesma célula. Aí o fundo é a
239
+ * média das duas metades, limitada para o texto continuar legível.
240
+ */
241
+ function painelUltra(L, fase) {
242
+ const alturaMeia = L.linhas.length * 2;
243
+ const foco = (L.destaque.de + L.destaque.ate) / 2;
244
+ const LILAS = 189;
245
+
246
+ const corDoTexto = (l, x) => {
247
+ if (l.tipo === 'titulo') return { cor: 231, negrito: true };
248
+ if (l.tipo === 'nomes') {
249
+ const dentro = x >= L.destaque.de && x < L.destaque.ate;
250
+ return { cor: dentro ? 231 : LILAS, negrito: dentro };
251
+ }
252
+ if (l.tipo === 'resumo' || l.tipo === 'extremos' || l.tipo === 'ajuda') {
253
+ return { cor: LILAS, negrito: false };
254
+ }
255
+ return { cor: 231, negrito: false };
256
+ };
257
+
258
+ return L.linhas.map((l, y) => {
259
+ // A régua "▔" some no ultra: sobre o plasma ela virava uma listra
260
+ // clara cortando o topo do bloco.
261
+ const texto = l.tipo === 'regua' ? '' : l.texto;
262
+ // Linha COM texto é acalmada inteira, não só debaixo das letras.
263
+ //
264
+ // Limitar só a célula do glifo não bastava: o vão entre as palavras
265
+ // recebia o plasma cheio, e um pico branco encostado na letra some
266
+ // com ela do mesmo jeito. A linha toda vira superfície escura, e o
267
+ // brilho fica para as faixas abertas — que é onde ele aparece sem
268
+ // atrapalhar ninguém.
269
+ const linhaCalma = texto.trim().length > 0 ? TETO_SOB_TEXTO : 0;
270
+ let saida = '';
271
+ let fg = -1, bg = -1, forte = null;
272
+
273
+ for (let x = 0; x < L.cols; x++) {
274
+ const ch = texto[x] || ' ';
275
+ const temLetra = ch !== ' ';
276
+ const cima = campoUltra(x, y * 2, fase, foco, alturaMeia);
277
+ const baixo = campoUltra(x, y * 2 + 1, fase, foco, alturaMeia);
278
+
279
+ let glifo, corFrente, corFundo, querForte;
280
+ if (temLetra) {
281
+ const t = corDoTexto(l, x);
282
+ glifo = ch;
283
+ corFrente = t.cor;
284
+ querForte = t.negrito;
285
+ corFundo = corDoCampo((cima + baixo) / 2, TETO_SOB_TEXTO);
286
+ } else {
287
+ glifo = '▀'; // ▀
288
+ corFrente = corDoCampo(cima, linhaCalma);
289
+ corFundo = corDoCampo(baixo, linhaCalma);
290
+ querForte = false;
291
+ }
292
+
293
+ if (corFundo !== bg) { saida += `\x1b[48;5;${corFundo}m`; bg = corFundo; }
294
+ if (corFrente !== fg) { saida += `\x1b[38;5;${corFrente}m`; fg = corFrente; }
295
+ if (querForte !== forte) { saida += querForte ? '\x1b[1m' : '\x1b[22m'; forte = querForte; }
296
+ saida += glifo;
297
+ }
298
+ return saida + '\x1b[0m';
299
+ }).join('\n');
300
+ }
301
+
302
+ function desenharEffort(idx, fase) {
303
+ const L = layoutEffort(idx);
304
+ const ultra = NOMES[idx] === 'ultracode';
305
+
306
+ if (ultra) {
307
+ return painelUltra(L, fase);
308
+ }
309
+
310
+ return L.linhas.map((l) => {
311
+ const t = l.texto;
312
+ switch (l.tipo) {
313
+ case 'regua': return c.dim(t);
314
+ case 'titulo': return c.bold(c.white(t));
315
+ case 'extremos': return c.muted(t);
316
+ case 'trilha': {
317
+ // O marcador aceso, o resto da trilha recuada.
318
+ const antes = t.slice(0, L.marca);
319
+ const depois = t.slice(L.marca + 1);
320
+ return c.dim(antes) + c.brand('▲') + c.dim(depois);
321
+ }
322
+ case 'nomes': {
323
+ const { de, ate } = L.destaque;
324
+ return c.muted(t.slice(0, de)) + c.bold(c.brand(t.slice(de, ate))) + c.muted(t.slice(ate));
325
+ }
326
+ case 'resumo': return c.dim(t);
327
+ case 'ajuda': return c.muted(t);
328
+ default: return t;
329
+ }
330
+ }).join('\n');
331
+ }
332
+
333
+ module.exports = { NIVEIS, NOMES, normalizar, nivelDe, indiceDe, modeloDe, pesquisaDe,
334
+ layoutEffort, fundoRoxo, pintarLinhaUltra, desenharEffort, ROXOS,
335
+ campoUltra, corDoCampo, painelUltra };
336
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "8.15.0",
3
+ "version": "8.17.0",
4
4
  "description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
5
5
  "main": "bin/primocode.js",
6
6
  "bin": {