primocode 8.42.2 → 9.0.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.
Files changed (102) hide show
  1. package/README.md +41 -37
  2. package/bin/primocode.js +598 -360
  3. package/lib/acervo.js +8 -1
  4. package/lib/act.js +205 -114
  5. package/lib/api.js +35 -4
  6. package/lib/boas-vindas.js +31 -192
  7. package/lib/catalogo.js +60 -60
  8. package/lib/config.js +1 -1
  9. package/lib/conta.js +3 -24
  10. package/lib/diff.js +161 -0
  11. package/lib/diretor.js +37 -47
  12. package/lib/entrada.js +545 -0
  13. package/lib/especialistas.js +2 -2
  14. package/lib/estudio/assets.js +343 -0
  15. package/lib/estudio/identidade.js +560 -0
  16. package/lib/estudio/index.js +565 -0
  17. package/lib/estudio/plano.js +327 -0
  18. package/lib/estudio/projeto.js +246 -0
  19. package/lib/estudio/remotion.js +606 -0
  20. package/lib/estudio/slides.js +190 -0
  21. package/lib/estudio/video.js +296 -0
  22. package/lib/fala.js +13 -1
  23. package/lib/marca.js +4 -1
  24. package/lib/marketplace.js +189 -0
  25. package/lib/permissao.js +137 -0
  26. package/lib/registro.js +229 -0
  27. package/lib/skills.js +329 -0
  28. package/lib/tools.js +51 -181
  29. package/lib/ui.js +7 -3
  30. package/lib/voz.js +239 -0
  31. package/package.json +12 -3
  32. package/skills/acessibilidade/SKILL.md +28 -0
  33. package/skills/api-que-nao-quebra/SKILL.md +27 -0
  34. package/skills/depurar-de-verdade/SKILL.md +29 -0
  35. package/skills/editar-meu-video/SKILL.md +59 -0
  36. package/skills/escrever-em-portugues/SKILL.md +28 -0
  37. package/skills/git-limpo/SKILL.md +28 -0
  38. package/skills/motion-de-primeira/SKILL.md +62 -0
  39. package/skills/pagina-que-converte/SKILL.md +28 -0
  40. package/skills/performance-web/SKILL.md +28 -0
  41. package/skills/revisao-antes-de-entregar/SKILL.md +27 -0
  42. package/skills/slides-que-prendem/SKILL.md +40 -0
  43. package/studio/edge.py +16 -66
  44. package/studio/narrar.py +274 -0
  45. package/studio/pessoa.py +167 -0
  46. package/voz/primo_voz/__init__.py +0 -0
  47. package/voz/primo_voz/__main__.py +325 -0
  48. package/voz/primo_voz/escuta.py +235 -0
  49. package/voz/primo_voz/orbe.py +216 -0
  50. package/lib/audio-livre.js +0 -177
  51. package/lib/chaves.js +0 -149
  52. package/lib/claude-engine.js +0 -631
  53. package/lib/codex-engine.js +0 -208
  54. package/lib/remotion.js +0 -663
  55. package/lib/studio.js +0 -960
  56. package/studio/PRIMOCODE.md +0 -58
  57. package/studio/README.md +0 -154
  58. package/studio/__pycache__/edge.cpython-311.pyc +0 -0
  59. package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
  60. package/studio/__pycache__/voz.cpython-311.pyc +0 -0
  61. package/studio/arquivos/corte-teste-de-camera-fb823.json +0 -57
  62. package/studio/ferramentas/00-protocolo.md +0 -125
  63. package/studio/ferramentas/01-modelos.md +0 -270
  64. package/studio/ferramentas/README.md +0 -32
  65. package/studio/ferramentas/corte.md +0 -919
  66. package/studio/ferramentas/grade.md +0 -92
  67. package/studio/ferramentas/prisma.md +0 -107
  68. package/studio/ferramentas/prosa.md +0 -86
  69. package/studio/ferramentas/tela.md +0 -93
  70. package/studio/ferramentas/traco.md +0 -79
  71. package/studio/server.py +0 -2254
  72. package/studio/studio.log +0 -57
  73. package/studio/voz.py +0 -388
  74. package/studio/web/analise.html +0 -52
  75. package/studio/web/core.css +0 -1167
  76. package/studio/web/fontes/bebas.woff2 +0 -0
  77. package/studio/web/fontes/grotesk-700.woff2 +0 -0
  78. package/studio/web/fontes/inter-400.woff2 +0 -0
  79. package/studio/web/fontes/inter-700.woff2 +0 -0
  80. package/studio/web/fontes/jetbrains-400.woff2 +0 -0
  81. package/studio/web/fontes/playfair-700.woff2 +0 -0
  82. package/studio/web/fontes.css +0 -23
  83. package/studio/web/home.html +0 -217
  84. package/studio/web/js/analise.js +0 -510
  85. package/studio/web/js/audio.js +0 -607
  86. package/studio/web/js/editor.js +0 -1802
  87. package/studio/web/js/exportar.js +0 -222
  88. package/studio/web/js/ferramentas.js +0 -108
  89. package/studio/web/js/icones.js +0 -120
  90. package/studio/web/js/midia.js +0 -157
  91. package/studio/web/js/narracao.js +0 -325
  92. package/studio/web/js/player.js +0 -903
  93. package/studio/web/js/render-grade.js +0 -243
  94. package/studio/web/js/render-prosa.js +0 -104
  95. package/studio/web/js/render.js +0 -1852
  96. package/studio/web/js/sfx.js +0 -233
  97. package/studio/web/js/video.js +0 -464
  98. package/studio/web/js/voz-neural.js +0 -152
  99. package/studio/web/render.css +0 -1352
  100. package/studio/web/tf.html +0 -15
  101. package/studio/web/tool.html +0 -78
  102. package/studio/web/view.html +0 -27
