primocode 8.42.2 → 9.1.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 (104) 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 +42 -195
  7. package/lib/catalogo.js +100 -61
  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 +576 -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 +644 -0
  20. package/lib/estudio/slides.js +190 -0
  21. package/lib/estudio/video.js +296 -0
  22. package/lib/fala.js +49 -6
  23. package/lib/marca.js +4 -1
  24. package/lib/markdown.js +376 -0
  25. package/lib/marketplace.js +189 -0
  26. package/lib/pasta.js +15 -4
  27. package/lib/permissao.js +137 -0
  28. package/lib/registro.js +229 -0
  29. package/lib/skills.js +329 -0
  30. package/lib/tools.js +106 -187
  31. package/lib/ui.js +7 -3
  32. package/lib/voz.js +239 -0
  33. package/package.json +12 -3
  34. package/skills/acessibilidade/SKILL.md +28 -0
  35. package/skills/api-que-nao-quebra/SKILL.md +27 -0
  36. package/skills/depurar-de-verdade/SKILL.md +29 -0
  37. package/skills/editar-meu-video/SKILL.md +59 -0
  38. package/skills/escrever-em-portugues/SKILL.md +28 -0
  39. package/skills/git-limpo/SKILL.md +28 -0
  40. package/skills/motion-de-primeira/SKILL.md +62 -0
  41. package/skills/pagina-que-converte/SKILL.md +28 -0
  42. package/skills/performance-web/SKILL.md +28 -0
  43. package/skills/revisao-antes-de-entregar/SKILL.md +27 -0
  44. package/skills/slides-que-prendem/SKILL.md +40 -0
  45. package/studio/edge.py +16 -66
  46. package/studio/narrar.py +274 -0
  47. package/studio/pessoa.py +199 -0
  48. package/voz/primo_voz/__init__.py +0 -0
  49. package/voz/primo_voz/__main__.py +383 -0
  50. package/voz/primo_voz/escuta.py +235 -0
  51. package/voz/primo_voz/orbe.py +216 -0
  52. package/lib/audio-livre.js +0 -177
  53. package/lib/chaves.js +0 -149
  54. package/lib/claude-engine.js +0 -631
  55. package/lib/codex-engine.js +0 -208
  56. package/lib/remotion.js +0 -663
  57. package/lib/studio.js +0 -960
  58. package/studio/PRIMOCODE.md +0 -58
  59. package/studio/README.md +0 -154
  60. package/studio/__pycache__/edge.cpython-311.pyc +0 -0
  61. package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
  62. package/studio/__pycache__/voz.cpython-311.pyc +0 -0
  63. package/studio/arquivos/corte-teste-de-camera-fb823.json +0 -57
  64. package/studio/ferramentas/00-protocolo.md +0 -125
  65. package/studio/ferramentas/01-modelos.md +0 -270
  66. package/studio/ferramentas/README.md +0 -32
  67. package/studio/ferramentas/corte.md +0 -919
  68. package/studio/ferramentas/grade.md +0 -92
  69. package/studio/ferramentas/prisma.md +0 -107
  70. package/studio/ferramentas/prosa.md +0 -86
  71. package/studio/ferramentas/tela.md +0 -93
  72. package/studio/ferramentas/traco.md +0 -79
  73. package/studio/server.py +0 -2254
  74. package/studio/studio.log +0 -57
  75. package/studio/voz.py +0 -388
  76. package/studio/web/analise.html +0 -52
  77. package/studio/web/core.css +0 -1167
  78. package/studio/web/fontes/bebas.woff2 +0 -0
  79. package/studio/web/fontes/grotesk-700.woff2 +0 -0
  80. package/studio/web/fontes/inter-400.woff2 +0 -0
  81. package/studio/web/fontes/inter-700.woff2 +0 -0
  82. package/studio/web/fontes/jetbrains-400.woff2 +0 -0
  83. package/studio/web/fontes/playfair-700.woff2 +0 -0
  84. package/studio/web/fontes.css +0 -23
  85. package/studio/web/home.html +0 -217
  86. package/studio/web/js/analise.js +0 -510
  87. package/studio/web/js/audio.js +0 -607
  88. package/studio/web/js/editor.js +0 -1802
  89. package/studio/web/js/exportar.js +0 -222
  90. package/studio/web/js/ferramentas.js +0 -108
  91. package/studio/web/js/icones.js +0 -120
  92. package/studio/web/js/midia.js +0 -157
  93. package/studio/web/js/narracao.js +0 -325
  94. package/studio/web/js/player.js +0 -903
  95. package/studio/web/js/render-grade.js +0 -243
  96. package/studio/web/js/render-prosa.js +0 -104
  97. package/studio/web/js/render.js +0 -1852
  98. package/studio/web/js/sfx.js +0 -233
  99. package/studio/web/js/video.js +0 -464
  100. package/studio/web/js/voz-neural.js +0 -152
  101. package/studio/web/render.css +0 -1352
  102. package/studio/web/tf.html +0 -15
  103. package/studio/web/tool.html +0 -78
  104. package/studio/web/view.html +0 -27
