primocode 10.12.0 → 10.14.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 (207) hide show
  1. package/README.md +74 -12
  2. package/bin/primocode.js +148 -8
  3. package/lib/act.js +48 -1
  4. package/lib/catalogo.js +12 -1
  5. package/lib/config.js +1 -0
  6. package/lib/estudio/assinaturas/gerativo.json +1 -1
  7. package/lib/estudio/camadas.js +4 -0
  8. package/lib/estudio/cota.js +150 -0
  9. package/lib/estudio/dados/projetos/clube-ad5.json +105 -0
  10. package/lib/estudio/dados/projetos/clube-ae-sem-memoria.json +107 -0
  11. package/lib/estudio/dados/projetos/clube-agent.json +120 -0
  12. package/lib/estudio/dados/projetos/clube-antes-e-agora.json +86 -0
  13. package/lib/estudio/dados/projetos/clube-apresentando-clube.json +110 -0
  14. package/lib/estudio/dados/projetos/clube-bluevisa.json +106 -0
  15. package/lib/estudio/dados/projetos/clube-caught-in-the-fire.json +127 -0
  16. package/lib/estudio/dados/projetos/clube-chat-cliente-satisfeito.json +98 -0
  17. package/lib/estudio/dados/projetos/clube-chatgpt-anuncios.json +120 -0
  18. package/lib/estudio/dados/projetos/clube-claude-dj.json +140 -0
  19. package/lib/estudio/dados/projetos/clube-claude-mcp.json +142 -0
  20. package/lib/estudio/dados/projetos/clube-cleanui.json +106 -0
  21. package/lib/estudio/dados/projetos/clube-clinica-take-unico.json +128 -0
  22. package/lib/estudio/dados/projetos/clube-clinica.json +104 -0
  23. package/lib/estudio/dados/projetos/clube-clube-opener.json +125 -0
  24. package/lib/estudio/dados/projetos/clube-clube.json +108 -0
  25. package/lib/estudio/dados/projetos/clube-codigo.json +101 -0
  26. package/lib/estudio/dados/projetos/clube-distilbook.json +104 -0
  27. package/lib/estudio/dados/projetos/clube-drive-upload.json +115 -0
  28. package/lib/estudio/dados/projetos/clube-dub.json +119 -0
  29. package/lib/estudio/dados/projetos/clube-enxovaly.json +105 -0
  30. package/lib/estudio/dados/projetos/clube-fecha7.json +118 -0
  31. package/lib/estudio/dados/projetos/clube-filatov.json +128 -0
  32. package/lib/estudio/dados/projetos/clube-frame.json +108 -0
  33. package/lib/estudio/dados/projetos/clube-glass.json +118 -0
  34. package/lib/estudio/dados/projetos/clube-letra-em-apps.json +84 -0
  35. package/lib/estudio/dados/projetos/clube-letra-globo-3d.json +111 -0
  36. package/lib/estudio/dados/projetos/clube-letra-nos-apps.json +146 -0
  37. package/lib/estudio/dados/projetos/clube-letra-seis-mundos.json +100 -0
  38. package/lib/estudio/dados/projetos/clube-lula-3.json +146 -0
  39. package/lib/estudio/dados/projetos/clube-morph.json +119 -0
  40. package/lib/estudio/dados/projetos/clube-motion-designer.json +115 -0
  41. package/lib/estudio/dados/projetos/clube-motion-pacote.json +112 -0
  42. package/lib/estudio/dados/projetos/clube-movimento.json +103 -0
  43. package/lib/estudio/dados/projetos/clube-not-like-us.json +117 -0
  44. package/lib/estudio/dados/projetos/clube-pinterest-inspiracao.json +86 -0
  45. package/lib/estudio/dados/projetos/clube-pixel-e-tinta.json +108 -0
  46. package/lib/estudio/dados/projetos/clube-ponte.json +118 -0
  47. package/lib/estudio/dados/projetos/clube-processo-edit-tonal.json +126 -0
  48. package/lib/estudio/dados/projetos/clube-processo-motion-100-ia.json +109 -0
  49. package/lib/estudio/dados/projetos/clube-promo.json +114 -0
  50. package/lib/estudio/dados/projetos/clube-proposito.json +115 -0
  51. package/lib/estudio/dados/projetos/clube-rankhog.json +128 -0
  52. package/lib/estudio/dados/projetos/clube-recursao.json +116 -0
  53. package/lib/estudio/dados/projetos/clube-revelar.json +118 -0
  54. package/lib/estudio/dados/projetos/clube-robo.json +125 -0
  55. package/lib/estudio/dados/projetos/clube-shelby-ep2.json +144 -0
  56. package/lib/estudio/dados/projetos/clube-shelby.json +97 -0
  57. package/lib/estudio/dados/projetos/clube-solar-eclipse.json +96 -0
  58. package/lib/estudio/dados/projetos/clube-soldier.json +128 -0
  59. package/lib/estudio/dados/projetos/clube-som-de-cena.json +108 -0
  60. package/lib/estudio/dados/projetos/clube-spotify-candidatura.json +112 -0
  61. package/lib/estudio/dados/projetos/clube-tda-app.json +118 -0
  62. package/lib/estudio/dados/projetos/clube-tda-ia.json +75 -0
  63. package/lib/estudio/dados/projetos/clube-telegram-chat.json +118 -0
  64. package/lib/estudio/dados/projetos/clube-telegram-ilha.json +106 -0
  65. package/lib/estudio/dados/projetos/clube-tennessee-nike.json +132 -0
  66. package/lib/estudio/dados/projetos/clube-tudo-se-move.json +112 -0
  67. package/lib/estudio/dados/projetos/clube-uiad.json +107 -0
  68. package/lib/estudio/dados/projetos/clube-viralgrow.json +108 -0
  69. package/lib/estudio/dados/projetos/clube-whose-side.json +140 -0
  70. package/lib/estudio/dados/projetos/clube-youtube-inscrever.json +108 -0
  71. package/lib/estudio/dados/projetos/elementos-do-site-em-movimento.json +49 -0
  72. package/lib/estudio/dados/projetos/explainer-narrado.json +51 -0
  73. package/lib/estudio/dados/projetos/lancamento-de-produto.json +51 -0
  74. package/lib/estudio/dados/projetos/motion-so-com-sons.json +50 -0
  75. package/lib/estudio/dados/projetos/titulo-em-after-effects.json +51 -0
  76. package/lib/estudio/dados/projetos/tour-de-saas.json +50 -0
  77. package/lib/estudio/dados/projetos-ae/antes-e-agora/ae/cenas.jsx +733 -0
  78. package/lib/estudio/dados/projetos-ae/antes-e-agora/assets/fazer_assets.py +88 -0
  79. package/lib/estudio/dados/projetos-ae/letra-em-apps/ae/cenas.jsx +402 -0
  80. package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/preparar_assets.py +17 -0
  81. package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/svg_para_ae.py +25 -0
  82. package/lib/estudio/dados/projetos-ae/letra-nos-apps/BRIEF-AGENTES.md +31 -0
  83. package/lib/estudio/dados/projetos-ae/letra-nos-apps/README.md +53 -0
  84. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ROTEIRO-CENAS.md +69 -0
  85. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/A-agenda.jsx +148 -0
  86. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/B-docs.jsx +174 -0
  87. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/C-alttab.jsx +113 -0
  88. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/D-spotify.jsx +118 -0
  89. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/E-notas.jsx +146 -0
  90. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/F-jornal1.jsx +144 -0
  91. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/G-google.jsx +153 -0
  92. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/H-mapa.jsx +176 -0
  93. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/I-imagens.jsx +132 -0
  94. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/J-jornal2.jsx +191 -0
  95. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/K-maps.jsx +184 -0
  96. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera-movimentos.jsx +48 -0
  97. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera.jsx +57 -0
  98. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/card-spotify.jsx +98 -0
  99. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/digitar.jsx +38 -0
  100. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/jornal-comum.jsx +196 -0
  101. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra.jsx +23 -0
  102. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra_automatica.py +83 -0
  103. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/montagem.jsx +74 -0
  104. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/render_html.py +13 -0
  105. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/util.jsx +162 -0
  106. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-midia-comum.jsx +114 -0
  107. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/wg-util.jsx +360 -0
  108. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/F-jornal1/render_html.py +13 -0
  109. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/render_w-midia.py +26 -0
  110. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/inclin.py +30 -0
  111. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_d.py +36 -0
  112. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_e.py +27 -0
  113. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ajustar_movimentos.py +61 -0
  114. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ancorar.py +74 -0
  115. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/assar.py +47 -0
  116. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/camera3d_jornal.py +172 -0
  117. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/comparar.py +32 -0
  118. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/dados_ae_jornal.py +46 -0
  119. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/folha_contato.py +28 -0
  120. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/homografia_jornal.py +74 -0
  121. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/integrar.py +44 -0
  122. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/medir_quadro_a_quadro.py +32 -0
  123. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/mesa_2d.py +64 -0
  124. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/plot_cenas.py +11 -0
  125. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/retificar_quadros.py +38 -0
  126. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/assar_ab.py +52 -0
  127. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/medir_ab.py +59 -0
  128. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ajustar_h.py +111 -0
  129. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_corrige_cam.py +17 -0
  130. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ecc_cadeia.py +80 -0
  131. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_finalizar_cam.py +65 -0
  132. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_mosaico.py +61 -0
  133. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/01-gramatica-e-roteiro.md +55 -0
  134. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/02-camera.md +66 -0
  135. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/03-montagem-no-ae.md +50 -0
  136. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/04-render-e-conferencia.md +43 -0
  137. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/05-abertura-celular-mergulho.md +43 -0
  138. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces.md +42 -0
  139. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/atraso_imagem.py +31 -0
  140. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/fundo.py +56 -0
  141. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/juntar.py +45 -0
  142. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/montar.py +177 -0
  143. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/rastreio.py +74 -0
  144. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/sincronia.py +18 -0
  145. package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/ae/cenas.jsx +530 -0
  146. package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/assets/fazer_assets.py +107 -0
  147. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/assets/pessoas/figurinha.py +46 -0
  148. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/BRIEF-A.md +83 -0
  149. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/audio.py +179 -0
  150. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/confere.py +20 -0
  151. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte1.js +1 -0
  152. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte2.js +1 -0
  153. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/gerar_emojis.py +11 -0
  154. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/img/filmes/_indice.js +122 -0
  155. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/base.js +380 -0
  156. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/main.js +86 -0
  157. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1a.js +169 -0
  158. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1b.js +455 -0
  159. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1c.js +389 -0
  160. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p2.js +379 -0
  161. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/video.py +62 -0
  162. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/README.md +65 -0
  163. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/oraculo_take.py +137 -0
  164. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/render_take.py +388 -0
  165. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/take.js +235 -0
  166. package/lib/estudio/diretor.js +2 -1
  167. package/lib/estudio/editor.js +285 -0
  168. package/lib/estudio/efeitos-fabrica/terminal-digitando.jsx +2 -1
  169. package/lib/estudio/effectcraft.js +152 -0
  170. package/lib/estudio/exportar.js +181 -0
  171. package/lib/estudio/gerativo.js +44 -15
  172. package/lib/estudio/index.js +126 -5
  173. package/lib/estudio/kit/gerativo.jsx +15 -0
  174. package/lib/estudio/kit-ae/base.jsx +1146 -0
  175. package/lib/estudio/kit-ae/cenas.jsx +395 -0
  176. package/lib/estudio/kit-ae/primitivas.jsx +911 -0
  177. package/lib/estudio/kokoro.js +3 -1
  178. package/lib/estudio/maquina-diretor.js +393 -0
  179. package/lib/estudio/maquina-elementos.js +134 -0
  180. package/lib/estudio/maquina-layout.js +198 -0
  181. package/lib/estudio/maquina-som.js +478 -0
  182. package/lib/estudio/maquina-trilha.js +160 -0
  183. package/lib/estudio/maquina-voz.js +429 -0
  184. package/lib/estudio/maquina.js +516 -0
  185. package/lib/estudio/projetos.js +60 -0
  186. package/lib/estudio/remotion.js +6 -4
  187. package/lib/estudio/revisor.js +61 -0
  188. package/lib/estudio/voicestudio.js +81 -0
  189. package/lib/headless.js +71 -0
  190. package/lib/hooks.js +72 -0
  191. package/lib/local/cerebro.json +398 -12
  192. package/lib/local/entrada.js +46 -2
  193. package/lib/marca-kit.js +309 -0
  194. package/lib/mcp.js +286 -6
  195. package/lib/perigo.js +31 -3
  196. package/lib/planos.js +47 -19
  197. package/lib/preparar.js +111 -0
  198. package/lib/retomar.js +55 -0
  199. package/lib/sandbox.js +197 -0
  200. package/lib/skills.js +3 -1
  201. package/lib/tools.js +13 -4
  202. package/lib/ui.js +1 -1
  203. package/lib/video/auto.js +18 -3
  204. package/package.json +4 -2
  205. package/skills/after-effects/SKILL.md +52 -0
  206. package/skills/maquina-de-motion/SKILL.md +84 -0
  207. package/skills/motion-de-primeira/SKILL.md +33 -51