package/lib/entrada.js ADDED
@@ -0,0 +1,545 @@
1
+ /**
2
+ * entrada.js — a caixa de digitar do PrimoCode.
3
+ *
4
+ * O que havia antes era o `readline` do Node com um prompt de uma linha só:
5
+ *
6
+ * Auto · ctx 12% · loja ❯ _
7
+ *
8
+ * Funciona, e é o que qualquer programa de terminal faz. O que ele NÃO faz é
9
+ * o que se pediu aqui: uma caixa desenhada, ancorada embaixo, que diz em que
10
+ * pasta o agente está trabalhando e quais teclas existem — sem a pessoa
11
+ * precisar lembrar de nada.
12
+ *
13
+ * ── O QUE O readline NÃO DAVA, E POR ISSO ELE SAIU ───────────────────────
14
+ * Não é gosto. São quatro coisas que ele não faz e que a caixa precisa:
15
+ *
16
+ * 1. desenhar mais de uma linha e continuar sabendo onde o cursor está —
17
+ * o readline pensa em UMA linha de texto, e um pedido de três linhas
18
+ * vira uma linha comprida que rola de lado;
19
+ * 2. manter a caixa ANCORADA embaixo enquanto o agente escreve por cima.
20
+ * Cada `console.log` do agente precisa apagar a caixa, escrever, e
21
+ * redesenhar — senão o texto sai no meio dela;
22
+ * 3. barra de status embaixo do campo, que é onde a pasta e o modo moram;
23
+ * 4. um menu de comandos navegável com as setas.
24
+ *
25
+ * ── A REGRA DA ÂNCORA ────────────────────────────────────────────────────
26
+ * A caixa é sempre a última coisa da tela. Para isso, TODA saída que não é
27
+ * dela passa por `escreverAcima`: apaga a caixa, escreve o texto, redesenha.
28
+ * O `process.stdout.write` é embrulhado enquanto a caixa está no ar, então
29
+ * nenhum `console.log` espalhado pelo programa precisa saber disso.
30
+ *
31
+ * ── O DESENHO É FUNÇÃO PURA ──────────────────────────────────────────────
32
+ * `montar()` recebe um estado e devolve as linhas com a posição do cursor.
33
+ * Ela não toca no terminal, então o teste consegue conferir quadro a quadro
34
+ * (largura estreita, texto que quebra, menu aberto) sem nada na tela. Foi
35
+ * assim que o `/effort` foi feito, pelo mesmo motivo.
36
+ */
37
+
38
+ 'use strict';
39
+
40
+ const readline = require('readline');
41
+ const { EventEmitter } = require('events');
42
+ const { c, visibleWidth, truncate } = require('./ui.js');
43
+
44
+ // ── O desenho ────────────────────────────────────────────────────────────
45
+
46
+ const BORDA = { ne: '╭', no: '╮', se: '╰', so: '╯', h: '─', v: '│' };
47
+
48
+ /** Preenche até `w` colunas VISÍVEIS (códigos de cor não ocupam coluna). */
49
+ function encher(s, w) {
50
+ const falta = w - visibleWidth(s);
51
+ return falta > 0 ? s + ' '.repeat(falta) : s;
52
+ }
53
+
54
+ /**
55
+ * Quebra o texto em linhas de no máximo `w` colunas, guardando de onde cada
56
+ * uma começa no texto cru.
57
+ *
58
+ * O deslocamento é o que faz o cursor saber em que linha da caixa ele está:
59
+ * sem ele, um pedido de três linhas põe o cursor sempre na primeira.
60
+ */
61
+ function quebrar(texto, w) {
62
+ const linhas = [];
63
+ let inicio = 0;
64
+ for (const paragrafo of String(texto).split('\n')) {
65
+ let resto = paragrafo;
66
+ let base = inicio;
67
+ do {
68
+ const pedaco = resto.slice(0, w);
69
+ linhas.push({ texto: pedaco, de: base });
70
+ base += pedaco.length;
71
+ resto = resto.slice(w);
72
+ } while (resto.length > 0);
73
+ inicio += paragrafo.length + 1; // +1 é o \n que separa
74
+ }
75
+ return linhas;
76
+ }
77
+
78
+ /**
79
+ * O quadro inteiro: caixa, menu e as duas linhas de status.
80
+ *
81
+ * @param {object} e
82
+ * @param {string} e.texto o que está digitado
83
+ * @param {number} e.cursor posição do cursor dentro de `texto`
84
+ * @param {number} e.colunas largura do terminal
85
+ * @param {string} e.fantasma sugestão esmaecida depois do cursor
86
+ * @param {string} e.vazio o texto de espera, quando não há nada digitado
87
+ * @param {string[]} e.status as duas linhas de baixo, já coloridas
88
+ * @param {object|null} e.menu { itens: [[nome, descricao]], sel }
89
+ * @param {boolean} e.ocupado o agente está trabalhando (a caixa recua)
90
+ * @returns {{linhas: string[], cursorLinha: number, cursorColuna: number}}
91
+ */
92
+ function montar(e) {
93
+ const colunas = Math.max(24, Math.min(e.colunas || 80, 200));
94
+ const largura = colunas - 1; // a última coluna fica livre: escrever nela
95
+ // faz o terminal quebrar a linha sozinho
96
+ const w = Math.max(8, largura - 6); // │ + espaço + recuo(2) + … + espaço + │
97
+
98
+ const cursor = Math.max(0, Math.min(e.cursor | 0, String(e.texto).length));
99
+ const linhas = quebrar(e.texto, w);
100
+
101
+ // Em qual linha quebrada o cursor caiu. `<=` no fim de cada linha porque o
102
+ // cursor pode estar logo depois do último caractere dela.
103
+ let cursorLinha = 0;
104
+ let cursorColuna = 0;
105
+ for (let i = 0; i < linhas.length; i++) {
106
+ const l = linhas[i];
107
+ const fim = l.de + l.texto.length;
108
+ if (cursor >= l.de && cursor <= fim) { cursorLinha = i; cursorColuna = cursor - l.de; }
109
+ }
110
+
111
+ const out = [];
112
+ const regua = c.dim(BORDA.h.repeat(largura - 2));
113
+ out.push(c.dim(BORDA.ne) + regua + c.dim(BORDA.no));
114
+
115
+ const vazio = !String(e.texto).length;
116
+ linhas.forEach((l, i) => {
117
+ const marca = i === 0
118
+ ? (e.ocupado ? c.dim('❯ ') : c.brand('❯ '))
119
+ : c.dim('┆ ');
120
+ let conteudo = c.white(l.texto);
121
+ // O fantasma só existe na última linha e só com o cursor no fim dela:
122
+ // desenhado no meio do texto, ele empurraria o que vem depois.
123
+ if (i === linhas.length - 1 && e.fantasma && cursor === String(e.texto).length) {
124
+ conteudo += c.dim(e.fantasma);
125
+ }
126
+ if (i === 0 && vazio && e.vazio) conteudo = c.dim(e.vazio);
127
+ out.push(c.dim(BORDA.v) + ' ' + marca + encher(conteudo, w) + ' ' + c.dim(BORDA.v));
128
+ });
129
+
130
+ out.push(c.dim(BORDA.se) + regua + c.dim(BORDA.so));
131
+
132
+ // ── o menu dos comandos ──────────────────────────────────────────────
133
+ // Fica ABAIXO da caixa, não dentro: dentro, ele empurraria o campo de
134
+ // digitar para cima a cada tecla, e o texto que a pessoa escreve saltaria
135
+ // de lugar enquanto ela escreve.
136
+ if (e.menu && e.menu.itens.length) {
137
+ const cabem = Math.max(1, Math.min(e.menu.itens.length, 8));
138
+ // A janela acompanha a seleção: descer na lista rola, em vez de o
139
+ // item escolhido sumir embaixo.
140
+ let topo = Math.max(0, Math.min(e.menu.sel - Math.floor(cabem / 2),
141
+ e.menu.itens.length - cabem));
142
+ for (let i = topo; i < topo + cabem; i++) {
143
+ const [nome, desc] = e.menu.itens[i];
144
+ const escolhido = i === e.menu.sel;
145
+ const nomeW = 16;
146
+ const corpo = (escolhido ? c.brand('❯ ') : ' ')
147
+ + (escolhido ? c.bold(c.white(encher(nome, nomeW))) : c.muted(encher(nome, nomeW)))
148
+ + c.dim(desc || '');
149
+ out.push(' ' + truncate(corpo, largura - 2));
150
+ }
151
+ if (e.menu.itens.length > cabem) {
152
+ out.push(' ' + c.dim(`… e mais ${e.menu.itens.length - cabem}`));
153
+ }
154
+ }
155
+
156
+ for (const linha of (e.status || [])) out.push(' ' + linha);
157
+
158
+ return {
159
+ linhas: out,
160
+ cursorLinha: 1 + cursorLinha, // +1 pela borda de cima
161
+ cursorColuna: 1 + 1 + 2 + cursorColuna, // │ + espaço + recuo
162
+ };
163
+ }
164
+
165
+ // ── A caixa viva ─────────────────────────────────────────────────────────
166
+
167
+ class Entrada extends EventEmitter {
168
+ /**
169
+ * @param {object} op
170
+ * @param {()=>string[]} op.status as duas linhas de baixo, sob demanda
171
+ * @param {()=>Array} op.comandos [[nome, descrição]] para o menu e o fantasma
172
+ * @param {string} op.vazio o texto de espera
173
+ */
174
+ constructor(op = {}) {
175
+ super();
176
+ this.op = op;
177
+ this.line = '';
178
+ this.cursor = 0;
179
+ this.historico = [];
180
+ this.noHistorico = -1;
181
+ this.rascunho = '';
182
+ this.menu = null;
183
+ this.desenhado = null; // {linhas, cursorLinha} do último quadro
184
+ this.ativo = false;
185
+ this.suspenso = false;
186
+ this.ocupado = false;
187
+ this.fechado = false;
188
+ this.colando = false;
189
+ this.tty = !!(process.stdin.isTTY && process.stdout.isTTY);
190
+ this.escreverCru = process.stdout.write.bind(process.stdout);
191
+ }
192
+
193
+ // ── ciclo de vida ────────────────────────────────────────────────────
194
+
195
+ iniciar() {
196
+ if (this.ativo) return this;
197
+ this.ativo = true;
198
+ if (!this.tty) { this._semTerminal(); return this; }
199
+
200
+ readline.emitKeypressEvents(process.stdin);
201
+ try { process.stdin.setRawMode(true); } catch { /* alguns terminais recusam */ }
202
+ process.stdin.resume();
203
+
204
+ /* Colagem entre marcas. Sem isto, colar um texto de trinta linhas faz
205
+ o terminal mandar trinta Enters, e o PrimoCode dispara trinta
206
+ pedidos — um por linha. Com as marcas, o bloco inteiro chega como
207
+ texto e vira UM pedido de várias linhas. */
208
+ this.escreverCru('\x1b[?2004h');
209
+
210
+ this._aoTeclar = (ch, key) => this._tecla(ch, key);
211
+ process.stdin.on('keypress', this._aoTeclar);
212
+
213
+ this._aoRedimensionar = () => { if (this.desenhado) this._redesenhar(); };
214
+ process.stdout.on('resize', this._aoRedimensionar);
215
+
216
+ /* Todo `console.log` do programa passa a apagar a caixa antes de
217
+ escrever, e a redesenhá-la depois. Embrulhar aqui é o que evita ter
218
+ de trocar todas as centenas de `console.log` espalhados pelo
219
+ código — e, mais importante, o que evita que UM esquecido escreva
220
+ por cima da caixa. */
221
+ this._stdoutOriginal = process.stdout.write;
222
+ const eu = this;
223
+ process.stdout.write = function (texto, ...resto) {
224
+ if (eu._pintando || !eu.desenhado) return eu._stdoutOriginal.call(this, texto, ...resto);
225
+ eu._apagar();
226
+ const r = eu._stdoutOriginal.call(this, texto, ...resto);
227
+ eu._desenhar();
228
+ return r;
229
+ };
230
+ return this;
231
+ }
232
+
233
+ /* Sem terminal de verdade (pipe, CI, `primocode "faça x"`) não há caixa
234
+ para desenhar nem tecla para escutar: cada linha que chega é um pedido.
235
+ Desenhar aqui encheria a saída de códigos de cor e de bordas. */
236
+ _semTerminal() {
237
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
238
+ this._rlPipe = rl;
239
+ rl.on('line', (l) => this.emit('line', l));
240
+ rl.on('close', () => { this.fechado = true; this.emit('close'); });
241
+ }
242
+
243
+ close() {
244
+ if (this.fechado) return;
245
+ this.fechado = true;
246
+ this.parar();
247
+ this.emit('close');
248
+ }
249
+
250
+ parar() {
251
+ if (!this.tty) { if (this._rlPipe) this._rlPipe.close(); return; }
252
+ this._apagar();
253
+ this.escreverCru('\x1b[?2004l');
254
+ if (this._stdoutOriginal) process.stdout.write = this._stdoutOriginal;
255
+ if (this._aoTeclar) process.stdin.removeListener('keypress', this._aoTeclar);
256
+ if (this._aoRedimensionar) process.stdout.removeListener('resize', this._aoRedimensionar);
257
+ try { process.stdin.setRawMode(false); } catch {}
258
+ process.stdin.pause();
259
+ this.ativo = false;
260
+ }
261
+
262
+ /* Enquanto outro desenho é dono da tela — o painel do /effort, uma tela
263
+ do marketplace — a caixa sai da frente e larga o teclado. Sem isso, os
264
+ dois desenhariam no mesmo lugar e as setas moveriam os dois. */
265
+ suspender() { this.suspenso = true; this._apagar(); }
266
+ retomar() { this.suspenso = false; if (this.ativo && !this.fechado) this._desenhar(); }
267
+
268
+ /** O agente está trabalhando: a caixa esmaece e o Enter não manda nada. */
269
+ trabalhando(sim) {
270
+ if (this.ocupado === !!sim) return;
271
+ this.ocupado = !!sim;
272
+ if (this.desenhado) this._redesenhar();
273
+ }
274
+
275
+ prompt() { if (!this.suspenso && this.tty && !this.fechado) this._desenhar(); }
276
+
277
+ /** Escreve texto ACIMA da caixa — é por onde todo o resto do programa fala. */
278
+ escreverAcima(texto) {
279
+ if (!this.desenhado) { this.escreverCru(texto); return; }
280
+ this._apagar();
281
+ this.escreverCru(texto);
282
+ this._desenhar();
283
+ }
284
+
285
+ /** Põe texto no campo, como se tivesse sido digitado. */
286
+ write(texto) {
287
+ if (!this.tty) return;
288
+ this.line = this.line.slice(0, this.cursor) + texto + this.line.slice(this.cursor);
289
+ this.cursor += texto.length;
290
+ this._mudou();
291
+ }
292
+
293
+ limpar() { this.line = ''; this.cursor = 0; this.menu = null; this._mudou(); }
294
+
295
+ // ── desenho ──────────────────────────────────────────────────────────
296
+
297
+ _quadro() {
298
+ return montar({
299
+ texto: this.line,
300
+ cursor: this.cursor,
301
+ colunas: process.stdout.columns || 80,
302
+ fantasma: this._fantasma(),
303
+ vazio: this.op.vazio || '',
304
+ status: this.op.status ? this.op.status() : [],
305
+ menu: this.menu,
306
+ ocupado: this.ocupado,
307
+ });
308
+ }
309
+
310
+ _desenhar() {
311
+ if (!this.tty || this.suspenso || this.fechado) return;
312
+ const q = this._quadro();
313
+ this._pintando = true;
314
+ // `\x1b[0J` limpa daqui até o fim da tela: sem isso, um quadro mais
315
+ // curto que o anterior (menu que fechou) deixa lixo embaixo.
316
+ this.escreverCru('\r\x1b[0J' + q.linhas.join('\n'));
317
+ const paraCima = q.linhas.length - 1 - q.cursorLinha;
318
+ let volta = '\r';
319
+ if (paraCima > 0) volta += `\x1b[${paraCima}A`;
320
+ if (q.cursorColuna > 0) volta += `\x1b[${q.cursorColuna}C`;
321
+ this.escreverCru(volta);
322
+ this._pintando = false;
323
+ this.desenhado = q;
324
+ }
325
+
326
+ _apagar() {
327
+ if (!this.desenhado || !this.tty) return;
328
+ this._pintando = true;
329
+ // Sobe até a primeira linha do quadro e limpa daí para baixo.
330
+ const acima = this.desenhado.cursorLinha;
331
+ this.escreverCru((acima > 0 ? `\x1b[${acima}A` : '') + '\r\x1b[0J');
332
+ this._pintando = false;
333
+ this.desenhado = null;
334
+ }
335
+
336
+ _redesenhar() { this._apagar(); this._desenhar(); }
337
+
338
+ _mudou() {
339
+ if (this.menu) this._filtrarMenu();
340
+ this._redesenhar();
341
+ }
342
+
343
+ // ── o fantasma e o menu ──────────────────────────────────────────────
344
+
345
+ _fantasma() {
346
+ if (this.ocupado) return '';
347
+ const linha = this.line;
348
+ if (!linha.startsWith('/') || /\s/.test(linha) || linha.length < 2) return '';
349
+ if (this.cursor !== linha.length) return '';
350
+ const lista = this.op.comandos ? this.op.comandos() : [];
351
+ const baixa = linha.toLowerCase();
352
+ const hit = lista.map(([n]) => n).find((n) => n.startsWith(baixa) && n.length > linha.length);
353
+ return hit ? hit.slice(linha.length) : '';
354
+ }
355
+
356
+ _filtrarMenu() {
357
+ const lista = this.op.comandos ? this.op.comandos() : [];
358
+ const baixa = this.line.toLowerCase();
359
+ const itens = this.line.startsWith('/') && !/\s/.test(this.line)
360
+ ? lista.filter(([n]) => n.startsWith(baixa))
361
+ : [];
362
+ if (!itens.length) { this.menu = null; return; }
363
+ const sel = this.menu ? Math.min(this.menu.sel, itens.length - 1) : 0;
364
+ this.menu = { itens, sel };
365
+ }
366
+
367
+ // ── teclado ──────────────────────────────────────────────────────────
368
+
369
+ _tecla(ch, key) {
370
+ if (this.suspenso || this.fechado) return;
371
+ const seq = (key && key.sequence) || ch || '';
372
+
373
+ // Colagem entre marcas: tudo que vier até a marca de fim é texto.
374
+ if (seq.includes('\x1b[200~')) { this.colando = true; }
375
+ if (this.colando) {
376
+ const limpo = seq.replace('\x1b[200~', '').replace('\x1b[201~', '');
377
+ if (limpo) this.write(limpo.replace(/\r/g, '\n'));
378
+ if (seq.includes('\x1b[201~')) this.colando = false;
379
+ return;
380
+ }
381
+
382
+ if (!key) { if (ch) this.write(ch); return; }
383
+
384
+ // Esc Esc e o resto das teclas de fora: quem escuta decide. A caixa
385
+ // não sabe o que é "parar o agente" — isso é assunto do programa.
386
+ if (key.name === 'escape') {
387
+ if (this.menu) { this.menu = null; this._redesenhar(); return; }
388
+ this.emit('escape');
389
+ return;
390
+ }
391
+
392
+ if (key.ctrl) return this._ctrl(key);
393
+
394
+ switch (key.name) {
395
+ case 'return':
396
+ case 'enter':
397
+ /* Alt+Enter (e a barra invertida no fim) quebram linha em vez
398
+ de mandar. É o que deixa escrever um pedido de parágrafo
399
+ sem ele sair pela metade no primeiro Enter. */
400
+ if (key.meta) return this._novaLinha();
401
+ if (this.line.endsWith('\\')) {
402
+ this.line = this.line.slice(0, -1);
403
+ this.cursor = Math.max(0, this.cursor - 1);
404
+ return this._novaLinha();
405
+ }
406
+ return this._enviar();
407
+
408
+ case 'tab': {
409
+ if (this.menu) return this._escolherDoMenu();
410
+ const f = this._fantasma();
411
+ if (f) return this.write(f);
412
+ if (this.line.startsWith('/')) { this._filtrarMenu(); this._redesenhar(); }
413
+ return;
414
+ }
415
+
416
+ case 'backspace':
417
+ if (this.cursor > 0) {
418
+ this.line = this.line.slice(0, this.cursor - 1) + this.line.slice(this.cursor);
419
+ this.cursor--;
420
+ this._mudou();
421
+ }
422
+ return;
423
+
424
+ case 'delete':
425
+ if (this.cursor < this.line.length) {
426
+ this.line = this.line.slice(0, this.cursor) + this.line.slice(this.cursor + 1);
427
+ this._mudou();
428
+ }
429
+ return;
430
+
431
+ case 'left': if (this.cursor > 0) { this.cursor--; this._redesenhar(); } return;
432
+ case 'right': {
433
+ // No fim da linha, a seta direita aceita a sugestão inteira —
434
+ // era assim antes da caixa, e o dedo de quem usa já sabe.
435
+ const f = this._fantasma();
436
+ if (f) return this.write(f);
437
+ if (this.cursor < this.line.length) { this.cursor++; this._redesenhar(); }
438
+ return;
439
+ }
440
+ case 'home': this.cursor = 0; this._redesenhar(); return;
441
+ case 'end': this.cursor = this.line.length; this._redesenhar(); return;
442
+
443
+ case 'up':
444
+ if (this.menu) { this.menu.sel = Math.max(0, this.menu.sel - 1); return this._redesenhar(); }
445
+ return this._historico(-1);
446
+ case 'down':
447
+ if (this.menu) {
448
+ this.menu.sel = Math.min(this.menu.itens.length - 1, this.menu.sel + 1);
449
+ return this._redesenhar();
450
+ }
451
+ return this._historico(1);
452
+
453
+ default:
454
+ if (seq && !key.meta && seq >= ' ' && seq !== '\x7f') this.write(seq);
455
+ }
456
+ }
457
+
458
+ _ctrl(key) {
459
+ switch (key.name) {
460
+ case 'c':
461
+ // Com texto no campo, Ctrl+C limpa o que foi digitado — sair
462
+ // por acidente no meio de um pedido longo é perder o pedido.
463
+ if (this.line) return this.limpar();
464
+ this.emit('sigint');
465
+ return;
466
+ case 'd':
467
+ if (!this.line) return this.close();
468
+ return;
469
+ case 'u': this.line = this.line.slice(this.cursor); this.cursor = 0; return this._mudou();
470
+ case 'k': this.line = this.line.slice(0, this.cursor); return this._mudou();
471
+ case 'a': this.cursor = 0; return this._redesenhar();
472
+ case 'e': this.cursor = this.line.length; return this._redesenhar();
473
+ case 'w': {
474
+ const antes = this.line.slice(0, this.cursor).replace(/\s*\S+$/, '');
475
+ const depois = this.line.slice(this.cursor);
476
+ this.line = antes + depois;
477
+ this.cursor = antes.length;
478
+ return this._mudou();
479
+ }
480
+ case 'l': this._apagar(); this.escreverCru('\x1b[2J\x1b[H'); return this._desenhar();
481
+ case 'j': return this._novaLinha();
482
+ default: return;
483
+ }
484
+ }
485
+
486
+ _novaLinha() {
487
+ this.line = this.line.slice(0, this.cursor) + '\n' + this.line.slice(this.cursor);
488
+ this.cursor++;
489
+ this._mudou();
490
+ }
491
+
492
+ _escolherDoMenu() {
493
+ const [nome] = this.menu.itens[this.menu.sel];
494
+ this.line = nome;
495
+ this.cursor = nome.length;
496
+ this.menu = null;
497
+ this._redesenhar();
498
+ }
499
+
500
+ _historico(passo) {
501
+ if (!this.historico.length) return;
502
+ if (this.noHistorico === -1) {
503
+ if (passo > 0) return;
504
+ this.rascunho = this.line;
505
+ this.noHistorico = this.historico.length;
506
+ }
507
+ const i = this.noHistorico + passo;
508
+ if (i >= this.historico.length) {
509
+ this.noHistorico = -1;
510
+ this.line = this.rascunho;
511
+ } else if (i < 0) {
512
+ return;
513
+ } else {
514
+ this.noHistorico = i;
515
+ this.line = this.historico[i];
516
+ }
517
+ this.cursor = this.line.length;
518
+ this._redesenhar();
519
+ }
520
+
521
+ _enviar() {
522
+ if (this.menu) return this._escolherDoMenu();
523
+ const texto = this.line;
524
+ this.line = '';
525
+ this.cursor = 0;
526
+ this.menu = null;
527
+ this.noHistorico = -1;
528
+
529
+ /* O que foi mandado sai da caixa e vira linha do histórico da tela —
530
+ senão o pedido some no instante em que a resposta começa, e quem
531
+ rola para cima não acha o que perguntou. */
532
+ this._apagar();
533
+ if (texto.trim()) {
534
+ const marca = c.brand('❯ ');
535
+ this.escreverCru(texto.split('\n').map((l, i) => (i ? ' ' : marca) + c.white(l)).join('\n') + '\n');
536
+ if (this.historico[this.historico.length - 1] !== texto) this.historico.push(texto);
537
+ }
538
+ this._desenhar();
539
+ this.emit('line', texto);
540
+ }
541
+ }
542
+
543
+ function criar(op) { return new Entrada(op).iniciar(); }
544
+
545
+ module.exports = { criar, Entrada, montar, quebrar, encher };
@@ -11,8 +11,8 @@
11
11
  * generalista escreve o CSS que qualquer um escreveria. O briefing é o que um
12
12
  * sênior daquela área diria a um colega antes de ele começar — o que olhar
13
13
  * primeiro, o que nunca fazer, como PROVAR que acabou. Ele vai na frente da
14
- * tarefa, na MESMA mensagem: o servidor não precisa conhecer os especialistas,
15
- * e o /claude e o /codex os recebem igual.
14
+ * tarefa, na MESMA mensagem — assim o servidor não precisa conhecer os
15
+ * especialistas.
16
16
  *
17
17
  * ── CURTO DE PROPÓSITO ──────────────────────────────────────────────────
18
18
  * Cada briefing cabe em ~800 caracteres. O subagente vive no mesmo teto de