primocode 10.16.0 → 10.17.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.
Files changed (237) hide show
  1. package/LICENSES/edit-ia-pro.txt +32 -0
  2. package/LICENSES/fontes-OFL.txt +476 -0
  3. package/LICENSES/hyperframes-Apache-2.0.txt +205 -0
  4. package/LICENSES/permanent-marker-Apache-2.0.txt +204 -0
  5. package/LICENSES/selfie-multiclass-Apache-2.0.txt +227 -0
  6. package/LICENSES/three-MIT.txt +23 -0
  7. package/LICENSES/video-use-MIT.txt +24 -0
  8. package/LICENSES/woven-sfx-CC0.txt +16 -0
  9. package/LICENSES/yunet-MIT.txt +21 -0
  10. package/README.md +2 -2
  11. package/bin/primocode.js +28 -8
  12. package/editor/LICENSE.md +11 -0
  13. package/editor/README.md +321 -0
  14. package/editor/SKILL.md +307 -0
  15. package/editor/THIRD_PARTY_NOTICES.md +228 -0
  16. package/editor/assets/baixar.json +591 -0
  17. package/editor/assets/fonts/anton/OFL.txt +93 -0
  18. package/editor/assets/fonts/caveat/OFL.txt +93 -0
  19. package/editor/assets/fonts/figtree/OFL.txt +93 -0
  20. package/editor/assets/fonts/fonts.json +10 -0
  21. package/editor/assets/fonts/montserrat/OFL.txt +93 -0
  22. package/editor/assets/fonts/permanentmarker/LICENSE.txt +202 -0
  23. package/editor/assets/models/SELFIE_MULTICLASS_LICENSE.txt +227 -0
  24. package/editor/assets/models/YUNET_LICENSE.txt +21 -0
  25. package/editor/assets/sfx/licencas/bass_drop.txt +32 -0
  26. package/editor/assets/sfx/licencas/cash.txt +76 -0
  27. package/editor/assets/sfx/licencas/charge.txt +31 -0
  28. package/editor/assets/sfx/licencas/click.txt +46 -0
  29. package/editor/assets/sfx/licencas/coin.txt +50 -0
  30. package/editor/assets/sfx/licencas/count.txt +47 -0
  31. package/editor/assets/sfx/licencas/data.txt +31 -0
  32. package/editor/assets/sfx/licencas/error.txt +32 -0
  33. package/editor/assets/sfx/licencas/flash.txt +31 -0
  34. package/editor/assets/sfx/licencas/gears.txt +82 -0
  35. package/editor/assets/sfx/licencas/keyboard.txt +48 -0
  36. package/editor/assets/sfx/licencas/metal_hit.txt +62 -0
  37. package/editor/assets/sfx/licencas/metal_riser.txt +61 -0
  38. package/editor/assets/sfx/licencas/mouse_click.txt +50 -0
  39. package/editor/assets/sfx/licencas/notification.txt +54 -0
  40. package/editor/assets/sfx/licencas/pop.txt +38 -0
  41. package/editor/assets/sfx/licencas/rewind.txt +32 -0
  42. package/editor/assets/sfx/licencas/rise.txt +46 -0
  43. package/editor/assets/sfx/licencas/sinteticos.txt +21 -0
  44. package/editor/assets/sfx/licencas/snap.txt +50 -0
  45. package/editor/assets/sfx/licencas/success.txt +40 -0
  46. package/editor/assets/sfx/licencas/synth_hit.txt +32 -0
  47. package/editor/assets/sfx/licencas/tick.txt +14 -0
  48. package/editor/assets/sfx/licencas/whoosh.txt +47 -0
  49. package/editor/assets/sfx/licencas/wind.txt +31 -0
  50. package/editor/assets/sfx/sfx.json +832 -0
  51. package/editor/assets/sfx/sfx_sinteticos.json +317 -0
  52. package/editor/assets/sfx/sfx_woven.json +387 -0
  53. package/editor/assets/sfx/woven/LICENCA.md +16 -0
  54. package/editor/fichas/briefing-do-video.md +45 -0
  55. package/editor/fichas/conferencia-da-amostra.md +56 -0
  56. package/editor/fichas/matriz-de-ganchos.csv +3 -0
  57. package/editor/fichas/padrao.json +16 -0
  58. package/editor/fichas/planilha-de-cenas.csv +4 -0
  59. package/editor/fichas/planilha-de-versoes.csv +4 -0
  60. package/editor/fichas/plano-em-blocos.md +32 -0
  61. package/editor/fichas/registro-de-materiais.json +27 -0
  62. package/editor/helpers/assets_ocr/ocr_vision.swift +41 -0
  63. package/editor/helpers/bordas_precisas.py +272 -0
  64. package/editor/helpers/camera_viva.py +426 -0
  65. package/editor/helpers/captions.py +601 -0
  66. package/editor/helpers/common.py +308 -0
  67. package/editor/helpers/compose_final.py +3555 -0
  68. package/editor/helpers/conferir_corte.py +234 -0
  69. package/editor/helpers/conferir_dados.py +405 -0
  70. package/editor/helpers/contact_sheet.py +282 -0
  71. package/editor/helpers/corte_por_onda.py +433 -0
  72. package/editor/helpers/corte_seco.py +595 -0
  73. package/editor/helpers/denoise.py +971 -0
  74. package/editor/helpers/efeitos.py +408 -0
  75. package/editor/helpers/efeitos_gerar.py +159 -0
  76. package/editor/helpers/elevenlabs_api.py +442 -0
  77. package/editor/helpers/face_layout.py +570 -0
  78. package/editor/helpers/fetch_images.py +423 -0
  79. package/editor/helpers/ganho_por_trecho.py +214 -0
  80. package/editor/helpers/gen_image.py +312 -0
  81. package/editor/helpers/gen_music.py +316 -0
  82. package/editor/helpers/gen_sfx.py +148 -0
  83. package/editor/helpers/grade.py +307 -0
  84. package/editor/helpers/icloud.py +119 -0
  85. package/editor/helpers/make_sfx.py +940 -0
  86. package/editor/helpers/matte.py +1342 -0
  87. package/editor/helpers/medir_referencia.py +2232 -0
  88. package/editor/helpers/mixar_trilha.py +354 -0
  89. package/editor/helpers/montar_corte.py +295 -0
  90. package/editor/helpers/motion/__init__.py +13 -0
  91. package/editor/helpers/motion/engine.py +1915 -0
  92. package/editor/helpers/motion/registry.py +510 -0
  93. package/editor/helpers/motion/render.py +665 -0
  94. package/editor/helpers/motion/templates/barra_progresso.py +370 -0
  95. package/editor/helpers/motion/templates/carimbo.py +132 -0
  96. package/editor/helpers/motion/templates/checklist.py +271 -0
  97. package/editor/helpers/motion/templates/comentario_cta.py +380 -0
  98. package/editor/helpers/motion/templates/contador.py +222 -0
  99. package/editor/helpers/motion/templates/destaque.py +263 -0
  100. package/editor/helpers/motion/templates/etiqueta_preco.py +461 -0
  101. package/editor/helpers/motion/templates/grafico_barras.py +510 -0
  102. package/editor/helpers/motion/templates/grafico_linha.py +462 -0
  103. package/editor/helpers/motion/templates/notificacao.py +266 -0
  104. package/editor/helpers/motion/templates/palavra_impacto.py +410 -0
  105. package/editor/helpers/motion/templates/passos.py +321 -0
  106. package/editor/helpers/motion/templates/riscado.py +298 -0
  107. package/editor/helpers/motion/templates/selo_garantia.py +150 -0
  108. package/editor/helpers/motion_render.py +241 -0
  109. package/editor/helpers/motion_sugerir.py +961 -0
  110. package/editor/helpers/noise_check.py +134 -0
  111. package/editor/helpers/pack_transcripts.py +223 -0
  112. package/editor/helpers/palavras_na_onda.py +176 -0
  113. package/editor/helpers/plano_sfx.py +839 -0
  114. package/editor/helpers/portao.py +427 -0
  115. package/editor/helpers/preview_server.py +1177 -0
  116. package/editor/helpers/rastrear_rosto.py +298 -0
  117. package/editor/helpers/render_cut.py +1256 -0
  118. package/editor/helpers/respiros.py +89 -0
  119. package/editor/helpers/sincronizar_cameras.py +571 -0
  120. package/editor/helpers/speech_regions.py +423 -0
  121. package/editor/helpers/style_catalog.json +231 -0
  122. package/editor/helpers/style_samples.py +246 -0
  123. package/editor/helpers/tela_verde.py +2113 -0
  124. package/editor/helpers/text_render.py +2059 -0
  125. package/editor/helpers/texto_na_tela.py +366 -0
  126. package/editor/helpers/timeline_view.py +292 -0
  127. package/editor/helpers/tira_de_quadros.py +113 -0
  128. package/editor/helpers/transcribe.py +734 -0
  129. package/editor/helpers/transcribe_batch.py +129 -0
  130. package/editor/helpers/trilhas.py +1146 -0
  131. package/editor/helpers/trilhas_humores.json +66 -0
  132. package/editor/helpers/valores.py +187 -0
  133. package/editor/helpers/verify_cut.py +820 -0
  134. package/editor/helpers/voice_levels.py +233 -0
  135. package/editor/helpers/voz_check.py +386 -0
  136. package/editor/helpers/voz_limpa.py +81 -0
  137. package/editor/helpers/watch_preview.py +372 -0
  138. package/editor/modelo-projeto/LEIA.md +402 -0
  139. package/editor/modelo-projeto/assets/fontes/OFL-Caveat.txt +93 -0
  140. package/editor/modelo-projeto/assets/fontes/OFL-JetBrainsMono.txt +93 -0
  141. package/editor/modelo-projeto/assets/fontes/OFL-Montserrat.txt +93 -0
  142. package/editor/modelo-projeto/assets/fontes/larguras.json +1 -0
  143. package/editor/modelo-projeto/assets/three/BufferGeometryUtils.js +1438 -0
  144. package/editor/modelo-projeto/assets/three/GLTFLoader.js +4895 -0
  145. package/editor/modelo-projeto/assets/three/LICENSE +21 -0
  146. package/editor/modelo-projeto/assets/three/ORIGEM.txt +17 -0
  147. package/editor/modelo-projeto/assets/three/RoomEnvironment.js +185 -0
  148. package/editor/modelo-projeto/assets/three/three.core.min.js +6 -0
  149. package/editor/modelo-projeto/assets/three/three.module.min.js +6 -0
  150. package/editor/modelo-projeto/build.py +4828 -0
  151. package/editor/modelo-projeto/compositions/cenas/2d/k2d_antes_depois.html +199 -0
  152. package/editor/modelo-projeto/compositions/cenas/2d/k2d_arquivo.html +232 -0
  153. package/editor/modelo-projeto/compositions/cenas/2d/k2d_busca.html +315 -0
  154. package/editor/modelo-projeto/compositions/cenas/2d/k2d_comando.html +304 -0
  155. package/editor/modelo-projeto/compositions/cenas/2d/k2d_conversa.html +217 -0
  156. package/editor/modelo-projeto/compositions/cenas/2d/k2d_esquema.html +289 -0
  157. package/editor/modelo-projeto/compositions/cenas/2d/k2d_medidor.html +249 -0
  158. package/editor/modelo-projeto/compositions/cenas/2d/k2d_notas.html +233 -0
  159. package/editor/modelo-projeto/compositions/cenas/3d/k3d_caminho.html +197 -0
  160. package/editor/modelo-projeto/compositions/cenas/3d/k3d_explodida.html +152 -0
  161. package/editor/modelo-projeto/compositions/cenas/3d/k3d_tela.html +167 -0
  162. package/editor/modelo-projeto/compositions/cenas/3d/k3d_volume.html +124 -0
  163. package/editor/modelo-projeto/compositions/cenas/3d/motor-three.js +728 -0
  164. package/editor/modelo-projeto/compositions/cenas/LEIA.md +177 -0
  165. package/editor/modelo-projeto/ferramentas/medir_fontes.py +65 -0
  166. package/editor/modelo-projeto/ferramentas/pessoa_recortada.py +176 -0
  167. package/editor/modelo-projeto/ferramentas/rastrear_maos.py +189 -0
  168. package/editor/modelo-projeto/hyperframes.json +12 -0
  169. package/editor/modelo-projeto/package.json +11 -0
  170. package/editor/modelo-projeto/projeto.json +16 -0
  171. package/editor/preview/app.css +1009 -0
  172. package/editor/preview/app.js +2122 -0
  173. package/editor/preview/index.html +307 -0
  174. package/editor/pyproject.toml +23 -0
  175. package/editor/references/3d.md +236 -0
  176. package/editor/references/animacao-catalogo.md +672 -0
  177. package/editor/references/animacao.md +498 -0
  178. package/editor/references/comandos.md +92 -0
  179. package/editor/references/corte-seco-como-funciona.md +50 -0
  180. package/editor/references/corte-seco.md +88 -0
  181. package/editor/references/corte.md +290 -0
  182. package/editor/references/direcao-por-blocos.md +218 -0
  183. package/editor/references/enquadramento.md +654 -0
  184. package/editor/references/estilos-prontos.md +355 -0
  185. package/editor/references/estudio.md +100 -0
  186. package/editor/references/exemplo-eventos.json +67 -0
  187. package/editor/references/lettering.md +252 -0
  188. package/editor/references/modelos.md +494 -0
  189. package/editor/references/phase2.md +1007 -0
  190. package/editor/references/referencia-aplicar.md +203 -0
  191. package/editor/references/referencia-medir.md +375 -0
  192. package/editor/references/revisao.md +335 -0
  193. package/editor/references/som-e-trilha.md +146 -0
  194. package/editor/references/tabela-de-sons.md +130 -0
  195. package/editor/references/variacoes.md +234 -0
  196. package/editor/uv.lock +1141 -0
  197. package/lib/act.js +36 -8
  198. package/lib/catalogo.js +4 -0
  199. package/lib/chamada-torta.js +81 -0
  200. package/lib/editor-pro.js +786 -0
  201. package/lib/entrega.js +58 -0
  202. package/lib/estudio/conferencia-visual.js +76 -0
  203. package/lib/estudio/diretor.js +6 -1
  204. package/lib/estudio/exportar.js +3 -1
  205. package/lib/estudio/gosto.js +45 -4
  206. package/lib/estudio/index.js +8 -5
  207. package/lib/estudio/kit-ae/base.jsx +119 -13
  208. package/lib/estudio/kit-ae/cenas.jsx +47 -15
  209. package/lib/estudio/kit-ae/guarda.jsx +274 -0
  210. package/lib/estudio/kit-ae/primitivas.jsx +180 -24
  211. package/lib/estudio/maquina-diretor.js +61 -7
  212. package/lib/estudio/maquina-foto.js +177 -0
  213. package/lib/estudio/maquina-guarda.js +207 -0
  214. package/lib/estudio/maquina-layout.js +22 -1
  215. package/lib/estudio/maquina.js +60 -11
  216. package/lib/estudio/referencias.js +1 -1
  217. package/lib/estudio/visao/visao.swift +97 -0
  218. package/lib/estudio/visao.js +60 -0
  219. package/lib/local/cerebro.json +192 -6
  220. package/lib/local/motor.js +5 -2
  221. package/lib/mcp.js +105 -7
  222. package/lib/nuvem.js +11 -1
  223. package/lib/outra-pasta.js +1 -1
  224. package/lib/passagem.js +50 -0
  225. package/lib/perigo.js +29 -12
  226. package/lib/protecao.js +134 -0
  227. package/lib/sandbox.js +11 -4
  228. package/lib/titulo-conversa.js +46 -0
  229. package/lib/tools.js +3 -0
  230. package/lib/ui.js +1 -1
  231. package/package.json +3 -2
  232. package/skills/editar-meu-video/SKILL.md +6 -2
  233. package/skills/editar-video/SKILL.md +113 -0
  234. package/studio/pessoa.py +52 -0
  235. package/voz/primo_voz/app.py +43 -4
  236. package/voz/primo_voz/chat.html +17 -6
  237. package/voz/primo_voz/painel_dados.py +134 -0