@@ -0,0 +1,111 @@
1
+ /**
2
+ * preparar.js — deixa o PrimoCode pronto logo depois do `npm i -g primocode`, e mostra o que falta.
3
+ *
4
+ * node lib/preparar.js → postinstall: dispara o download do EffectCraft em segundo plano. NUNCA falha a instalação.
5
+ * primocode preparar → baixa o EffectCraft agora e mostra o resultado.
6
+ * primocode doutor → o diagnóstico de sempre + o que o Estúdio precisa (estudioLinhas).
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+ const path = require('path');
14
+ const { spawn, spawnSync } = require('child_process');
15
+
16
+ const sem = () => process.env.PRIMOCODE_SEM_POSINSTALL === '1' || process.env.CI === 'true';
17
+
18
+ /** Roda no postinstall: rápido, sem rede no processo principal, sem erro. */
19
+ function posinstalar() {
20
+ try {
21
+ if (sem()) return;
22
+ const ec = require('./estudio/effectcraft.js');
23
+ if (!ec.asset()) return;
24
+ const filho = spawn(process.execPath, [__filename, '--fundo'], { detached: true, stdio: 'ignore', windowsHide: true });
25
+ filho.on('error', () => {});
26
+ filho.unref();
27
+ console.log('PrimoCode instalado. Baixando o motor After Effects (EffectCraft) em segundo plano; rode `primocode doutor` para conferir o que falta.');
28
+ } catch { /* instalação nunca falha por causa disto */ }
29
+ }
30
+
31
+ async function baixarEffectCraft() {
32
+ const ec = require('./estudio/effectcraft.js');
33
+ try { return await Promise.race([ec.garantir(), new Promise((r) => setTimeout(() => r({ ok: false, error: 'demorou demais (10 min)' }), 600000))]); }
34
+ catch (e) { return { ok: false, error: e.message }; }
35
+ }
36
+
37
+ function achar(nome) {
38
+ try { const r = spawnSync(process.platform === 'win32' ? 'where' : 'which', [nome], { encoding: 'utf8', timeout: 5000 }); return r.status === 0 ? r.stdout.split('\n')[0].trim() : null; } catch { return null; }
39
+ }
40
+
41
+ /** As linhas do `doutor` que dizem respeito ao Estúdio e aos recursos novos. Cada ✗ traz como resolver. */
42
+ function estudioLinhas(dep = {}) {
43
+ const linhas = [];
44
+ const poe = (ok, item, detalhe, conserto) => linhas.push({ ok, item, detalhe, conserto: ok === false ? conserto : undefined });
45
+ const tenta = (fn, padrao) => { try { return fn(); } catch { return padrao; } };
46
+
47
+ // EffectCraft (motor After Effects)
48
+ const ec = tenta(() => require('./estudio/effectcraft.js'));
49
+ if (ec) {
50
+ const bin = process.env.PRIMOCODE_EFFECTCRAFT_BIN || path.join(os.homedir(), '.primocode', 'bin', 'effectcraft-' + ec.VERSAO);
51
+ const tem = fs.existsSync(bin) && (fs.statSync(bin).isFile() || fs.readdirSync(bin).length > 0);
52
+ poe(ec.asset() ? tem : null, 'EffectCraft', tem ? `v${ec.VERSAO} instalado` : (ec.asset() ? 'ainda não baixado' : 'sem binário para este sistema'),
53
+ 'rode: primocode preparar (baixa uns 100 MB; precisa de internet)');
54
+ }
55
+ // ffmpeg / ffprobe
56
+ const ff = tenta(() => require('./estudio/video.js').ffmpeg(), null) || achar('ffmpeg');
57
+ poe(!!ff, 'ffmpeg', ff || 'não encontrado', { darwin: 'brew install ffmpeg', linux: 'sudo apt install ffmpeg', win32: 'winget install Gyan.FFmpeg' }[process.platform] || 'instale o ffmpeg');
58
+ // whisper (tempo de cada palavra na legenda)
59
+ const wbin = process.env.PRIMOCODE_WHISPER_BIN || '/opt/homebrew/bin/whisper-cli';
60
+ const w = fs.existsSync(wbin) ? wbin : achar('whisper-cli');
61
+ poe(w ? true : null, 'whisper', w || 'não instalado — a legenda usa o tempo estimado, sem medir cada palavra', undefined);
62
+ if (!w) linhas[linhas.length - 1].conserto = 'opcional: brew install whisper-cpp';
63
+ // fontes (cache do Google Fonts)
64
+ const cache = tenta(() => require('./estudio/glifos.js').CACHE, null);
65
+ const nFontes = cache && fs.existsSync(cache) ? fs.readdirSync(cache).filter((f) => /\.(ttf|otf)$/i.test(f)).length : 0;
66
+ poe(null, 'Fontes', nFontes ? `${nFontes} em cache` : 'baixam sozinhas no primeiro vídeo (precisa de internet)');
67
+ // sandbox
68
+ const sb = tenta(() => require('./sandbox.js').estado(), null);
69
+ if (sb) poe(null, 'Sandbox', sb.ativo ? `ligado (${sb.mecanismo})` : (sb.mecanismo ? `desligado — ${sb.mecanismo} disponível; /sandbox on liga` : 'sem mecanismo neste sistema (comandos rodam sem isolamento)'));
70
+ // marca ativa
71
+ const mk = tenta(() => require('./marca-kit.js').ativa([process.cwd()]), null);
72
+ poe(null, 'Marca ativa', mk ? (mk.nome || mk.slug || 'sim') : 'nenhuma — primocode marca criar <nome>');
73
+ // MCP
74
+ const mcp = tenta(() => Object.keys(require('./mcp.js').servidores()), []);
75
+ poe(null, 'MCP', mcp.length ? mcp.join(', ') : 'nenhum servidor');
76
+ // plano
77
+ const eu = tenta(() => require('./conta.js').atual(), null);
78
+ poe(eu ? true : false, 'Plano', eu ? `${eu.plano}${eu.pro ? ' (pago)' : ''} · ${eu.email || ''}` : 'sem login', 'entre com /login (ou `primocode`, no primeiro uso)');
79
+ return linhas;
80
+ }
81
+
82
+ function imprimir(linhas, log = console.log) {
83
+ for (const l of linhas) {
84
+ log(` ${l.ok === true ? '✓' : l.ok === false ? '✗' : '·'} ${String(l.item).padEnd(18)}${l.detalhe || ''}`);
85
+ if (l.conserto) log(` → ${l.conserto}`);
86
+ }
87
+ const ruins = linhas.filter((l) => l.ok === false).length;
88
+ log(ruins ? `\n ${ruins} ${ruins === 1 ? 'coisa para arrumar' : 'coisas para arrumar'}.` : '\n Tudo certo.');
89
+ return ruins;
90
+ }
91
+
92
+ async function main(args = []) {
93
+ const cmd = args[0];
94
+ if (cmd === 'preparar') {
95
+ console.log('Baixando o EffectCraft…');
96
+ const r = await baixarEffectCraft();
97
+ console.log(r.ok ? '✓ EffectCraft pronto: ' + r.bin : '✗ ' + r.error);
98
+ return r.ok ? 0 : 1;
99
+ }
100
+ const extra = require('./comandos-extra.js'), cfg = require('./config.js');
101
+ console.log('');
102
+ const base = await extra.diagnosticar({ raiz: cfg.ROOT_DIR, arquivoConfig: cfg.CONFIG_FILE, acharChrome: () => require('./browser').findChrome(), prazoServidor: 8000 });
103
+ return imprimir(base.concat(estudioLinhas())) ? 1 : 0;
104
+ }
105
+
106
+ module.exports = { posinstalar, baixarEffectCraft, estudioLinhas, imprimir, main };
107
+
108
+ if (require.main === module) {
109
+ if (process.argv[2] === '--fundo') baixarEffectCraft().then(() => process.exit(0), () => process.exit(0));
110
+ else posinstalar();
111
+ }
package/lib/retomar.js ADDED
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+ // --resume [id] e --worktree <nome>: flags puras + git worktree. Sem tela, testável.
3
+ const { spawnSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ /* Tira --resume/--worktree do argv. `--resume` sem id (ou seguido de outra flag/pedido
8
+ entre aspas não é adivinhado): só consome o próximo se parecer um id (sem espaço, sem '-'). */
9
+ function analisar(argv) {
10
+ const out = { resume: null, worktree: null, erro: null, args: [] };
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const a = argv[i];
13
+ if (a === '--resume' || a.startsWith('--resume=')) {
14
+ let id = a.includes('=') ? a.slice(a.indexOf('=') + 1) : '';
15
+ if (!id && argv[i + 1] && /^[\w.:-]+$/.test(argv[i + 1]) && !argv[i + 1].startsWith('-')
16
+ && /\d/.test(argv[i + 1])) id = argv[++i];
17
+ out.resume = id || true;
18
+ } else if (a === '--worktree' || a.startsWith('--worktree=')) {
19
+ let n = a.includes('=') ? a.slice(a.indexOf('=') + 1) : argv[i + 1];
20
+ if (!a.includes('=')) i++;
21
+ if (!n || n.startsWith('-')) { out.erro = 'Uso: primocode --worktree <nome>'; if (n) i--; } else out.worktree = n;
22
+ } else out.args.push(a);
23
+ }
24
+ return out;
25
+ }
26
+
27
+ function nomeValido(n) { return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(n || ''); }
28
+
29
+ function git(cwd, args) {
30
+ const r = spawnSync('git', args, { cwd, encoding: 'utf-8' });
31
+ return { ok: r.status === 0, out: (r.stdout || '').trim(), err: (r.stderr || '').trim() };
32
+ }
33
+
34
+ /* Cria ../<repo>-<nome> numa branch nova <nome>. Recusa fora de repo git. */
35
+ function criarWorktree(nome, cwd) {
36
+ if (!nomeValido(nome)) return { ok: false, error: 'Nome inválido: use letras, números, ponto, hífen ou _.' };
37
+ const topo = git(cwd, ['rev-parse', '--show-toplevel']);
38
+ if (!topo.ok || !topo.out) return { ok: false, error: 'Worktree só funciona dentro de um repositório git.' };
39
+ const raiz = topo.out;
40
+ const destino = path.join(path.dirname(raiz), `${path.basename(raiz)}-${nome}`);
41
+ if (fs.existsSync(destino)) return { ok: false, error: `A pasta ${destino} já existe.` };
42
+ const r = git(raiz, ['worktree', 'add', '-b', nome, destino]);
43
+ if (!r.ok) return { ok: false, error: `git worktree falhou: ${r.err.split('\n')[0]}` };
44
+ return { ok: true, path: fs.realpathSync(destino), branch: nome };
45
+ }
46
+
47
+ /* Escolhe uma conversa da lista por número (1..n) ou por id (inteiro ou prefixo). */
48
+ function escolher(conversas, ref) {
49
+ const r = String(ref || '').trim();
50
+ if (!r) return null;
51
+ if (/^\d{1,3}$/.test(r) && conversas[Number(r) - 1]) return conversas[Number(r) - 1];
52
+ return conversas.find((x) => String(x.id) === r) || conversas.find((x) => String(x.id).startsWith(r)) || null;
53
+ }
54
+
55
+ module.exports = { analisar, criarWorktree, escolher, nomeValido };
package/lib/sandbox.js ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * sandbox.js — a cerca em volta do run_shell.
3
+ *
4
+ * Decisão do Joel (08/10, JOEL-FEAT-002): isolamento NATIVO, leve, sem VM.
5
+ * macOS → sandbox-exec (Seatbelt) com um perfil gerado na hora
6
+ * Linux → bubblewrap (bwrap), se existir
7
+ * Windows/sem mecanismo → best_effort (avisa uma vez e segue) ou
8
+ * hard_requirement (recusa)
9
+ *
10
+ * O que o comando pode: escrever na pasta do projeto, em /tmp e em
11
+ * ~/.primocode/cache; ler quase tudo, MENOS ~/.ssh, ~/.aws, ~/.config/gcloud,
12
+ * ~/.npmrc e .env*. Rede ligada (o `npm install` precisa), com opção de cortar.
13
+ * Quando bloqueia, a mensagem diz POR QUE, para o modelo aprender.
14
+ *
15
+ * Config em ~/.primocode/config.json:
16
+ * "sandbox": { "ligado": true, "modo": "best_effort"|"hard_requirement",
17
+ * "rede": true, "env_permitidas": ["MINHA_KEY"],
18
+ * "escrita_extra": ["~/.npm"] }
19
+ */
20
+ 'use strict';
21
+
22
+ const fs = require('fs');
23
+ const os = require('os');
24
+ const path = require('path');
25
+ const { execFile, spawnSync } = require('child_process');
26
+
27
+ const MODOS = ['best_effort', 'hard_requirement'];
28
+ const LEITURA_NEGADA_HOME = ['.ssh', '.aws', '.config/gcloud', '.npmrc'];
29
+ const SECRETA = /(_KEY|_TOKEN|SECRET)/i;
30
+
31
+ let _avisou = false;
32
+ let _forcaMecanismo; // teste: undefined = detectar; null = ausente; 'seatbelt'|'bwrap'
33
+
34
+ function arquivoConfig(home) { return path.join(home || os.homedir(), '.primocode', 'config.json'); }
35
+
36
+ function lerConfig(home) {
37
+ try { return JSON.parse(fs.readFileSync(arquivoConfig(home), 'utf8')) || {}; } catch { return {}; }
38
+ }
39
+
40
+ function config(home) {
41
+ const s = lerConfig(home).sandbox || {};
42
+ return {
43
+ ligado: typeof s.ligado === 'boolean' ? s.ligado : process.platform === 'darwin',
44
+ modo: MODOS.includes(s.modo) ? s.modo : 'best_effort',
45
+ rede: s.rede !== false,
46
+ env_permitidas: Array.isArray(s.env_permitidas) ? s.env_permitidas.map(String) : [],
47
+ escrita_extra: Array.isArray(s.escrita_extra) ? s.escrita_extra.map(String) : [],
48
+ };
49
+ }
50
+
51
+ function salvar(parcial, home) {
52
+ const arq = arquivoConfig(home);
53
+ const atual = lerConfig(home);
54
+ atual.sandbox = { ...(atual.sandbox || {}), ...parcial };
55
+ fs.mkdirSync(path.dirname(arq), { recursive: true });
56
+ fs.writeFileSync(arq, JSON.stringify(atual, null, 2));
57
+ return config(home);
58
+ }
59
+
60
+ function temBinario(nome) {
61
+ try { return spawnSync(nome, ['--version'], { stdio: 'ignore', timeout: 3000 }).error === undefined; } catch { return false; }
62
+ }
63
+
64
+ function mecanismo() {
65
+ if (_forcaMecanismo !== undefined) return _forcaMecanismo;
66
+ if (process.platform === 'darwin') return fs.existsSync('/usr/bin/sandbox-exec') ? 'seatbelt' : null;
67
+ if (process.platform === 'linux') return temBinario('bwrap') ? 'bwrap' : null;
68
+ return null;
69
+ }
70
+
71
+ function real(p) { try { return fs.realpathSync(p); } catch { return path.resolve(p); } }
72
+ function expandir(p, home) { return p.startsWith('~') ? path.join(home, p.slice(1)) : p; }
73
+ const sbq = (s) => '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
74
+
75
+ function caminhos(projectDir, cfg, home) {
76
+ const escrita = [real(projectDir), '/tmp', real('/tmp'), real(os.tmpdir()), path.join(home, '.primocode', 'cache'),
77
+ ...cfg.escrita_extra.map((p) => expandir(p, home))];
78
+ try { fs.mkdirSync(path.join(home, '.primocode', 'cache'), { recursive: true }); } catch { /* sem cache, sem drama */ }
79
+ const negadosLeitura = LEITURA_NEGADA_HOME.map((p) => path.join(home, p));
80
+ return { escrita: [...new Set(escrita.map((p) => real(p)))], negadosLeitura };
81
+ }
82
+
83
+ /** Perfil Seatbelt. Em Seatbelt a ÚLTIMA regra que casa vence: negações no fim. */
84
+ function perfilSeatbelt(projectDir, cfg = config(), home = os.homedir()) {
85
+ const { escrita, negadosLeitura } = caminhos(projectDir, cfg, home);
86
+ const l = ['(version 1)', '(allow default)', '(deny file-write*)'];
87
+ for (const p of escrita) l.push(`(allow file-write* (subpath ${sbq(p)}))`);
88
+ l.push('(allow file-write* (literal "/dev/null") (literal "/dev/tty") (literal "/dev/dtracehelper") (regex #"^/dev/fd/"))');
89
+ for (const p of negadosLeitura) l.push(`(deny file-read* (subpath ${sbq(real(p))}) (subpath ${sbq(p)}))`);
90
+ l.push('(deny file-read* (regex #"/\\.env($|\\.[^/]*$)"))');
91
+ if (!cfg.rede) l.push('(deny network*)');
92
+ return l.join('\n');
93
+ }
94
+
95
+ function argsBwrap(projectDir, cfg = config(), home = os.homedir()) {
96
+ const { escrita, negadosLeitura } = caminhos(projectDir, cfg, home);
97
+ const a = ['--ro-bind', '/', '/', '--dev', '/dev', '--proc', '/proc'];
98
+ for (const p of escrita) if (fs.existsSync(p)) a.push('--bind', p, p);
99
+ for (const p of negadosLeitura) {
100
+ if (!fs.existsSync(p)) continue;
101
+ if (fs.statSync(p).isDirectory()) a.push('--tmpfs', p); else a.push('--ro-bind', '/dev/null', p);
102
+ }
103
+ // .env* na pasta do projeto (um nível; o resto o Seatbelt cobre por regex)
104
+ try {
105
+ for (const f of fs.readdirSync(projectDir)) {
106
+ if (/^\.env($|\.)/.test(f)) a.push('--ro-bind', '/dev/null', path.join(real(projectDir), f));
107
+ }
108
+ } catch { /* pasta sumiu */ }
109
+ if (!cfg.rede) a.push('--unshare-net');
110
+ a.push('--die-with-parent', '--chdir', real(projectDir));
111
+ return a;
112
+ }
113
+
114
+ /** Decide como envolver o comando. Devolve { cmd, args } ou { recusado } ou { semCerca }. */
115
+ function envolver(command, projectDir, opts = {}) {
116
+ const home = opts.home || os.homedir();
117
+ const cfg = opts.cfg || config(home);
118
+ if (!cfg.ligado) return { semCerca: true, motivo: 'desligado' };
119
+ const mec = mecanismo();
120
+ if (!mec) {
121
+ if (cfg.modo === 'hard_requirement') {
122
+ return { recusado: 'o sandbox está em hard_requirement e este computador não tem como isolar (macOS: sandbox-exec; Linux: bwrap). Comando não rodou. Instale o bwrap ou use /sandbox off.' };
123
+ }
124
+ let aviso = null;
125
+ if (!_avisou) { _avisou = true; aviso = 'aviso: sem sandbox neste computador (modo best_effort); o comando roda sem isolamento.'; }
126
+ return { semCerca: true, motivo: 'sem_mecanismo', aviso };
127
+ }
128
+ if (mec === 'seatbelt') return { cmd: '/usr/bin/sandbox-exec', args: ['-p', perfilSeatbelt(projectDir, cfg, home), '/bin/sh', '-c', command], mec, cfg, home };
129
+ return { cmd: 'bwrap', args: [...argsBwrap(projectDir, cfg, home), '/bin/sh', '-c', command], mec, cfg, home };
130
+ }
131
+
132
+ /** Se o comando falhou por causa da cerca, diz por quê (e qual caminho). */
133
+ function explicar(stderr, projectDir, cfg, home) {
134
+ const txt = String(stderr || '');
135
+ const m = /(?:^|\n)(?:[^\n:]*:\s*)?(\/[^\n:]+?|\.{1,2}\/[^\n:]+?|[^\s:'"]+?):?\s*(?:Operation not permitted|Permission denied|Read-only file system)/.exec(txt)
136
+ || /(?:Operation not permitted|Permission denied|Read-only file system)[^\n]*?['"]([^'"\n]+)['"]/.exec(txt);
137
+ if (!m) return null;
138
+ const alvo = path.resolve(projectDir, m[1].trim());
139
+ const { negadosLeitura } = caminhos(projectDir, cfg, home);
140
+ const secreto = negadosLeitura.some((p) => alvo === p || alvo.startsWith(p + path.sep) || alvo.startsWith(real(p) + path.sep))
141
+ || /(^|\/)\.env($|\.)/.test(alvo);
142
+ if (secreto) return `o sandbox bloqueou leitura de segredo: ${alvo} (~/.ssh, ~/.aws, ~/.config/gcloud, ~/.npmrc e .env* ficam fora do alcance). Não tente ler isso; peça ao usuário o que precisa.`;
143
+ if (/Read-only|Operation not permitted|Permission denied/.test(txt)) {
144
+ return `o sandbox bloqueou escrita fora do projeto: ${alvo}. Escreva só dentro de ${projectDir}, em /tmp ou em ~/.primocode/cache.`;
145
+ }
146
+ return null;
147
+ }
148
+
149
+ function filtrarEnv(env, permitidas) {
150
+ const out = {};
151
+ for (const [k, v] of Object.entries(env)) {
152
+ if (v === undefined) continue;
153
+ if (SECRETA.test(k) && !permitidas.includes(k)) continue;
154
+ out[k] = v;
155
+ }
156
+ return out;
157
+ }
158
+
159
+ /** Roda o comando dentro da cerca. Mesmo formato do runShell: { ok, code, stdout, stderr }. */
160
+ function executar(command, { projectDir, timeout = 60000, env = process.env, home } = {}) {
161
+ const cwd = projectDir || process.cwd();
162
+ const cfg = config(home);
163
+ const env2 = filtrarEnv(env, cfg.env_permitidas);
164
+ // `home` só escolhe de onde lê a config; as pastas protegidas são sempre as do ~ de verdade.
165
+ const e = envolver(command, cwd, { cfg });
166
+ return new Promise((resolve) => {
167
+ if (e.recusado) return resolve({ ok: false, code: 126, stdout: '', stderr: e.recusado, sandbox: { recusado: true } });
168
+ const termina = (error, stdout, stderr) => {
169
+ let err = stderr || '';
170
+ const info = { mecanismo: e.mec || null };
171
+ if (e.aviso) err = e.aviso + '\n' + err;
172
+ if (error && e.mec) {
173
+ const why = explicar(err, cwd, cfg, e.home);
174
+ if (why) { err = err + '\n[sandbox] ' + why; info.bloqueou = why; }
175
+ }
176
+ const r = error ? { ok: false, code: error.code ?? 1, stdout, stderr: err, killed: error.killed } : { ok: true, code: 0, stdout, stderr: err };
177
+ r.sandbox = info;
178
+ resolve(r);
179
+ };
180
+ const opt = { timeout, cwd, maxBuffer: 10 * 1024 * 1024, env: env2 };
181
+ if (e.semCerca) return require('child_process').exec(command, opt, termina);
182
+ execFile(e.cmd, e.args, opt, termina);
183
+ });
184
+ }
185
+
186
+ function estado(home) {
187
+ const cfg = config(home);
188
+ const mec = mecanismo();
189
+ return { ...cfg, mecanismo: mec, ativo: cfg.ligado && !!mec };
190
+ }
191
+
192
+ function _teste({ mecanismo: m, avisou } = {}) {
193
+ if (m !== undefined) _forcaMecanismo = m === 'auto' ? undefined : m;
194
+ if (avisou !== undefined) _avisou = avisou;
195
+ }
196
+
197
+ module.exports = { config, salvar, estado, mecanismo, envolver, executar, explicar, filtrarEnv, perfilSeatbelt, argsBwrap, MODOS, _teste };
package/lib/skills.js CHANGED
@@ -377,7 +377,9 @@ function pontuar(skill, texto) {
377
377
  // A borda e escrita a mao em vez de `\b`: o gatilho pode comecar ou
378
378
  // terminar em simbolo ("e-mail"), e ali o `\b` nao vale.
379
379
  const re = new RegExp('(^|[^a-z0-9])' + escapado + '($|[^a-z0-9])');
380
- if (re.test(t)) pontos++;
380
+ // Gatilho de várias palavras é mais específico ("render do vídeo" vale mais que "vídeo"):
381
+ // sem isso uma skill genérica e grande empata e come o teto da específica.
382
+ if (re.test(t)) pontos += p.split(/\s+/).length;
381
383
  }
382
384
  return pontos;
383
385
  }
package/lib/tools.js CHANGED
@@ -13,6 +13,7 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const os = require('os');
15
15
  const { exec, execFile } = require('child_process');
16
+ const sandbox = require('./sandbox.js');
16
17
  const browser = require('./browser');
17
18
  const desktop = require('./desktop');
18
19
  const cursor = require('./cursor');
@@ -278,11 +279,11 @@ const NOME_SECRETO = /(TOKEN|SECRET|KEY|PASSWORD|PASSWD|CREDENTIAL|AUTH|COOKIE|S
278
279
  const PREFIXO_SECRETO = /^(AWS_|OPENAI_|ANTHROPIC_|GEMINI_|GOOGLE_API|GROQ_|OPENROUTER_|AZURE_|HF_|HUGGING)/i;
279
280
  const SEMPRE_PASSA = new Set(['PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'LANG', 'TERM', 'TMPDIR', 'PWD', 'SSH_AUTH_SOCK']);
280
281
 
281
- function ambienteLimpo(env = process.env) {
282
+ function ambienteLimpo(env = process.env, permitidas = []) {
282
283
  const limpo = {};
283
284
  for (const [nome, valor] of Object.entries(env)) {
284
285
  if (valor === undefined) continue;
285
- if (!SEMPRE_PASSA.has(nome) && (NOME_SECRETO.test(nome) || PREFIXO_SECRETO.test(nome))) continue;
286
+ if (!SEMPRE_PASSA.has(nome) && !permitidas.includes(nome) && (NOME_SECRETO.test(nome) || PREFIXO_SECRETO.test(nome))) continue;
286
287
  limpo[nome] = valor;
287
288
  }
288
289
  return limpo;
@@ -664,7 +665,9 @@ async function despachar(name, args, ctx) {
664
665
  return b.outra && r && r.ok !== false ? { ...r, pasta: b.raiz } : r;
665
666
  }
666
667
  case 'run_shell':
667
- return runShell(args.command, args.timeout_ms || 60000, projectDir, ambienteLimpo());
668
+ // Dentro da cerca (lib/sandbox.js); desligada ou sem mecanismo cai no exec de sempre.
669
+ return sandbox.executar(args.command, { projectDir, timeout: args.timeout_ms || 60000,
670
+ env: ambienteLimpo(process.env, sandbox.config().env_permitidas) });
668
671
  case 'run_in_new_terminal':
669
672
  return runInNewTerminal(args.command, args.cwd || projectDir);
670
673
  case 'open_in_browser': {
@@ -830,6 +833,12 @@ async function despachar(name, args, ctx) {
830
833
  return estudio.ferramentaPlano({ ...args, __pasta: projectDir });
831
834
  case 'estudio_midia':
832
835
  return estudio.ferramentaMidia(args);
836
+ case 'estudio_ae':
837
+ return estudio.ferramentaAE(args);
838
+ case 'estudio_motion':
839
+ return estudio.ferramentaMotion(args);
840
+ case 'estudio_editar':
841
+ return estudio.ferramentaEditar(args);
833
842
  case 'estudio_narrar':
834
843
  return estudio.ferramentaNarrar(args);
835
844
  case 'estudio_video':
@@ -937,7 +946,7 @@ async function despachar(name, args, ctx) {
937
946
  parecidas: catalogo.parecidasCom(name),
938
947
  comoResolver: velhoEstudio
939
948
  ? 'O Estúdio foi TROCADO: studio_* não existe mais. As ferramentas de hoje são '
940
- + 'planejar_producao, estudio_projeto, estudio_plano, estudio_midia, estudio_narrar, '
949
+ + 'planejar_producao, estudio_projeto, estudio_plano, estudio_midia, estudio_ae, estudio_motion, estudio_editar, estudio_narrar, '
941
950
  + 'estudio_video, estudio_render e estudio_site. Use-as. '
942
951
  + 'Se ELAS não estiverem na sua lista de ferramentas, o servidor está desatualizado: '
943
952
  + 'PARE de tentar outros nomes e diga isso ao usuário em uma frase — é o que ele '
package/lib/ui.js CHANGED
@@ -556,7 +556,7 @@ const ROTULOS = {
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',
559
- estudio_midia:'Estúdio·mídia', construir_app:'Construir',
559
+ estudio_midia:'Estúdio·mídia', estudio_ae:'Estúdio·After Effects', estudio_motion:'Estúdio·Motion', estudio_editar:'Estúdio·Editar', construir_app:'Construir',
560
560
  remember_general:'Lembrar',
561
561
  mcp_procurar:'MCP·procurar', mcp_conectar:'MCP·conectar', mcp_usar:'MCP',
562
562
  };
package/lib/video/auto.js CHANGED
@@ -254,9 +254,22 @@ function juntarSilencios(sil) {
254
254
  return out;
255
255
  }
256
256
 
257
+ /* O que vem depois do auto. Sem pedido falado (15/10: um vídeo do Instagram,
258
+ "edita pra mim?"), o plano é vazio e só a legenda entra — e o agente
259
+ exportava assim. Aí a edição volta para ele decidir, com o cardápio. */
260
+ function depois(passos) {
261
+ if ((passos || []).length) return { semPedido: false, texto: 'inspect para OLHAR os momentos; export renderiza (o 3D sai no export)' };
262
+ return {
263
+ semPedido: true,
264
+ texto: 'A gravação não tem pedido falado: só a legenda entrou, e isso ainda não é a edição. '
265
+ + 'Se a pessoa pediu para editar, a edição é sua: leia a fala (get_transcript) e decida — por exemplo cortar os silêncios (remove_silence), '
266
+ + 'zoom nos momentos fortes (auto_zoom), apoio quando ela cita uma coisa (add_clips), cor (grade). Olhe com inspect antes do export.',
267
+ };
268
+ }
269
+
257
270
  const OPS = {
258
271
  auto: {
259
- uso: 'auto {asset?,dryRun?} — O VÍDEO É O PEDIDO: lê o que a pessoa FALA na gravação ("separe em camadas", "coloca o logo do Claude aqui", "com o estalar de dedos me coloca numa praia", "junta e vira um cérebro", "cortou o silêncio", "o vídeo acaba") e aplica cada efeito no momento em que ela diz, ancorado na palavra, na mão, no estalo e no juntar das mãos; legenda sempre. Use logo depois do import quando pedirem "edite esse vídeo" sem dizer o quê',
272
+ uso: 'auto {asset?,dryRun?} — O VÍDEO É O PEDIDO: lê o que a pessoa FALA na gravação ("separe em camadas", "coloca o logo do Claude aqui", "com o estalar de dedos me coloca numa praia", "junta e vira um cérebro", "cortou o silêncio", "o vídeo acaba") e aplica cada efeito no momento em que ela diz, ancorado na palavra, na mão, no estalo e no juntar das mãos; legenda sempre. Serve quando a gravação TRAZ pedidos falados. Sem pedido falado (vídeo de outra pessoa, ou só fala), ele põe só a legenda, e o resto da edição é decisão sua',
260
273
  async run(ctx, a) {
261
274
  const T = require('./timeline.js');
262
275
  const C = require('./camadas.js');
@@ -317,13 +330,15 @@ const OPS = {
317
330
  if (L.position && ids.length) await roda('style_captions', { ids, position: L.position }, desde !== null ? 'em cima da cabeça, desde o momento dito' : 'em cima da cabeça');
318
331
  }
319
332
  const falhou = feitos.filter((f) => !f.ok);
333
+ const prox = depois(plano.passos);
320
334
  return { ok: !falhou.length, feitos, tookS: Math.round((Date.now() - inicio) / 100) / 10,
321
335
  ...(falhou.length ? { error: falhou.map((f) => `${f.op}: ${f.error}`).join('; ') } : {}),
322
- depois: 'inspect para OLHAR os momentos; export renderiza (o 3D sai no export)' };
336
+ ...(prox.semPedido ? { semPedido: true } : {}),
337
+ depois: prox.texto };
323
338
  },
324
339
  },
325
340
  };
326
341
 
327
342
  const EX = { auto: '{}' };
328
343
 
329
- module.exports = { planejar, frases, normal, fundoPedido, juntarSilencios, OPS, EX };
344
+ module.exports = { planejar, frases, normal, fundoPedido, juntarSilencios, depois, OPS, EX };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "10.12.0",
3
+ "version": "10.14.0",
4
4
  "description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
5
5
  "main": "bin/primocode.js",
6
6
  "bin": {
@@ -8,7 +8,9 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/primocode.js",
11
- "test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/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/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"
11
+ "postinstall": "node lib/preparar.js || true",
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/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"
12
14
  },
13
15
  "engines": {
14
16
  "node": ">=18.17.0"
@@ -0,0 +1,52 @@
1
+ ---
2
+ nome: After Effects
3
+ descricao: motion design em camadas, com keyframes, ease e expressões, renderizado pelo motor EffectCraft
4
+ quando: after effects, ae, aftereffects, effectcraft, extendscript, jsx, keyframe, motion design
5
+ autor: PrimoCode
6
+ ---
7
+ Use a ferramenta `estudio_ae`: você escreve um JSX (ExtendScript do After Effects),
8
+ o EffectCraft roda, renderiza `video-ae.mp4` e devolve quadros PNG em `ae/`.
9
+
10
+ **Antes de escrever, procure um modelo:** `estudio_midia acao:"projetos"` e, na receita
11
+ com `scripts_ae`, leia os `.jsx` do `caminho` (são projetos reais do Clube do Motion que
12
+ montam as comps por script). Copie o MÉTODO (câmera, curvas, trocas de cena), não o conteúdo.
13
+
14
+ ## Regras
15
+
16
+ - **Sempre com ease.** Keyframe linear parece máquina: ponha `KeyframeEase` em todo
17
+ movimento (`setTemporalEaseAtKey`).
18
+ - **9:16 = 1080×1920** (16:9 = 1920×1080), fps 30. A duração da comp é a do vídeo.
19
+ - **Fontes** vão em `fontes: [{familia, peso}]` (Google Fonts); o mesmo nome entra em `doc.font`.
20
+ - **Confira os quadros**: passe `conferir: [0.5, 2]` e olhe os PNG antes de dar por pronto.
21
+ Texto cortado, sobreposto ou fora da área segura: ajuste o JSX e rode de novo.
22
+ - Erro do script volta com o texto da saída. Corrija a linha, não reescreva tudo.
23
+ - `comp` é o nome exato que você deu no `addComp`.
24
+
25
+ ## Match names que funcionam
26
+
27
+ - `app.project.items.addComp(nome, w, h, 1, dur, fps)`
28
+ - `comp.layers.addSolid([r,g,b], nome, w, h, 1)`, `addText("..")`, `addShape()`
29
+ - Forma: `"ADBE Root Vectors Group".addProperty("ADBE Vector Group")`, depois
30
+ `"ADBE Vectors Group".addProperty("ADBE Vector Shape - Ellipse" | "ADBE Vector Graphic - Fill")`,
31
+ com `"ADBE Vector Ellipse Size"` e `"ADBE Vector Fill Color"`
32
+ - Transform: `"ADBE Transform Group"` com `"ADBE Position" | "ADBE Scale" | "ADBE Opacity"` e `setValueAtTime`
33
+ - Ease: `KeyframeEase(0, 80)` com `setTemporalEaseAtKey(i, [ease], [ease])`
34
+ - Expressão: `prop.expression = "wiggle(2,20)"`
35
+ - Efeito: `"ADBE Effect Parade".addProperty("ADBE Gaussian Blur 2" | "ADBE Glo2")`
36
+ - Texto: `"ADBE Text Properties"` / `"ADBE Text Document"`; `td.value` é o `TextDocument`;
37
+ mude `doc.font`, `fontSize`, `fillColor`, `justification` e grave com `td.setValue(doc)`
38
+
39
+ ## Exemplo mínimo
40
+
41
+ ```js
42
+ var comp = app.project.items.addComp("Cena", 1080, 1920, 1, 3, 30);
43
+ comp.layers.addSolid([0.06, 0.06, 0.1], "fundo", 1080, 1920, 1);
44
+ var t = comp.layers.addText("Olá");
45
+ var td = t.property("ADBE Text Properties").property("ADBE Text Document");
46
+ var doc = td.value; doc.font = "Poppins-Bold"; doc.fontSize = 140; doc.fillColor = [1, 1, 1];
47
+ doc.justification = ParagraphJustification.CENTER_JUSTIFY; td.setValue(doc);
48
+ var pos = t.property("ADBE Transform Group").property("ADBE Position");
49
+ pos.setValueAtTime(0, [540, 1100]); pos.setValueAtTime(0.8, [540, 960]);
50
+ var e = new KeyframeEase(0, 80);
51
+ pos.setTemporalEaseAtKey(1, [e], [e]); pos.setTemporalEaseAtKey(2, [e], [e]);
52
+ ```