package/lib/skills.js ADDED
@@ -0,0 +1,329 @@
1
+ /**
2
+ * skills.js — o que o PrimoCode sabe fazer bem, escrito em texto.
3
+ *
4
+ * "O Primo Code já vem com várias skills instaladas por padrão. Comandos:
5
+ * /skills, /install-skill e /marketplace."
6
+ *
7
+ * ── O QUE É UMA SKILL, E O QUE ELA NÃO É ─────────────────────────────────
8
+ * Uma skill é uma pasta com um `SKILL.md` dentro: um cabeçalho que diz quem
9
+ * ela é e quando serve, e um corpo que é o prompt dela. Nada de código, nada
10
+ * de instalar dependência, nada que rode. É conhecimento, não programa.
11
+ *
12
+ * Isso é decisão, não limitação. Uma skill que executa código é uma porta
13
+ * para qualquer pessoa mandar um arquivo e rodar o que quiser na máquina de
14
+ * quem instalou — e um marketplace de programas executáveis exige assinatura,
15
+ * revisão e sandbox, que é outro produto. Skill é texto: o pior que um texto
16
+ * mal-intencionado faz é dar um conselho ruim ao modelo, e o usuário vê o
17
+ * agente agindo, passo a passo, na tela.
18
+ *
19
+ * ── POR QUE NÃO É A MESMA COISA QUE OS ESPECIALISTAS ─────────────────────
20
+ * `especialistas.js` são sete briefings FIXOS, escolhidos pelo modelo quando
21
+ * ele delega uma tarefa a um subagente. Eles são do produto: ninguém instala,
22
+ * ninguém desliga, e a lista não cresce.
23
+ *
24
+ * Skill é do USUÁRIO. Ele instala, desliga, escreve a dele. É a resposta para
25
+ * "o Primo sempre esquece de rodar o lint antes de terminar" — que não se
26
+ * resolve pedindo de novo a cada conversa, e sim escrevendo uma vez.
27
+ *
28
+ * ── COMO ELA CHEGA AO MODELO ─────────────────────────────────────────────
29
+ * O corpo NÃO vai em todo pedido. São oito skills; mandar todas custaria
30
+ * milhares de tokens por mensagem num plano limitado por token, para que
31
+ * sete delas não tenham nada a ver com o pedido.
32
+ *
33
+ * O que decide é o `quando:` do cabeçalho — as palavras que fazem aquela
34
+ * skill valer a pena. Bateu, o corpo dela entra na frente da tarefa; não
35
+ * bateu, ela não custa nada. É o mesmo desenho dos especialistas, e pelo
36
+ * mesmo motivo: o teto de tokens é o recurso escasso deste produto.
37
+ *
38
+ * ── ONDE ELAS MORAM ──────────────────────────────────────────────────────
39
+ * Em `~/.primocode/skills/`, fora do pacote — pelo mesmo motivo do Studio:
40
+ * instalado global, a pasta do pacote costuma ser de sistema, e um `npm
41
+ * update` apagaria o que o usuário escreveu. As que vêm com o produto são
42
+ * copiadas na primeira vez; as dele ficam lá, a salvo de atualização.
43
+ */
44
+
45
+ 'use strict';
46
+
47
+ const fs = require('fs');
48
+ const os = require('os');
49
+ const path = require('path');
50
+
51
+ const ORIGEM = path.join(__dirname, '..', 'skills');
52
+ const CASA = path.join(os.homedir(), '.primocode', 'skills');
53
+ const ESTADO = path.join(CASA, 'estado.json');
54
+
55
+ /* Teto do que entra num pedido. Duas skills longas já são mais texto que a
56
+ maioria das tarefas — e cada caractere aqui é um caractere a menos de
57
+ arquivo lido. Estourou, entram as que couberam, das que casaram melhor. */
58
+ const MAX_CARACTERES = 6000;
59
+
60
+ // ── leitura ──────────────────────────────────────────────────────────────
61
+
62
+ /**
63
+ * O cabeçalho e o corpo de um SKILL.md.
64
+ *
65
+ * O cabeçalho fica entre duas linhas de `---`, uma chave por linha. Formato
66
+ * escolhido por ser o que qualquer pessoa já viu, e por caber num parser de
67
+ * dez linhas — YAML de verdade seria uma dependência para ler quatro campos.
68
+ */
69
+ function separar(texto) {
70
+ const bruto = String(texto || '');
71
+ const m = bruto.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
72
+ if (!m) return { cabecalho: {}, corpo: bruto.trim() };
73
+ const cabecalho = {};
74
+ for (const linha of m[1].split(/\r?\n/)) {
75
+ const par = linha.match(/^\s*([a-zA-Z_][\w-]*)\s*:\s*(.*)$/);
76
+ if (par) cabecalho[par[1].toLowerCase()] = par[2].trim().replace(/^["']|["']$/g, '');
77
+ }
78
+ return { cabecalho, corpo: m[2].trim() };
79
+ }
80
+
81
+ function lerEstado() {
82
+ try { return JSON.parse(fs.readFileSync(ESTADO, 'utf8')); } catch { return { desligadas: [] }; }
83
+ }
84
+
85
+ function salvarEstado(estado) {
86
+ try {
87
+ fs.mkdirSync(CASA, { recursive: true });
88
+ fs.writeFileSync(ESTADO, JSON.stringify(estado, null, 2), 'utf8');
89
+ return true;
90
+ } catch { return false; }
91
+ }
92
+
93
+ /* As que vêm com o produto são copiadas na primeira vez. Copiar SEMPRE por
94
+ cima seria desfazer a edição de quem melhorou uma delas; copiar só o que
95
+ falta deixa o usuário dono do que ele mexeu. */
96
+ function prepararCasa() {
97
+ fs.mkdirSync(CASA, { recursive: true });
98
+ let nascidas = 0;
99
+ let origens = [];
100
+ try { origens = fs.readdirSync(ORIGEM, { withFileTypes: true }); } catch { return 0; }
101
+ for (const item of origens) {
102
+ if (!item.isDirectory()) continue;
103
+ const destino = path.join(CASA, item.name);
104
+ if (fs.existsSync(destino)) continue;
105
+ try {
106
+ fs.mkdirSync(destino, { recursive: true });
107
+ fs.copyFileSync(path.join(ORIGEM, item.name, 'SKILL.md'), path.join(destino, 'SKILL.md'));
108
+ nascidas++;
109
+ } catch { /* uma que falha não impede as outras */ }
110
+ }
111
+ return nascidas;
112
+ }
113
+
114
+ /** Uma skill pelo nome da pasta, ou null. */
115
+ function ler(nome) {
116
+ const arquivo = path.join(CASA, nome, 'SKILL.md');
117
+ let texto;
118
+ try { texto = fs.readFileSync(arquivo, 'utf8'); } catch { return null; }
119
+ const { cabecalho, corpo } = separar(texto);
120
+ const estado = lerEstado();
121
+ return {
122
+ nome,
123
+ titulo: cabecalho.nome || cabecalho.titulo || nome,
124
+ descricao: cabecalho.descricao || '',
125
+ quando: String(cabecalho.quando || '').split(',').map((s) => s.trim().toLowerCase()).filter(Boolean),
126
+ autor: cabecalho.autor || '',
127
+ corpo,
128
+ ligada: !estado.desligadas.includes(nome),
129
+ doProduto: fs.existsSync(path.join(ORIGEM, nome, 'SKILL.md')),
130
+ caminho: arquivo,
131
+ };
132
+ }
133
+
134
+ /** Todas as instaladas, em ordem alfabética. */
135
+ function listar() {
136
+ prepararCasa();
137
+ let nomes = [];
138
+ try {
139
+ nomes = fs.readdirSync(CASA, { withFileTypes: true })
140
+ .filter((d) => d.isDirectory())
141
+ .map((d) => d.name)
142
+ .sort();
143
+ } catch { return []; }
144
+ return nomes.map(ler).filter(Boolean);
145
+ }
146
+
147
+ // ── escrita ──────────────────────────────────────────────────────────────
148
+
149
+ /* O nome vira pasta, então precisa ser um nome de pasta: sem barra, sem
150
+ `..`, sem acento. Um nome com `../` escreveria fora de ~/.primocode. */
151
+ function apelidar(bruto) {
152
+ return String(bruto || '')
153
+ .normalize('NFD').replace(/[̀-ͯ]/g, '')
154
+ .toLowerCase()
155
+ .replace(/[^a-z0-9]+/g, '-')
156
+ .replace(/^-+|-+$/g, '')
157
+ .slice(0, 48);
158
+ }
159
+
160
+ /**
161
+ * Cria (ou reescreve) uma skill.
162
+ *
163
+ * @param {string} nome como a pessoa a chamou
164
+ * @param {string} corpo o prompt inteiro
165
+ * @param {object} extra { descricao, quando, autor }
166
+ */
167
+ function criar(nome, corpo, extra = {}) {
168
+ const apelido = apelidar(nome);
169
+ if (!apelido) return { ok: false, error: 'Dê um nome à skill.' };
170
+ if (!String(corpo || '').trim()) return { ok: false, error: 'A skill precisa de um conteúdo — é ele que vai para o modelo.' };
171
+
172
+ const cabecalho = [
173
+ '---',
174
+ `nome: ${String(nome).trim()}`,
175
+ `descricao: ${String(extra.descricao || '').replace(/\n/g, ' ').trim()}`,
176
+ `quando: ${String(extra.quando || '').replace(/\n/g, ' ').trim()}`,
177
+ `autor: ${String(extra.autor || 'você').trim()}`,
178
+ '---',
179
+ '',
180
+ ].join('\n');
181
+
182
+ try {
183
+ const pasta = path.join(CASA, apelido);
184
+ fs.mkdirSync(pasta, { recursive: true });
185
+ fs.writeFileSync(path.join(pasta, 'SKILL.md'), cabecalho + String(corpo).trim() + '\n', 'utf8');
186
+ // Skill recém-criada nasce LIGADA, mesmo que uma de mesmo nome tenha
187
+ // sido desligada antes: quem acabou de escrevê-la quer usá-la.
188
+ const estado = lerEstado();
189
+ estado.desligadas = estado.desligadas.filter((x) => x !== apelido);
190
+ salvarEstado(estado);
191
+ return { ok: true, nome: apelido, caminho: path.join(pasta, 'SKILL.md') };
192
+ } catch (e) {
193
+ return { ok: false, error: `Não deu para gravar: ${e.message}` };
194
+ }
195
+ }
196
+
197
+ function ligar(nome, ligada) {
198
+ const estado = lerEstado();
199
+ estado.desligadas = estado.desligadas.filter((x) => x !== nome);
200
+ if (!ligada) estado.desligadas.push(nome);
201
+ salvarEstado(estado);
202
+ return { ok: true, nome, ligada: !!ligada };
203
+ }
204
+
205
+ /**
206
+ * Apaga uma skill.
207
+ *
208
+ * As do produto voltam no próximo `listar()` — a cópia nasce de novo a partir
209
+ * do pacote. Por isso apagar uma delas não é apagar, é DESLIGAR: prometer
210
+ * remoção e ver a skill reaparecer amanhã é pior que dizer a verdade agora.
211
+ */
212
+ function apagar(nome) {
213
+ const s = ler(nome);
214
+ if (!s) return { ok: false, error: `Não achei a skill "${nome}".` };
215
+ if (s.doProduto) {
216
+ ligar(nome, false);
217
+ return { ok: true, desligada: true,
218
+ aviso: 'Esta skill vem com o PrimoCode, então ela foi DESLIGADA em vez de apagada — '
219
+ + 'apagada, ela voltaria na próxima atualização.' };
220
+ }
221
+ try {
222
+ fs.rmSync(path.join(CASA, nome), { recursive: true, force: true });
223
+ return { ok: true, apagada: true };
224
+ } catch (e) { return { ok: false, error: e.message }; }
225
+ }
226
+
227
+ /**
228
+ * Instala de um arquivo ou de uma pasta que já está no disco.
229
+ *
230
+ * Não busca na rede de propósito: baixar e executar texto que virou prompt do
231
+ * agente é uma decisão que merece o caminho explícito de quem salvou o
232
+ * arquivo antes. O `/marketplace` traz as do catálogo; `/install-skill
233
+ * <caminho>` traz as que a pessoa escreveu ou recebeu.
234
+ */
235
+ function instalarDe(caminho) {
236
+ const alvo = String(caminho || '').replace(/^~(?=$|[/\\])/, os.homedir());
237
+ let arquivo = alvo;
238
+ try {
239
+ if (fs.statSync(alvo).isDirectory()) arquivo = path.join(alvo, 'SKILL.md');
240
+ } catch { return { ok: false, error: `Não achei ${caminho}.` }; }
241
+
242
+ let texto;
243
+ try { texto = fs.readFileSync(arquivo, 'utf8'); }
244
+ catch { return { ok: false, error: `Não consegui ler ${arquivo}. Uma skill é um arquivo SKILL.md.` }; }
245
+
246
+ const { cabecalho, corpo } = separar(texto);
247
+ const nome = cabecalho.nome || path.basename(path.dirname(arquivo));
248
+ if (!corpo.trim()) return { ok: false, error: 'O arquivo não tem conteúdo depois do cabeçalho.' };
249
+ return criar(nome, corpo, cabecalho);
250
+ }
251
+
252
+ // ── o que entra no pedido ────────────────────────────────────────────────
253
+
254
+ const semAcento = (v) => String(v || '').toLowerCase()
255
+ .normalize('NFD').replace(/[\u0300-\u036f]/g, '');
256
+
257
+ /**
258
+ * Quantas palavras do `quando:` aparecem no texto do pedido.
259
+ *
260
+ * ── PALAVRA INTEIRA, NUNCA PEDACO ────────────────────────────────────────
261
+ * O primeiro jeito era `texto.includes(palavra)`, e estava errado de um jeito
262
+ * que so apareceu no ensaio: "faz uma landing page PRO meu curso" puxou a
263
+ * skill de Git, porque o gatilho `pr` mora dentro de "pro". Gatilhos curtos
264
+ * — pr, api, seo, css — casam com metade da lingua portuguesa se a borda nao
265
+ * for exigida.
266
+ *
267
+ * Uma skill errada nao e ruido inofensivo: ela ocupa o teto de tokens do
268
+ * pedido e da ao modelo instrucoes sobre um assunto que ninguem pediu.
269
+ */
270
+ function pontuar(skill, texto) {
271
+ const t = semAcento(texto);
272
+ let pontos = 0;
273
+ for (const palavra of skill.quando) {
274
+ const p = semAcento(palavra).trim();
275
+ if (!p) continue;
276
+ const escapado = p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
277
+ // A borda e escrita a mao em vez de `\b`: o gatilho pode comecar ou
278
+ // terminar em simbolo ("e-mail"), e ali o `\b` nao vale.
279
+ const re = new RegExp('(^|[^a-z0-9])' + escapado + '($|[^a-z0-9])');
280
+ if (re.test(t)) pontos++;
281
+ }
282
+ return pontos;
283
+ }
284
+
285
+ /**
286
+ * As skills que valem a pena para ESTE pedido, das mais ao menos relevantes.
287
+ *
288
+ * Só as ligadas, só as que casaram com alguma palavra, e só até o teto de
289
+ * caracteres. Skill que não casou não entra: mandar as oito em todo pedido
290
+ * custaria milhares de tokens para sete delas não terem nada a ver com o que
291
+ * foi pedido.
292
+ */
293
+ function paraOPedido(texto, { teto = MAX_CARACTERES, todas = null } = {}) {
294
+ const lista = (todas || listar()).filter((s) => s.ligada && s.corpo);
295
+ const casadas = lista
296
+ .map((s) => ({ skill: s, pontos: pontuar(s, texto) }))
297
+ .filter((x) => x.pontos > 0)
298
+ .sort((a, b) => b.pontos - a.pontos);
299
+
300
+ const escolhidas = [];
301
+ let tamanho = 0;
302
+ for (const { skill } of casadas) {
303
+ if (tamanho + skill.corpo.length > teto) continue;
304
+ escolhidas.push(skill);
305
+ tamanho += skill.corpo.length;
306
+ }
307
+ return escolhidas;
308
+ }
309
+
310
+ /**
311
+ * O texto que vai na frente da tarefa.
312
+ *
313
+ * Vem antes do pedido, e não depois, pelo mesmo motivo do briefing dos
314
+ * especialistas: é contexto para ler o pedido, não um adendo a ele.
315
+ */
316
+ function montar(texto, op = {}) {
317
+ const escolhidas = paraOPedido(texto, op);
318
+ if (!escolhidas.length) return { texto, skills: [] };
319
+ const blocos = escolhidas.map((s) => `### ${s.titulo}\n${s.corpo}`);
320
+ const cabeca = 'O QUE VOCÊ SABE SOBRE ESTE TIPO DE TAREFA (siga ao trabalhar):\n\n'
321
+ + blocos.join('\n\n') + '\n\n---\n\nPEDIDO:\n';
322
+ return { texto: cabeca + texto, skills: escolhidas };
323
+ }
324
+
325
+ module.exports = {
326
+ CASA, ORIGEM, MAX_CARACTERES,
327
+ separar, listar, ler, criar, ligar, apagar, instalarDe, apelidar,
328
+ paraOPedido, montar, pontuar, prepararCasa,
329
+ };