package/lib/nuvem.js CHANGED
@@ -352,7 +352,7 @@ function tituloDe(texto) {
352
352
  * terminal e simplesmente não sobe. Derrubar o turno de trabalho de alguém
353
353
  * porque o histórico não salvou seria trocar um incômodo por um prejuízo.
354
354
  */
355
- function gravador() {
355
+ function gravador({ batizar } = {}) {
356
356
  let id = null;
357
357
  let titulo = null;
358
358
  let contagem = 0;
@@ -381,6 +381,7 @@ function gravador() {
381
381
  const s = await abrir();
382
382
  if (!s) return; // sem conta: fica só no local
383
383
  if (!titulo) titulo = tituloDe(pergunta);
384
+ const nova = !id; // só a primeira troca de uma conversa nova ganha título da IA
384
385
  if (!id) {
385
386
  const r = await salvarConversa({ titulo });
386
387
  if (!r.ok) { ultimoErro = r.error; return; }
@@ -396,6 +397,15 @@ function gravador() {
396
397
  // celular. Sem esta volta, a conversa de hoje aparece embaixo
397
398
  // das de semana passada.
398
399
  await salvarConversa({ id, titulo, mensagens: contagem });
400
+ if (nova && batizar) {
401
+ // O título da IA entra depois, num PATCH à parte: se falhar, fica o de antes, sem erro.
402
+ let t = null;
403
+ try { t = await batizar(pergunta, resposta); } catch { /* fica o título de antes */ }
404
+ if (t && t !== titulo) {
405
+ const r = await salvarConversa({ id, titulo: t, mensagens: contagem });
406
+ if (r.ok) titulo = t;
407
+ }
408
+ }
399
409
  });
400
410
  },
401
411
 
@@ -152,7 +152,7 @@ async function pedirTroca(args, { atual = '', subagent = false, trocar = null, r
152
152
  const r = await trocar({ caminho: destino, motivo });
153
153
  if (!r || !r.ok) {
154
154
  return { ok: false, recusado: true,
155
- error: (r && r.error) || 'o usuário NÃO quis trocar de pasta. Continue na atual; ler a outra continua liberado.' };
155
+ error: (r && r.error) || 'o usuário NÃO quis trocar de pasta. Continue na atual; ler a outra pasta NÃO foi liberado.' };
156
156
  }
157
157
  return { ok: true, caminho: r.caminho || destino, ...(r.memoria ? { memoria: r.memoria } : {}),
158
158
  comoUsar: 'a pasta de trabalho agora é esta: caminhos relativos valem a partir dela.' };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * passagem.js — quando o agente PARA sem terminar, ele passa o bastão.
3
+ *
4
+ * Visto no SystemOneHarness (harnessrouter, Apache-2.0; só a ideia, código
5
+ * nosso): ao encerrar sem confiança, o agente grava o estado e o que falta
6
+ * para quem assumir — em vez de só dizer "parei". Aqui, "quem assume" é a
7
+ * próxima conversa ("continua"), que lê o memory.md da pasta.
8
+ *
9
+ * Antes, a memória guardava `Pedido` + os 500 primeiros caracteres do motivo
10
+ * ("Usei as 40 voltas…"). A conversa seguinte sabia QUE parou, mas não o que
11
+ * já existia no disco, o que falhou, nem por onde recomeçar — e relia a pasta
12
+ * às cegas (ou reescrevia um arquivo que já estava pronto).
13
+ *
14
+ * Puro: recebe o registro do pedido (lib/registro.js) e devolve o texto.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const lista = (xs, n = 12) => {
20
+ const v = (xs || []).filter(Boolean);
21
+ return v.length ? v.slice(0, n).join(', ') + (v.length > n ? ` (+${v.length - n})` : '') : '';
22
+ };
23
+
24
+ /**
25
+ * @param {{pedido: string, motivo: string, registro?: object, falhasPendentes?: Array<{texto: string}>}} dados
26
+ * @returns {string} bloco curto, em português, para o memory.md
27
+ */
28
+ function montar({ pedido, motivo, registro, falhasPendentes = [] }) {
29
+ const reg = registro || {};
30
+ const L = [`**Pedido (NÃO concluído):** ${String(pedido || '').replace(/\s+/g, ' ').trim().slice(0, 300)}`,
31
+ `**Por que parei:** ${String(motivo || 'sem explicação').replace(/\s+/g, ' ').trim().slice(0, 300)}`];
32
+ const criados = lista(reg.criados), alterados = lista(reg.alterados);
33
+ if (criados) L.push(`**Já existe no disco (criei):** ${criados}`);
34
+ if (alterados) L.push(`**Já mudei:** ${alterados}`);
35
+ const testes = (reg.testes || []).slice(-3).map((t) => `${t.comando}: ${t.ok ? 'passou' : 'FALHOU'}`);
36
+ if (testes.length) L.push(`**Últimos testes:** ${testes.join(' | ')}`);
37
+ const falhas = [
38
+ ...(reg.falhas || []).slice(-3).map((f) => `${f.acao}${f.alvo ? ' ' + f.alvo : ''}: ${f.motivo}`),
39
+ ...falhasPendentes.slice(-2).map((f) => f.texto),
40
+ ].map((x) => String(x).replace(/\s+/g, ' ').slice(0, 160));
41
+ const unicas = [...new Set(falhas)];
42
+ if (unicas.length) L.push(`**O que falhou:** ${unicas.join(' | ')}`);
43
+ const arquivosFeitos = !!(criados || alterados);
44
+ L.push(arquivosFeitos
45
+ ? '**Ao retomar:** NÃO recomece do zero nem reescreva o que já existe — leia os arquivos acima, confira o que falta contra o pedido e continue de onde parou.'
46
+ : '**Ao retomar:** nada foi gravado ainda — comece pelo pedido, mas por um caminho diferente do que travou.');
47
+ return L.join('\n');
48
+ }
49
+
50
+ module.exports = { montar };
package/lib/perigo.js CHANGED
@@ -25,6 +25,7 @@
25
25
 
26
26
  const os = require('os');
27
27
  const path = require('path');
28
+ const protecao = require('./protecao');
28
29
 
29
30
  const MODOS = ['auto', 'perguntar', 'livre', 'plano'];
30
31
  const COMANDOS = new Set(['run_shell', 'run_in_new_terminal']);
@@ -122,7 +123,7 @@ function analisarTrecho(palavras, estado) {
122
123
  if (cmd === 'sudo' || cmd === 'doas') return 'usa sudo: mexe como administrador no computador inteiro';
123
124
  if (cmd === 'cd') { estado.cwd = path.resolve(estado.cwd, expandir(alvos[0] || os.homedir())); return null; }
124
125
  if ((cmd === 'sh' || cmd === 'bash' || cmd === 'zsh') && args[0] === '-c' && args[1]) {
125
- return analisarComando(args[1], estado.cwd, estado.projeto);
126
+ return analisarComando(args[1], estado.cwd, estado.projeto, estado.baixados);
126
127
  }
127
128
  if (cmd === 'rm' || cmd === 'rmdir' || cmd === 'trash') {
128
129
  const recursivo = cmd === 'rmdir' || tem(/^-[a-zA-Z]*[rR]|^--recursive$/);
@@ -141,6 +142,7 @@ function analisarTrecho(palavras, estado) {
141
142
  }
142
143
  if (cmd === 'git') {
143
144
  const sub = alvos[0];
145
+ if (sub === 'add') { const r = protecao.gitAddLevaSegredo(alvos, flags, estado.cwd); if (r) return r; }
144
146
  if (sub === 'push' && (tem(/^--force$|^-[a-zA-Z]*f/) || alvos.some((a) => /^\+/.test(a)))) {
145
147
  return 'git push --force: reescreve o histórico no GitHub, e o que estava lá some';
146
148
  }
@@ -188,7 +190,7 @@ function mexeNoLivro(palavras, cwd) {
188
190
  return p.slice(1).some((x) => !/^-/.test(x) && noLivro(x.split(/>>?/)[0]));
189
191
  }
190
192
 
191
- function analisarComando(comando, cwd, projeto) {
193
+ function analisarComando(comando, cwd, projeto, baixados = null) {
192
194
  const s = String(comando || '');
193
195
  // Baixar e rodar na hora: o que roda é o que o site mandar naquele segundo.
194
196
  if (/\b(curl|wget)\b[^;&]*\|\s*(sudo\s+)?(env\s+)?(ba|z|da|k)?sh\b/.test(s)) {
@@ -196,11 +198,16 @@ function analisarComando(comando, cwd, projeto) {
196
198
  }
197
199
  // O mesmo que mcp_conectar, pela porta dos fundos: não pula o portão.
198
200
  if (/\bprimocode\s+mcp\s+(conectar|connect|add)\b/.test(s)) return 'instala e roda um servidor MCP de terceiros';
199
- const estado = { cwd, projeto, pulandoFlags: false };
201
+ const estado = { cwd, projeto, pulandoFlags: false, baixados };
200
202
  for (const t of trechos(s)) {
201
203
  if (mexeNoLivro(t.palavras, estado.cwd)) return 'mexe no livro de ações do PrimoCode (~/.primocode/registro), o registro do que o agente fez';
202
204
  const r = analisarTrecho(t.palavras, estado);
203
205
  if (r) return r;
206
+ const prot = protecao.shellEscreveEmProtegido(t.palavras);
207
+ if (prot) return prot;
208
+ const baix = protecao.rodaBaixado(t.palavras, estado.cwd, baixados);
209
+ if (baix) return baix;
210
+ if (baixados) protecao.baixadosPor(t.palavras).forEach((x) => baixados.add(path.resolve(estado.cwd, x)));
204
211
  }
205
212
  return null;
206
213
  }
@@ -209,13 +216,13 @@ function analisarComando(comando, cwd, projeto) {
209
216
  * Esta ação é perigosa?
210
217
  * @returns {{perigoso: boolean, motivo: string}}
211
218
  */
212
- function classificar(nome, args, { projectDir } = {}) {
219
+ function classificar(nome, args, { projectDir, baixados = null } = {}) {
213
220
  const a = args || {};
214
221
  const projeto = path.resolve(projectDir || process.cwd());
215
- let motivo = null;
222
+ let motivo = null, fora = false;
216
223
  if (COMANDOS.has(nome)) {
217
224
  const cwd = a.cwd ? path.resolve(projeto, expandir(a.cwd)) : projeto;
218
- motivo = analisarComando(a.command, cwd, projeto);
225
+ motivo = analisarComando(a.command, cwd, projeto, baixados);
219
226
  } else if (nome === 'mcp_conectar') {
220
227
  /* Instala e roda código de terceiro (npx/uvx/docker) com as chaves do
221
228
  usuário: sempre pergunta, como o curl | sh. O motivo leva o comando,
@@ -227,9 +234,10 @@ function classificar(nome, args, { projectDir } = {}) {
227
234
  const rel = path.relative(projeto, alvo);
228
235
  if (rel.startsWith('..') || path.isAbsolute(rel) || /^[a-zA-Z]:[\\/]/.test(String(a.path))) {
229
236
  motivo = `mexe num arquivo fora da pasta do projeto (${a.path})`;
230
- }
237
+ fora = true;
238
+ } else motivo = protecao.motivoDoArquivo(rel);
231
239
  }
232
- return { perigoso: !!motivo, motivo: motivo || '' };
240
+ return { perigoso: !!motivo, motivo: motivo || '', fora };
233
241
  }
234
242
 
235
243
  /* Comando que só olha: todo trecho começa por uma palavra de leitura, sem
@@ -250,7 +258,15 @@ function somenteLeitura(comando) {
250
258
  });
251
259
  }
252
260
 
253
- function novo() { return { liberados: new Set(), negados: new Set() }; }
261
+ function sha256(t) { return require('crypto').createHash('sha256').update(String(t)).digest('hex'); }
262
+
263
+ function chaveDoPerigo(nome, args) {
264
+ const a = args || {};
265
+ if (nome === 'mcp_conectar') return `${nome}\0${JSON.stringify([a.nome, a.comando || a.command, a.args, a.env])}`;
266
+ return `${nome}\0${JSON.stringify(a)}`;
267
+ }
268
+
269
+ function novo() { return { liberados: new Set(), negados: new Set(), baixados: new Set() }; }
254
270
 
255
271
  /* Uma pergunta por vez. Subagentes rodam em paralelo (Promise.all) e dois
256
272
  pedidos ao mesmo tempo no mesmo terminal se atropelariam. */
@@ -274,7 +290,7 @@ async function portao(estado, { modo, nome, args, projectDir, subagent = false,
274
290
  }
275
291
  return { ok: true };
276
292
  }
277
- const { perigoso, motivo: motivoPerigo } = classificar(nome, args, { projectDir });
293
+ const { perigoso, motivo: motivoPerigo, fora } = classificar(nome, args, { projectDir, baixados: estado.baixados });
278
294
  const tipo = perigoso ? 'perigo' : (m === 'perguntar' && (COMANDOS.has(nome) || nome === 'mcp_usar')) ? 'comando'
279
295
  : (m === 'perguntar' && ESCRITAS.has(nome)) ? 'arquivo' : null;
280
296
  if (!tipo) return { ok: true };
@@ -287,7 +303,7 @@ async function portao(estado, { modo, nome, args, projectDir, subagent = false,
287
303
 
288
304
  // Escrever fora da pasta o tools.js já recusa sozinho: perguntar seria
289
305
  // pedir um "sim" que não serve para nada.
290
- if (perigoso && ESCRITAS.has(nome)) {
306
+ if (perigoso && fora && ESCRITAS.has(nome)) {
291
307
  return recusa(`Recusado: ${motivo}. O PrimoCode só escreve dentro da pasta do projeto — use /dir se a pessoa quiser trabalhar em outra pasta.`);
292
308
  }
293
309
  if (subagent) {
@@ -300,7 +316,8 @@ async function portao(estado, { modo, nome, args, projectDir, subagent = false,
300
316
  // "A sessão inteira" libera o TIPO de pergunta: no perigoso, aquele motivo
301
317
  // (um push --force autorizado não autoriza um rm -rf ~); no modo
302
318
  // perguntar, os comandos ou as escritas comuns.
303
- const liberacao = perigoso ? `perigo:${motivo}` : tipo;
319
+ // O motivo do mcp_conectar é texto de tela (cortado em 80): a chave leva o comando inteiro.
320
+ const liberacao = perigoso ? `perigo:${motivo}${nome === 'mcp_conectar' ? ':' + sha256(chaveDoPerigo(nome, args)) : ''}` : tipo;
304
321
  if (estado.liberados.has(liberacao)) return { ok: true };
305
322
  if (typeof perguntar !== 'function') {
306
323
  return recusa(`Recusado: ${motivo}. Isso precisa de autorização e não há ninguém para autorizar neste modo.`);
@@ -0,0 +1,134 @@
1
+ /**
2
+ * protecao.js — três portas que o perigo.js não tinha, vistas no OpenWorker
3
+ * (andrewyng, MIT) e no repo de skills do Instagram (sergebulaev, MIT).
4
+ * Reescritas do nosso jeito: nenhum código copiado, só a ideia.
5
+ *
6
+ * 1. AUTOPROTEÇÃO. O agente não reescreve, calado, o arquivo que manda nele
7
+ * (AGENTS.md, CLAUDE.md, .primocode/hooks.json…). Quem escreve ali muda o
8
+ * comportamento de TODAS as próximas sessões, e a pessoa nem vê.
9
+ * 2. ARQUIVO QUE EXECUTA DEPOIS. `.git/hooks`, `.github/workflows`,
10
+ * `.vscode/tasks.json`: o texto que entra ali roda mais tarde, fora desta
11
+ * conversa e fora da vista da pessoa.
12
+ * 3. SEGREDO NO GIT. Uma chave real vazou num repo público porque o `.env`
13
+ * foi parar num commit. `git add .env` (ou `git add -A` com um `.env` que
14
+ * o .gitignore não cobre) agora pergunta.
15
+ * 4. PROCEDÊNCIA. Rodar um arquivo que o próprio agente baixou nesta sessão
16
+ * (`curl -o x.sh … && bash x.sh`) sempre pergunta: é o `curl | sh` com um
17
+ * passo no meio.
18
+ *
19
+ * Puro quase todo: o único I/O é ler o .gitignore e ver se o .env existe.
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+
27
+ const norm = (p) => String(p || '').replace(/\\/g, '/').replace(/^\.\//, '');
28
+
29
+ /** Esse caminho (relativo ao projeto) muda o agente ou roda depois? → motivo, ou null. */
30
+ function motivoDoArquivo(rel) {
31
+ const p = norm(rel);
32
+ const nome = path.posix.basename(p);
33
+ if (/^(agents|claude|gemini)\.md$/i.test(nome) || nome === '.cursorrules' || /(^|\/)\.github\/copilot-instructions\.md$/.test(p)
34
+ || /(^|\/)\.(claude|cursor)\/(rules|commands|agents|settings(\.local)?\.json|hooks)/.test(p)
35
+ || /(^|\/)\.primocode\/(hooks\.json|comandos\/)/.test(p)) {
36
+ return `mexe num arquivo de regras do agente (${nome}): o que entra ali vale para todas as próximas sessões`;
37
+ }
38
+ if (/(^|\/)\.git\/hooks\//.test(p)) return `escreve num gancho do git (${nome}): roda sozinho em todo commit`;
39
+ if (/(^|\/)\.github\/workflows\//.test(p)) return `escreve num fluxo do GitHub Actions (${nome}): roda nos servidores, com as chaves do repositório`;
40
+ if (/(^|\/)\.vscode\/tasks\.json$/.test(p)) return 'escreve em .vscode/tasks.json: o editor roda isso quando a pasta é aberta';
41
+ if (/(^|\/)\.husky\//.test(p)) return `escreve num gancho do husky (${nome}): roda sozinho em todo commit`;
42
+ return null;
43
+ }
44
+
45
+ /** Palavra de comando que aponta para arquivo protegido? Tira o `>`/`>>` do começo. */
46
+ function arquivoNaPalavra(w) {
47
+ const s = String(w).replace(/^\d?>>?/, '');
48
+ return s ? motivoDoArquivo(s) : null;
49
+ }
50
+
51
+ const ESCREVEM = new Set(['tee', 'cp', 'mv', 'ln', 'install', 'touch', 'truncate', 'rm', 'rsync', 'dd']);
52
+ /** Um trecho de shell (já em palavras) escreve num arquivo protegido? */
53
+ function shellEscreveEmProtegido(palavras) {
54
+ if (!palavras.length) return null;
55
+ const cmd = path.basename(palavras[0]);
56
+ const redirecionou = palavras.some((w, i) => /^\d?>>?$/.test(w) ? !!(palavras[i + 1] && motivoDoArquivo(palavras[i + 1])) : /^\d?>>?./.test(w) && !!arquivoNaPalavra(w));
57
+ const sedI = cmd === 'sed' && palavras.some((w) => /^-[a-zA-Z]*i/.test(w) || w === '--in-place');
58
+ if (!redirecionou && !sedI && !ESCREVEM.has(cmd)) return null;
59
+ for (let i = 0; i < palavras.length; i++) {
60
+ const w = palavras[i];
61
+ const alvo = /^\d?>>?$/.test(w) ? palavras[i + 1] : w;
62
+ const m = alvo && (/^\d?>>?./.test(alvo) ? arquivoNaPalavra(alvo) : (i > 0 || /^\d?>>?$/.test(w)) ? motivoDoArquivo(alvo) : null);
63
+ if (m) return m;
64
+ }
65
+ return null;
66
+ }
67
+
68
+ /* ── segredo no git ─────────────────────────────────────────────────── */
69
+
70
+ const MODELO = /\.(example|sample|template|dist|defaults?)$/i;
71
+ function nomeDeSegredo(alvo) {
72
+ const nome = path.posix.basename(norm(alvo));
73
+ if (MODELO.test(nome)) return false;
74
+ return /^\.env(\.[\w-]+)?$/i.test(nome) || /^id_(rsa|ed25519|ecdsa|dsa)$/.test(nome) || /\.(pem|p12|pfx|key)$/i.test(nome)
75
+ || /^(credentials?|service-account.*|secrets?)\.(json|ya?ml)$/i.test(nome) || nome === '.npmrc' || nome === '.netrc';
76
+ }
77
+
78
+ function envFicaDeFora(projeto) {
79
+ try {
80
+ const gi = fs.readFileSync(path.join(projeto, '.gitignore'), 'utf8').split('\n').map((l) => l.trim());
81
+ return gi.some((l) => /^\/?\.env(\*|\.\*)?\/?$/.test(l) || l === '*.env');
82
+ } catch { return false; }
83
+ }
84
+
85
+ /** `git add …` leva um segredo? `alvos` já sem as flags. */
86
+ function gitAddLevaSegredo(alvos, flags, projeto) {
87
+ const lista = alvos.slice(1);
88
+ const duro = lista.find(nomeDeSegredo);
89
+ if (duro) return `git add ${duro}: um arquivo de segredo no commit vira chave vazada no GitHub`;
90
+ const tudo = lista.some((a) => a === '.' || a === '*' || a === ':/') || flags.some((f) => /^(-A|--all|-a)$/.test(f));
91
+ if (tudo && projeto) {
92
+ const temEnv = ['.env', '.env.local', '.env.production'].some((n) => { try { return fs.existsSync(path.join(projeto, n)); } catch { return false; } });
93
+ if (temEnv && !envFicaDeFora(projeto)) return 'git add de tudo, com um .env na pasta que o .gitignore não cobre: a chave iria junto no commit';
94
+ }
95
+ return null;
96
+ }
97
+
98
+ /* ── procedência: o que o agente baixou ─────────────────────────────── */
99
+
100
+ /** Arquivos que um trecho de shell baixa (curl -o/-O, wget -O/padrão). */
101
+ function baixadosPor(palavras) {
102
+ const cmd = path.basename(palavras[0] || '');
103
+ const urls = palavras.filter((w) => /^https?:\/\//i.test(w));
104
+ const saida = [];
105
+ if (cmd === 'curl') {
106
+ for (let i = 1; i < palavras.length; i++) {
107
+ const w = palavras[i];
108
+ if ((w === '-o' || w === '--output') && palavras[i + 1]) saida.push(palavras[i + 1]);
109
+ else if (/^-[a-zA-Z]*O$/.test(w) || w === '--remote-name') urls.forEach((u) => saida.push(path.posix.basename(u.split(/[?#]/)[0])));
110
+ else if (/^-o./.test(w)) saida.push(w.slice(2));
111
+ }
112
+ } else if (cmd === 'wget') {
113
+ const o = palavras.findIndex((w) => w === '-O' || w === '--output-document');
114
+ if (o > 0 && palavras[o + 1]) saida.push(palavras[o + 1]);
115
+ else urls.forEach((u) => saida.push(path.posix.basename(u.split(/[?#]/)[0]) || 'index.html'));
116
+ }
117
+ return saida.filter((x) => x && x !== '-');
118
+ }
119
+
120
+ const INTERPRETES = new Set(['sh', 'bash', 'zsh', 'dash', 'node', 'python', 'python3', 'ruby', 'perl', 'source', '.', 'deno', 'bun', 'npx', 'tsx']);
121
+ /** O trecho roda um arquivo que foi baixado? `baixados` = Set de caminhos absolutos. */
122
+ function rodaBaixado(palavras, cwd, baixados) {
123
+ if (!baixados || !baixados.size || !palavras.length) return null;
124
+ const abs = (x) => path.resolve(cwd, String(x));
125
+ const cmd = palavras[0];
126
+ let alvo = null;
127
+ if (INTERPRETES.has(path.basename(cmd))) alvo = palavras.slice(1).find((w) => !/^-/.test(w));
128
+ else if (/^\.{0,2}\//.test(cmd) || cmd.includes('/')) alvo = cmd;
129
+ else if (baixados.has(abs(cmd))) alvo = cmd;
130
+ if (alvo && baixados.has(abs(alvo))) return `roda um arquivo que o próprio agente baixou nesta sessão (${path.basename(alvo)}) sem a pessoa ter lido`;
131
+ return null;
132
+ }
133
+
134
+ module.exports = { motivoDoArquivo, shellEscreveEmProtegido, nomeDeSegredo, gitAddLevaSegredo, envFicaDeFora, baixadosPor, rodaBaixado };
package/lib/sandbox.js CHANGED
@@ -31,7 +31,8 @@ const { execFile, spawnSync } = require('child_process');
31
31
 
32
32
  const MODOS = ['best_effort', 'hard_requirement'];
33
33
  const LEITURA_NEGADA_HOME = ['.ssh', '.aws', '.config/gcloud', '.npmrc',
34
- '.primocode/conta.json', '.netrc', '.git-credentials', '.config/gh', '.docker', '.kube', 'Library/Keychains'];
34
+ '.primocode/conta.json', '.netrc', '.git-credentials', '.config/gh', '.docker', '.kube', 'Library/Keychains',
35
+ '.config/git/credentials', '.pypirc', '.gnupg', '.cargo/credentials', '.cargo/credentials.toml'];
35
36
  // Socket do ssh-agent do macOS (launchd). O caminho exato vem do SSH_AUTH_SOCK; a regex pega os outros.
36
37
  const SOCKET_SSH_MAC = '^/private/(tmp|var/run)/com\\.apple\\.launchd\\.[^/]+/Listeners$';
37
38
  const SECRETA = /(_KEY|_TOKEN|SECRET)/i;
@@ -109,6 +110,10 @@ function perfilSeatbelt(projectDir, cfg = config(), home = os.homedir()) {
109
110
  l.push('(allow file-write* (literal "/dev/null") (literal "/dev/tty") (literal "/dev/dtracehelper") (regex #"^/dev/fd/"))');
110
111
  // Hook em .git/hooks roda fora da cerca no próximo `git commit` da pessoa. O .sample do `git init` passa.
111
112
  l.push('(deny file-write* (regex #"/\\.git/hooks/[^/]+$"))');
113
+ // A pasta em si também: sem `rm -rf .git/hooks && mv pasta-minha .git/hooks`, nem `mv .git` pra recriar.
114
+ // Só unlink/rename (não create): o `git init` precisa criar a pasta hooks.
115
+ l.push('(deny file-write-unlink (regex #"/\\.git/hooks$"))');
116
+ l.push('(deny file-write-unlink (regex #"/\\.git$"))');
112
117
  l.push('(allow file-write* (regex #"/\\.git/hooks/[^/]+\\.sample$"))');
113
118
  for (const p of negadosLeitura) l.push(`(deny file-read* (subpath ${sbq(real(p))}) (subpath ${sbq(p)}))`);
114
119
  l.push('(deny file-read* (regex #"/\\.env($|\\.[^/]*$)"))');
@@ -205,12 +210,14 @@ function filtrarEnv(env, permitidas) {
205
210
  }
206
211
 
207
212
  /** Roda o comando dentro da cerca. Mesmo formato do runShell: { ok, code, stdout, stderr }. */
208
- function executar(command, { projectDir, timeout = 60000, env = process.env, home } = {}) {
213
+ function executar(command, { projectDir, timeout = 60000, env = process.env, home, homeProtegido } = {}) {
209
214
  const cwd = projectDir || process.cwd();
210
215
  const cfg = config(home);
211
216
  const env2 = filtrarEnv(env, cfg.env_permitidas);
212
- // `home` só escolhe de onde lê a config; as pastas protegidas são sempre as do ~ de verdade.
213
- const e = envolver(command, cwd, { cfg });
217
+ // `home` só escolhe de onde lê a config; as pastas protegidas são as do ~ de verdade (`homeProtegido` é para teste).
218
+ const e = envolver(command, cwd, { cfg, home: homeProtegido });
219
+ // Sem .git/hooks (git init --template=), um `mv` criaria a pasta lá dentro. Criada aqui, fora da cerca, o unlink barra a troca.
220
+ try { if (fs.statSync(path.join(cwd, '.git')).isDirectory()) fs.mkdirSync(path.join(cwd, '.git', 'hooks'), { recursive: true }); } catch { /* sem .git: nada a proteger */ }
214
221
  return new Promise((resolve) => {
215
222
  if (e.recusado) return resolve({ ok: false, code: 126, stdout: '', stderr: e.recusado, sandbox: { recusado: true } });
216
223
  const termina = (error, stdout, stderr) => {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * titulo-conversa.js — quem dá título à conversa é quem leu a primeira troca.
3
+ *
4
+ * Antes o título era o começo da primeira mensagem ("Oi.", "E ele tem o…"): o
5
+ * gravador (lib/nuvem.js) usava `tituloDe`. Agora, depois da primeira resposta
6
+ * de uma conversa NOVA, o modelo escreve um título de 2 a 5 palavras e o
7
+ * gravador troca o do documento na nuvem. O app (Mac e desktop) lê esse título
8
+ * em `primocode conversas --json`, então a lateral muda sozinha.
9
+ *
10
+ * Se o modelo não responder a tempo, ou vier lixo, vale o título de antes:
11
+ * nenhum erro aparece. Puro, menos `escolher`, que recebe quem pergunta de fora.
12
+ */
13
+ 'use strict';
14
+
15
+ const TEMPO_MAXIMO_MS = 8000;
16
+
17
+ /** A pergunta. Curta: vai numa chamada à parte. */
18
+ function pergunta(pedido, resposta) {
19
+ return 'Dê um título curto para esta conversa, de 2 a 5 palavras, em português do Brasil. '
20
+ + 'O título diz o ASSUNTO (ex.: Site da padaria, Vídeo de lançamento, Erro no login), '
21
+ + 'nunca "Conversa" nem "Oi". Sem aspas, sem ponto final e sem explicação. Responda SÓ com o título.\n\n'
22
+ + `PESSOA: ${String(pedido || '').slice(0, 500)}\n\nPRIMO: ${String(resposta || '').slice(0, 500)}`;
23
+ }
24
+
25
+ /** Limpa a resposta do modelo. Devolve null quando não dá pra confiar nela. */
26
+ function limpar(resposta) {
27
+ const linha = String(resposta || '').trim().split('\n').map((l) => l.trim()).find(Boolean) || '';
28
+ const t = linha.replace(/^(t[ií]tulo)\s*:\s*/i, '').replace(/^[`"'“”*#\s]+|[`"'“”*.\s]+$/g, '').replace(/\s+/g, ' ');
29
+ const palavras = t.split(' ').filter(Boolean);
30
+ if (!t || t.length > 60 || palavras.length < 1 || palavras.length > 7) return null;
31
+ if (/^(conversa|oi|ol[aá]|t[ií]tulo|sem t[ií]tulo)$/i.test(t)) return null;
32
+ return t.charAt(0).toUpperCase() + t.slice(1);
33
+ }
34
+
35
+ /** Pergunta ao modelo e devolve o título limpo, ou null (falhou, demorou ou veio lixo). Nunca rejeita. */
36
+ async function escolher({ pedido, resposta, perguntar, tempoMs = TEMPO_MAXIMO_MS }) {
37
+ try {
38
+ let timer;
39
+ const limite = new Promise((ok) => { timer = setTimeout(() => ok(null), tempoMs); });
40
+ const r = await Promise.race([Promise.resolve(perguntar(pergunta(pedido, resposta))), limite]);
41
+ clearTimeout(timer);
42
+ return r == null ? null : limpar(r);
43
+ } catch { return null; }
44
+ }
45
+
46
+ module.exports = { pergunta, limpar, escolher, TEMPO_MAXIMO_MS };
package/lib/tools.js CHANGED
@@ -862,6 +862,9 @@ async function despachar(name, args, ctx) {
862
862
  // A transcrição precisa do servidor (a chave é dele), então o
863
863
  // contexto do pedido atravessa até aqui.
864
864
  return estudio.ferramentaVideo(args, ctx);
865
+ case 'editor_pro':
866
+ // A edição completa de vídeo FALADO: o motor do EDIT IA PRO (lib/editor-pro.js, editor/).
867
+ return require('./editor-pro.js').ferramenta(args, ctx);
865
868
  case 'estudio_render':
866
869
  // A pasta de trabalho vai junto: a composição do Hyperframes que a IA escreveu mora nela.
867
870
  return estudio.ferramentaRender({ ...args, __pasta: projectDir }, {
package/lib/ui.js CHANGED
@@ -552,7 +552,7 @@ const ROTULOS = {
552
552
  legítimo — e só a linha de baixo revelava que tinha falhado. */
553
553
  planejar_producao:'Diretor', estudio_projeto:'Estúdio·projeto',
554
554
  estudio_plano:'Estúdio·plano', estudio_narrar:'Estúdio·narração',
555
- estudio_video:'Estúdio·vídeo', estudio_render:'Estúdio·render',
555
+ estudio_video:'Estúdio·vídeo', estudio_render:'Estúdio·render', editor_pro:'Editor·vídeo',
556
556
  estudio_site:'Estúdio·marca',
557
557
  desktop_windows:'Desktop·janelas', desktop_focus:'Desktop·foco',
558
558
  desktop_elements:'Desktop·elementos', desktop_click_element:'Desktop·click',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "10.16.0",
3
+ "version": "10.17.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": {
@@ -10,7 +10,7 @@
10
10
  "start": "node bin/primocode.js",
11
11
  "postinstall": "node lib/preparar.js || true",
12
12
  "test:server": "cd server && npm test",
13
- "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/primo-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/planos-tela.test.js && 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/gerativo-variedade.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/conferencia.test.js && node test/cor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/mcp-http.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/rolar.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 && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js && node test/gerativo-so-fala.test.js && node test/estilos.test.js && node test/proporcao.test.js && node test/cena.test.js && node test/sem-narracao.test.js && node test/camadas.test.js && node test/gerativo-referencias.test.js && node test/voicestudio.test.js && node test/effectcraft.test.js && node test/projetos.test.js && node test/maquina.test.js && node test/maquina-som.test.js && node test/maquina-voz.test.js && node test/prova-fresca.test.js && node test/cota-video.test.js && node test/hooks.test.js && node test/modo-plano.test.js && node test/headless.test.js && node test/retomar.test.js && node test/sandbox.test.js && node test/exportar.test.js && node test/editor-video.test.js && node test/marca-kit.test.js && node test/preparar.test.js && node test/landing-planos.test.js && node test/limites.test.js && node test/app-chat-cli.test.js && node test/app-chat-ponte.test.js && node test/gosto-motion.test.js && node test/desktop-vivo.test.js && node test/memoria-usuario.test.js && node test/landing-instalar.test.js"
13
+ "test": "node scripts/testes.js"
14
14
  },
15
15
  "engines": {
16
16
  "node": ">=18.17.0"
@@ -31,6 +31,7 @@
31
31
  "lib/",
32
32
  "studio/",
33
33
  "skills/",
34
+ "editor/",
34
35
  "voz/",
35
36
  "LICENSES/",
36
37
  "README.md",
@@ -1,9 +1,13 @@
1
1
  ---
2
2
  nome: Editar o vídeo gravado
3
- descricao: como tratar uma gravação do usuário sem estragar o ritmo dela
4
- quando: gravei, gravação, gravacao, meu vídeo, meu video, editar, edição, edicao, corte, legenda, legendas, b-roll, broll
3
+ descricao: operações soltas numa gravação (importar, cortar silêncio, recortar a pessoa) — a edição completa é a skill editar-video
4
+ quando: cortar silêncio, cortar silencio, recortar a pessoa, trocar o fundo, b-roll, broll, importar a gravação
5
5
  autor: PrimoCode
6
6
  ---
7
+ A EDIÇÃO COMPLETA de um vídeo falado (corte, legenda animada, gancho, peças,
8
+ sons, trilha) é do `editor_pro` — carregue a skill `editar-video`. Esta aqui é
9
+ para as operações soltas no Estúdio.
10
+
7
11
  O vídeo é da pessoa. Ela apareceu, falou e errou — o seu trabalho é tirar o
8
12
  que atrapalha, não refazer o que ela fez.
9
13
 
@@ -0,0 +1,113 @@
1
+ ---
2
+ nome: Editar vídeo falado (motor EDIT IA PRO)
3
+ descricao: a edição completa da gravação da pessoa falando — corte, voz, legenda, gancho, peças, b-roll, sons, trilha e portão
4
+ quando: edita meu vídeo, editar meu vídeo, edite esse vídeo, editar o vídeo, edição do vídeo, edicao do video, reels, tiktok, shorts, criativo, legenda e efeitos, legendas, gancho, cortar os respiros, tirar os erros, melhor tomada, vídeo falado, gravei, gravação, gravacao, vsl, anúncio
5
+ autor: PrimoCode (adaptada do EDIT IA PRO, SHK Soluções Digitais, com autorização)
6
+ ---
7
+ Você é o editor e o diretor. O motor (ferramenta `editor_pro`, ou
8
+ `primocode editor <acao> --json '{...}'` pelo run_shell) mede e faz o
9
+ trabalho pesado; você decide o que aparece em cada momento e por quê.
10
+
11
+ ## Como falar com a pessoa
12
+
13
+ - Português simples, frases curtas. Nada de JSON, comando ou caminho comprido
14
+ na conversa: "estou ouvindo as tomadas", "tirei os respiros".
15
+ - Tempos no formato 00:12. Antes de passo demorado (transcrição, render),
16
+ diga mais ou menos quanto leva.
17
+ - No máximo UMA pergunta, e só se não der para deduzir ("é anúncio, Reels ou
18
+ aula?"). O resto tem padrão; conte as suposições numa frase no fim.
19
+
20
+ ## Princípios
21
+
22
+ 1. **A fala gravada manda.** Tudo entra no tempo da palavra que chama (pode
23
+ antecipar até 0,15 s). Tempo vem da fala que o `cortar` devolve, nunca
24
+ de estimativa.
25
+ 2. **Fidelidade.** Número, nome, prova e oferta só se a pessoa disse. Faltou
26
+ material: diga o que falta, não invente.
27
+ 3. **Respiro é erro.** O corte já tira silêncio de 0,25 s ou mais e fica com a
28
+ última tomada inteira de cada frase.
29
+ 4. **Uma ideia por vez na tela; rosto livre.** Nada sobre olhos e boca.
30
+ Alguma coisa muda a cada 2 a 4 s.
31
+ 5. **Só entrega com o portão PASSOU.** Reprovou: conserte o plano e monte de
32
+ novo. Nunca mostre um vídeo reprovado.
33
+
34
+ ## O caminho
35
+
36
+ 1. `editor_pro acao:"cortar" video:"<arquivo>" prompt:"nomes, de, marca"`.
37
+ Na primeira vez ele baixa os arquivos do editor e cria o Python dele
38
+ (avise: alguns minutos). Volta `pasta`, `duracao`, `volume` e `fala`: as
39
+ frases do corte com o tempo (`3.42-5.10 texto`). O original nunca é tocado.
40
+ 2. **Leia a fala e escreva o plano.** Divida por ideia e decida o que entra
41
+ em cada bloco (tabela "que peça para cada fala", abaixo).
42
+ 3. `editor_pro acao:"montar" pasta:"<pasta>" plano:{...} trilha:"auto"`.
43
+ Ele cria o projeto, põe a câmera viva, os sons casados com cada animação,
44
+ renderiza, mixa a trilha com ducking e roda o portão. Volta `video`
45
+ (`<pasta>/final.mp4`), `quadros` (6 PNGs), `audio` e `portao`.
46
+ 4. **Olhe os quadros** (read_file nos PNGs) antes de entregar. Algo feio
47
+ (texto cortado, peça no rosto, b-roll sem nada a ver): mude o plano e
48
+ monte de novo.
49
+ 5. Entregue com uma nota curta: onde está o vídeo, o que foi feito, o que
50
+ faltou. Atalho sem plano: `acao:"editar"` (o plano automático põe um
51
+ gancho e as peças que a fala pede).
52
+
53
+ ## O plano
54
+
55
+ ```json
56
+ {"gancho": "O que é *IA*",
57
+ "pecas": [
58
+ {"id": "def", "tipo": "lettering", "principal": "Máquinas *aprendem*", "apoio": "e decidem", "em": 6.9, "sai": 10.6, "posicao": "topo"},
59
+ {"id": "passos", "tipo": "passo_a_passo", "depois_de": 12.3, "sai": 23.1,
60
+ "passos": [{"texto": "Recebe exemplos", "em": 12.6}, {"texto": "Ajusta parâmetros", "em": 16.9}]},
61
+ {"id": "lista", "tipo": "lista_check", "titulo": "Tudo isso é *IA*", "marca": "check", "depois_de": 31, "sai": 35,
62
+ "itens": [{"texto": "Assistentes", "em": 31.05}, {"texto": "Carros sozinhos", "em": 32.9}]}],
63
+ "broll": [{"busca": "self-driving car", "de": 26.1, "ate": 28.4, "rotulo": "Carro *autônomo*"}]}
64
+ ```
65
+
66
+ - `gancho`: até 4 palavras fortes da primeira frase; `*palavra*` sai em
67
+ destaque. `false` desliga.
68
+ - `pecas`: as do catálogo. `em`, `sai`, `depois_de` em segundos do corte.
69
+ - `broll`: imagem livre (Wikimedia, CC0) buscada pelo que a pessoa DISSE
70
+ naquele instante (busca em inglês acha mais), 1,5 a 3 s, com `rotulo`
71
+ curto. `"modo": "tela_verde"` recorta a pessoa na frente da imagem.
72
+ - Opcionais: `estilo` (`laranja` padrão, `papelaria`, `noite`), `legenda`
73
+ (`padrao` | `discreta`), `correcoes` (`{"palavra errada": "certa"}` quando a
74
+ transcrição errou um nome), `blocos` (enquadramento à mão), `trilha_abaixo`
75
+ (dB da trilha abaixo da voz, 20), `sons: false`, `camera_viva: false`.
76
+ - `trilha`: `"auto"` (busca uma faixa com energia), o caminho de um áudio da
77
+ pessoa, ou `"nenhuma"`.
78
+
79
+ ## Que peça para cada fala
80
+
81
+ | a fala | a peça |
82
+ |---|---|
83
+ | uma expressão que muda o sentido | `lettering` (`principal`, `apoio`) |
84
+ | enumera ("espaço, estoque, frete") | `lista_check` (`marca` check ou x), cada item na sua palavra, até 22 letras |
85
+ | etapas em ordem | `passo_a_passo` |
86
+ | um número que importa | `contador` (`valor_final`, `sufixo`, `rotulo`) |
87
+ | nome de uma coisa a fixar | `rotulo` (em cima da cabeça) |
88
+ | afirmação curta que fecha a ideia | `carimbo` (1 ou 2 palavras) |
89
+ | cita algo concreto que se pode mostrar | `broll` |
90
+
91
+ Peça grande (lista, passos) ganha moldura sozinha. Lettering com mais de 6
92
+ palavras é erro. Campos de cada peça: `<editor>/references/animacao-catalogo.md`;
93
+ enquadramento: `references/enquadramento.md`; sons e trilha:
94
+ `references/som-e-trilha.md` (`<editor>` é a pasta que o `preparar` devolve).
95
+
96
+ ## O som
97
+
98
+ O motor mede a voz de cada trecho e acerta o ganho antes do volume final:
99
+ gravação baixa (-38 LUFS) sai em -14 LUFS, pico até -1 dBTP, 48 kHz, sem o
100
+ modo dinâmico que bombeia a voz. A limpeza de ruído é leve por padrão
101
+ (`limpar: "forte"` no cortar, `false` desliga). O portão reprova volume fora,
102
+ modo dinâmico, amostra clipada, respiro, quadro preto e texto fora da zona
103
+ segura do Reels.
104
+
105
+ ## Quando algo dá errado
106
+
107
+ - Erro de um passo: a mensagem diz qual e por quê (em português). Corrija a
108
+ entrada e tente de novo; não repita o mesmo pedido esperando outro
109
+ resultado.
110
+ - Palavra errada na legenda: `correcoes` no plano, ou `prompt` no cortar.
111
+ - A mesma correção falhou duas vezes: pare e conte à pessoa o que testou.
112
+ - Operação solta (só cortar um trecho, juntar, exportar): é o editor por
113
+ linha do tempo (`primocode video help`), não este.