primocode 10.13.0 → 10.14.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 (196) hide show
  1. package/README.md +66 -12
  2. package/bin/primocode.js +148 -8
  3. package/lib/act.js +56 -3
  4. package/lib/catalogo.js +12 -1
  5. package/lib/config.js +1 -0
  6. package/lib/estudio/cota.js +157 -0
  7. package/lib/estudio/dados/projetos/clube-ad5.json +105 -0
  8. package/lib/estudio/dados/projetos/clube-ae-sem-memoria.json +107 -0
  9. package/lib/estudio/dados/projetos/clube-agent.json +120 -0
  10. package/lib/estudio/dados/projetos/clube-antes-e-agora.json +86 -0
  11. package/lib/estudio/dados/projetos/clube-apresentando-clube.json +110 -0
  12. package/lib/estudio/dados/projetos/clube-bluevisa.json +106 -0
  13. package/lib/estudio/dados/projetos/clube-caught-in-the-fire.json +127 -0
  14. package/lib/estudio/dados/projetos/clube-chat-cliente-satisfeito.json +98 -0
  15. package/lib/estudio/dados/projetos/clube-chatgpt-anuncios.json +120 -0
  16. package/lib/estudio/dados/projetos/clube-claude-dj.json +140 -0
  17. package/lib/estudio/dados/projetos/clube-claude-mcp.json +142 -0
  18. package/lib/estudio/dados/projetos/clube-cleanui.json +106 -0
  19. package/lib/estudio/dados/projetos/clube-clinica-take-unico.json +128 -0
  20. package/lib/estudio/dados/projetos/clube-clinica.json +104 -0
  21. package/lib/estudio/dados/projetos/clube-clube-opener.json +125 -0
  22. package/lib/estudio/dados/projetos/clube-clube.json +108 -0
  23. package/lib/estudio/dados/projetos/clube-codigo.json +101 -0
  24. package/lib/estudio/dados/projetos/clube-distilbook.json +104 -0
  25. package/lib/estudio/dados/projetos/clube-drive-upload.json +115 -0
  26. package/lib/estudio/dados/projetos/clube-dub.json +119 -0
  27. package/lib/estudio/dados/projetos/clube-enxovaly.json +105 -0
  28. package/lib/estudio/dados/projetos/clube-fecha7.json +118 -0
  29. package/lib/estudio/dados/projetos/clube-filatov.json +128 -0
  30. package/lib/estudio/dados/projetos/clube-frame.json +108 -0
  31. package/lib/estudio/dados/projetos/clube-glass.json +118 -0
  32. package/lib/estudio/dados/projetos/clube-letra-em-apps.json +84 -0
  33. package/lib/estudio/dados/projetos/clube-letra-globo-3d.json +111 -0
  34. package/lib/estudio/dados/projetos/clube-letra-nos-apps.json +146 -0
  35. package/lib/estudio/dados/projetos/clube-letra-seis-mundos.json +100 -0
  36. package/lib/estudio/dados/projetos/clube-lula-3.json +146 -0
  37. package/lib/estudio/dados/projetos/clube-morph.json +119 -0
  38. package/lib/estudio/dados/projetos/clube-motion-designer.json +115 -0
  39. package/lib/estudio/dados/projetos/clube-motion-pacote.json +112 -0
  40. package/lib/estudio/dados/projetos/clube-movimento.json +103 -0
  41. package/lib/estudio/dados/projetos/clube-not-like-us.json +117 -0
  42. package/lib/estudio/dados/projetos/clube-pinterest-inspiracao.json +86 -0
  43. package/lib/estudio/dados/projetos/clube-pixel-e-tinta.json +108 -0
  44. package/lib/estudio/dados/projetos/clube-ponte.json +118 -0
  45. package/lib/estudio/dados/projetos/clube-processo-edit-tonal.json +126 -0
  46. package/lib/estudio/dados/projetos/clube-processo-motion-100-ia.json +109 -0
  47. package/lib/estudio/dados/projetos/clube-promo.json +114 -0
  48. package/lib/estudio/dados/projetos/clube-proposito.json +115 -0
  49. package/lib/estudio/dados/projetos/clube-rankhog.json +128 -0
  50. package/lib/estudio/dados/projetos/clube-recursao.json +116 -0
  51. package/lib/estudio/dados/projetos/clube-revelar.json +118 -0
  52. package/lib/estudio/dados/projetos/clube-robo.json +125 -0
  53. package/lib/estudio/dados/projetos/clube-shelby-ep2.json +144 -0
  54. package/lib/estudio/dados/projetos/clube-shelby.json +97 -0
  55. package/lib/estudio/dados/projetos/clube-solar-eclipse.json +96 -0
  56. package/lib/estudio/dados/projetos/clube-soldier.json +128 -0
  57. package/lib/estudio/dados/projetos/clube-som-de-cena.json +108 -0
  58. package/lib/estudio/dados/projetos/clube-spotify-candidatura.json +112 -0
  59. package/lib/estudio/dados/projetos/clube-tda-app.json +118 -0
  60. package/lib/estudio/dados/projetos/clube-tda-ia.json +75 -0
  61. package/lib/estudio/dados/projetos/clube-telegram-chat.json +118 -0
  62. package/lib/estudio/dados/projetos/clube-telegram-ilha.json +106 -0
  63. package/lib/estudio/dados/projetos/clube-tennessee-nike.json +132 -0
  64. package/lib/estudio/dados/projetos/clube-tudo-se-move.json +112 -0
  65. package/lib/estudio/dados/projetos/clube-uiad.json +107 -0
  66. package/lib/estudio/dados/projetos/clube-viralgrow.json +108 -0
  67. package/lib/estudio/dados/projetos/clube-whose-side.json +140 -0
  68. package/lib/estudio/dados/projetos/clube-youtube-inscrever.json +108 -0
  69. package/lib/estudio/dados/projetos/elementos-do-site-em-movimento.json +49 -0
  70. package/lib/estudio/dados/projetos/explainer-narrado.json +51 -0
  71. package/lib/estudio/dados/projetos/lancamento-de-produto.json +51 -0
  72. package/lib/estudio/dados/projetos/motion-so-com-sons.json +50 -0
  73. package/lib/estudio/dados/projetos/titulo-em-after-effects.json +51 -0
  74. package/lib/estudio/dados/projetos/tour-de-saas.json +50 -0
  75. package/lib/estudio/dados/projetos-ae/antes-e-agora/ae/cenas.jsx +733 -0
  76. package/lib/estudio/dados/projetos-ae/antes-e-agora/assets/fazer_assets.py +88 -0
  77. package/lib/estudio/dados/projetos-ae/letra-em-apps/ae/cenas.jsx +402 -0
  78. package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/preparar_assets.py +17 -0
  79. package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/svg_para_ae.py +25 -0
  80. package/lib/estudio/dados/projetos-ae/letra-nos-apps/BRIEF-AGENTES.md +31 -0
  81. package/lib/estudio/dados/projetos-ae/letra-nos-apps/README.md +53 -0
  82. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ROTEIRO-CENAS.md +69 -0
  83. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/A-agenda.jsx +148 -0
  84. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/B-docs.jsx +174 -0
  85. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/C-alttab.jsx +113 -0
  86. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/D-spotify.jsx +118 -0
  87. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/E-notas.jsx +146 -0
  88. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/F-jornal1.jsx +144 -0
  89. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/G-google.jsx +153 -0
  90. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/H-mapa.jsx +176 -0
  91. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/I-imagens.jsx +132 -0
  92. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/J-jornal2.jsx +191 -0
  93. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/K-maps.jsx +184 -0
  94. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera-movimentos.jsx +48 -0
  95. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera.jsx +57 -0
  96. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/card-spotify.jsx +98 -0
  97. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/digitar.jsx +38 -0
  98. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/jornal-comum.jsx +196 -0
  99. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra.jsx +23 -0
  100. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra_automatica.py +83 -0
  101. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/montagem.jsx +74 -0
  102. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/render_html.py +13 -0
  103. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/util.jsx +162 -0
  104. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-midia-comum.jsx +114 -0
  105. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/wg-util.jsx +360 -0
  106. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/F-jornal1/render_html.py +13 -0
  107. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/render_w-midia.py +26 -0
  108. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/inclin.py +30 -0
  109. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_d.py +36 -0
  110. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_e.py +27 -0
  111. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ajustar_movimentos.py +61 -0
  112. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ancorar.py +74 -0
  113. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/assar.py +47 -0
  114. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/camera3d_jornal.py +172 -0
  115. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/comparar.py +32 -0
  116. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/dados_ae_jornal.py +46 -0
  117. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/folha_contato.py +28 -0
  118. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/homografia_jornal.py +74 -0
  119. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/integrar.py +44 -0
  120. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/medir_quadro_a_quadro.py +32 -0
  121. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/mesa_2d.py +64 -0
  122. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/plot_cenas.py +11 -0
  123. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/retificar_quadros.py +38 -0
  124. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/assar_ab.py +52 -0
  125. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/medir_ab.py +59 -0
  126. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ajustar_h.py +111 -0
  127. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_corrige_cam.py +17 -0
  128. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ecc_cadeia.py +80 -0
  129. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_finalizar_cam.py +65 -0
  130. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_mosaico.py +61 -0
  131. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/01-gramatica-e-roteiro.md +55 -0
  132. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/02-camera.md +66 -0
  133. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/03-montagem-no-ae.md +50 -0
  134. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/04-render-e-conferencia.md +43 -0
  135. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/05-abertura-celular-mergulho.md +43 -0
  136. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces.md +42 -0
  137. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/atraso_imagem.py +31 -0
  138. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/fundo.py +56 -0
  139. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/juntar.py +45 -0
  140. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/montar.py +177 -0
  141. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/rastreio.py +74 -0
  142. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/sincronia.py +18 -0
  143. package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/ae/cenas.jsx +530 -0
  144. package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/assets/fazer_assets.py +107 -0
  145. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/assets/pessoas/figurinha.py +46 -0
  146. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/BRIEF-A.md +83 -0
  147. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/audio.py +179 -0
  148. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/confere.py +20 -0
  149. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte1.js +1 -0
  150. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte2.js +1 -0
  151. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/gerar_emojis.py +11 -0
  152. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/img/filmes/_indice.js +122 -0
  153. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/base.js +380 -0
  154. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/main.js +86 -0
  155. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1a.js +169 -0
  156. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1b.js +455 -0
  157. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1c.js +389 -0
  158. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p2.js +379 -0
  159. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/video.py +62 -0
  160. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/README.md +65 -0
  161. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/oraculo_take.py +137 -0
  162. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/render_take.py +388 -0
  163. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/take.js +235 -0
  164. package/lib/estudio/editor.js +290 -0
  165. package/lib/estudio/effectcraft.js +152 -0
  166. package/lib/estudio/exportar.js +181 -0
  167. package/lib/estudio/index.js +97 -2
  168. package/lib/estudio/kit-ae/base.jsx +1146 -0
  169. package/lib/estudio/kit-ae/cenas.jsx +395 -0
  170. package/lib/estudio/kit-ae/primitivas.jsx +911 -0
  171. package/lib/estudio/maquina-diretor.js +393 -0
  172. package/lib/estudio/maquina-elementos.js +134 -0
  173. package/lib/estudio/maquina-layout.js +198 -0
  174. package/lib/estudio/maquina-som.js +478 -0
  175. package/lib/estudio/maquina-trilha.js +160 -0
  176. package/lib/estudio/maquina-voz.js +429 -0
  177. package/lib/estudio/maquina.js +516 -0
  178. package/lib/estudio/projetos.js +72 -0
  179. package/lib/headless.js +71 -0
  180. package/lib/hooks.js +72 -0
  181. package/lib/limites.js +100 -0
  182. package/lib/local/cerebro.json +398 -12
  183. package/lib/local/entrada.js +46 -2
  184. package/lib/marca-kit.js +312 -0
  185. package/lib/mcp.js +286 -6
  186. package/lib/perigo.js +31 -3
  187. package/lib/planos.js +49 -19
  188. package/lib/preparar.js +111 -0
  189. package/lib/retomar.js +55 -0
  190. package/lib/sandbox.js +197 -0
  191. package/lib/skills.js +3 -1
  192. package/lib/tools.js +13 -4
  193. package/lib/ui.js +1 -1
  194. package/package.json +4 -2
  195. package/skills/after-effects/SKILL.md +52 -0
  196. package/skills/maquina-de-motion/SKILL.md +84 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # PrimoCode v10.13.0
1
+ # PrimoCode v10.14.0
2
2
 
3
3
  Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
4
4
  Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
9
9
  está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
10
10
 
11
11
  ```
12
- ▐▛███▜▌ Primo Code v10.13.0
12
+ ▐▛███▜▌ Primo Code v10.14.0
13
13
  ▝▜█████▛▘ Bem-vindo de volta, Joel
14
14
  ▘▘ ▝▝ ~/primocode · /dir <pasta> muda
15
15
  ```
@@ -17,6 +17,21 @@ está no limite de pedidos, o próximo atende. Você não vê o erro, e não pag
17
17
  Personalize o mascote (`primo`, `fox`, `cat`) e o nome com que ele te chama —
18
18
  veja `/mascot` e `/name`.
19
19
 
20
+ ## Novidades da 10.14
21
+
22
+ - **Vídeo de motion de ponta a ponta** (EffectCraft, 68 receitas, voz, trilha, prévia e editor por comando). Detalhes no `CHANGELOG.md`.
23
+ - **Exportar para redes** em três formatos, com capa, legenda e créditos. **Kit de marca** com `primocode marca`.
24
+ - **Sandbox** (`/sandbox on`), **hooks**, `-p`, `--resume`, `--worktree`, modo plano e **planos por recurso** (`primocode planos`).
25
+ - **Instalou, está pronto:** o motor After Effects baixa sozinho. `primocode doutor` mostra o que falta e como resolver.
26
+
27
+ ## O que mudou na 10.14
28
+
29
+ - **Motor After Effects:** a ferramenta `estudio_ae` roda um JSX e renderiza o mp4, com a skill `after-effects`.
30
+ - **Projetos completos:** seis receitas que a IA consulta (`estudio_midia acao:projetos`), uma delas em After Effects.
31
+ - **MCP remoto:** servidores por HTTP (`{"type":"http","url":"https://…/mcp"}` no `mcp.json`), com login OAuth pelo navegador.
32
+ - **Clube do Motion:** `primocode mcp login clube-do-motion` entra com a conta assinante e libera projetos de motion, referências, skills e músicas.
33
+ - O token fica em `~/.primocode/mcp-tokens.json` (só você lê). `primocode mcp logout <nome>` apaga.
34
+
20
35
  ## O que mudou na 10.13
21
36
 
22
37
  - **Motion pelas referências:** o título enche a tela (rótulo nunca abaixo de 2% da altura), a cena dura de 1 a 2 s, o texto entra com ataque rápido e freio forte, e a frase sai inteira. As saídas que espalhavam letras saíram. O círculo de caneta na palavra-chave ganha o ✓. Sem quadro morto entre as cenas. A conta está em `docs/planos/2026-10-15-motion-pelas-referencias.md`.
@@ -106,6 +121,24 @@ veja `/mascot` e `/name`.
106
121
 
107
122
  Em uma frase: **é o poder de um agente de código no terminal, em português, controlando o seu computador de verdade — sem você precisar configurar chave de API nenhuma.**
108
123
 
124
+ ## Comparado ao Claude Code e ao Codex
125
+
126
+ Tabela completa, com `arquivo:linha`, em [docs/paridade.md](docs/paridade.md). O resumo:
127
+
128
+ | | Claude Code | Codex | PrimoCode |
129
+ |---|---|---|---|
130
+ | Hooks antes/depois de ferramenta | tem | parcial | tem (`~/.primocode/hooks.json`) |
131
+ | Modo só leitura (plan) | tem | read-only | tem (`/permissoes plano`) |
132
+ | Headless (`-p`, JSON, stdin) | tem | `codex exec` | tem |
133
+ | Desfazer por pedido | `/rewind` | usa git | tem (`/desfazer`) |
134
+ | MCP stdio, HTTP, OAuth | tem | tem | tem |
135
+ | Sandbox de SO | opcional | tem | não |
136
+ | Worktrees | tem | não | não |
137
+
138
+ Hook em 3 linhas: `{"PreToolUse":[{"matcher":"run_shell","command":"./checar.sh"}]}`. O comando
139
+ recebe o evento em JSON no stdin; sair com código 2 bloqueia a ferramenta. Em script:
140
+ `git diff | primocode -p "revisa" --output-format json --permission-mode plan`.
141
+
109
142
  ## Instalação
110
143
 
111
144
  ```bash
@@ -302,6 +335,14 @@ De onde vem o material, tudo sem chave de API:
302
335
  A licença desce junto de tudo, num `.licenca.json` ao lado do arquivo:
303
336
  descobrir depois de publicar que a foto exigia crédito é tarde.
304
337
 
338
+ ## Motor After Effects (EffectCraft)
339
+
340
+ A ferramenta `estudio_ae` usa o [EffectCraft](https://github.com/storytold/effectcraft) (storytold, licença MIT/Apache-2.0): ele roda JSX (ExtendScript) no modelo de objetos do After Effects, sem o After Effects instalado. O PrimoCode baixa o binário 0.11.1 na primeira vez para `~/.primocode/bin/effectcraft-0.11.1/` e confere o sha256. `PRIMOCODE_EFFECTCRAFT_BIN` aponta para um binário seu. A IA escreve o JSX, as fontes vêm do Google Fonts e os quadros de conferência saem em `ae/`.
341
+
342
+ ## Projetos completos
343
+
344
+ Seis receitas originais em `lib/estudio/dados/projetos/` (narrado, produto, SaaS, só com sons, elementos de site e After Effects). Cada uma traz a ficha técnica, as etapas e um prompt com `{{CAMPOS}}`. A IA lista com `estudio_midia acao:"projetos"` e lê uma com `acao:"projeto" nome:"<id>"`.
345
+
305
346
  ## Subagentes
306
347
 
307
348
  Para tarefas com partes independentes, o agente delega:
@@ -338,12 +379,10 @@ PrimoCode barra e devolve a tarefa para ele:
338
379
  ## Testes
339
380
 
340
381
  ```bash
341
- npm test
382
+ npm test # o CLI: ferramentas, agente, Estúdio, vídeo (sai com 0)
383
+ npm run test:server # o servidor, dentro de server/
342
384
  ```
343
385
 
344
- 25 verificações sobre glob, grep, edit_file, leitura por partes e bloqueio de
345
- caminho fora do projeto.
346
-
347
386
  ## Uso
348
387
 
349
388
  ```bash
@@ -351,12 +390,16 @@ primocode # interativo
351
390
  primocode "crie um jogo de carro" # one-shot
352
391
  primocode -f app.js "onde está o bug?" # com arquivo anexado
353
392
  primocode -c # continua a última conversa salva (--continue)
393
+ primocode --resume [id] # retoma uma conversa (sem id: lista e você escolhe)
394
+ primocode --worktree <nome> # worktree ../<repo>-<nome> em branch nova, e abre a sessão lá
354
395
  ```
355
396
 
356
397
  ## Comandos
357
398
 
358
399
  | Comando | Descrição |
359
400
  |---|---|
401
+ | `/resume [n\|id]` | Lista as conversas salvas ou retoma uma |
402
+ | `/worktree <nome>` | Cria `../<repo>-<nome>` numa branch nova e entra nele (só em repo git) |
360
403
  | `/dir <caminho>` | Trabalha numa pasta que já existe, onde você quiser |
361
404
  | `/project` | Lista projetos. `/project <nome>` cria em `~/primocode` |
362
405
  | `/projetos [n]` | Os projetos de `~/primocode` do mais recente para o mais antigo, com quando mexeu e o tamanho. `/projetos 2` entra no segundo (o `/project` sem nome mostra a mesma lista) |
@@ -432,17 +475,28 @@ Mexer no mouse e no teclado (controle do computador) sempre pede licença.
432
475
  ## Arquitetura
433
476
 
434
477
  ```
435
- ┌────────────────┐ HTTPS/SSE ┌───────────────────┐ HTTPS ┌──────────┐
436
- │ primocode CLI │ ─────────────► │ primocode-server │ ────────► │OpenRouter│
437
- │ (seu terminal)│ ◄───────────── │ (Render) │ ◄──────── │ API │
438
- └────────────────┘ └───────────────────┘ └──────────┘
439
- executa as tools monta o prompt
440
- localmente e fala com o modelo
478
+ ┌────────────────┐ HTTPS/SSE ┌───────────────────┐ HTTPS ┌─────────────────────┐
479
+ │ primocode CLI │ ─────────────► │ primocode-server │ ────────► │ ZeroGPU · Gemini · │
480
+ │ (seu terminal)│ ◄───────────── │ (Railway) │ ◄──────── │ Groq · HF · OpenRtr │
481
+ └────────────────┘ └───────────────────┘ └─────────────────────┘
482
+ executa as tools monta o prompt, a conta, a corrente: se um modelo
483
+ localmente os planos e a voz recusa, o próximo atende
441
484
  ```
442
485
 
443
486
  As tools rodam **no seu computador** — o servidor nunca toca nos seus arquivos.
444
487
  Ele decide o que fazer; o CLI faz e devolve o resultado.
445
488
 
489
+ ### Um repositório, duas partes
490
+
491
+ | pasta | o que é | como sai |
492
+ |---|---|---|
493
+ | raiz (`bin/`, `lib/`, `skills/`, `studio/`, `voz/`) | o CLI `primocode` | `npm publish` (o `files` do `package.json` não leva o `server/`) |
494
+ | `server/` | o primocode-server: agente, conta Firebase, planos, voz, Estúdio | Railway, serviço `primocode-server`, pasta raiz `server`, em `https://primocode-server-production.up.railway.app` |
495
+
496
+ O `server/` veio do repositório `primocode-server` em 15/10/2026, com o histórico
497
+ inteiro (`git log -- server/`). As chaves ficam nas variáveis do Railway; o
498
+ `server/.env.example` diz quais são.
499
+
446
500
  ## Licença
447
501
 
448
502
  MIT © ConectaPrimoAI
package/bin/primocode.js CHANGED
@@ -44,6 +44,7 @@ const vozModo = require('../lib/voz');
44
44
  const conta = require('../lib/conta');
45
45
  const planosTela = require('../lib/planos.js');
46
46
  const nuvem = require('../lib/nuvem');
47
+ const retomar = require('../lib/retomar');
47
48
  const compat = require('../lib/compatibilidade');
48
49
  const mencoesLib = require('../lib/mencoes');
49
50
  const extra = require('../lib/comandos-extra');
@@ -121,13 +122,17 @@ const state = {
121
122
 
122
123
  const HELP = `
123
124
  ${c.bold('Comandos')}
125
+ ${c.brand('/resume')} [n|id] retoma uma conversa salva (sem argumento, lista)
126
+ ${c.brand('/worktree')} <nome> cria ../<repo>-<nome> numa branch nova e entra
124
127
  ${c.brand('/dir')} <caminho> trabalha numa pasta que já existe (onde você quiser)
125
128
  ${c.brand('/project')} lista projetos ou cria em ~/primocode: /project <nome>
126
129
  ${c.brand('/projetos')} [n] projetos do mais recente, com tamanho · /projetos 2 entra
127
130
  ${c.brand('/novo')} <descrição> começa um projeto NOVO do zero, em pasta própria
128
131
  ${c.brand('/mode')} /mode primocode|chat|auto
129
132
  ${c.brand('/effort')} esforço do agente — do rápido ao mais esperto
130
- ${c.brand('/permissoes')} quando ele pergunta antes de agir: auto|perguntar|livre
133
+ ${c.brand('/permissoes')} quando ele pergunta antes de agir: auto|perguntar|livre|plano
134
+ ${c.brand('-p')} "pedido" modo script: sem tela, aceita stdin e --output-format json
135
+ ${c.brand('/sandbox')} isola os comandos do agente: on|off|rede on|off|status
131
136
  ${c.brand('/compact')} resume a conversa e devolve o contexto
132
137
  ${c.brand('/init')} estuda o projeto e escreve o PRIMO.md
133
138
  ${c.brand('/attach')} <arquivo> anexa arquivo ao próximo pedido · ou @arquivo no pedido
@@ -140,6 +145,7 @@ ${c.bold('Comandos')}
140
145
  ${c.brand('/open')} <arq|url> abre no navegador
141
146
  ${c.brand('/abrir')} abre o projeto: o servidor de pé, ou a index.html
142
147
  ${c.brand('/copiar')} copia a última resposta
148
+ ${c.brand('/videos')} seus vídeos prontos (abrir <n> · pasta <n>)
143
149
  ${c.brand('/historico')} [termo] procura nos seus pedidos antigos
144
150
  ${c.brand('/login')} entra na sua conta · ${c.brand('/account')} mostra o plano
145
151
  ${c.brand('/planos')} os planos e o link para assinar
@@ -152,6 +158,7 @@ ${c.bold('Comandos')}
152
158
  ${c.brand('/custo')} tokens e pedidos desta sessão, por provedor
153
159
  ${c.brand('/atalhos')} as teclas que ajudam
154
160
  ${c.brand('/name')} <nome> como o Primo te chama
161
+ ${c.brand('/marca')} [criar|listar|usar] o kit de marca: cores, fontes e logo em todo vídeo
155
162
  ${c.brand('/mascot')} [nome|n] galeria dos mascotes · /mascot reacoes on|off
156
163
  ${c.brand('/config')} mostra suas preferências
157
164
  ${c.brand('/clear')} limpa a conversa ${c.brand('/cls')} limpa a tela
@@ -183,7 +190,7 @@ const SLASH_COMANDOS = [
183
190
  ['/dir', 'trabalhar numa pasta sua'],
184
191
  ['/mode', 'primocode | chat | auto'],
185
192
  ['/effort', 'esforço: low → ultracode (←/→ para ajustar)'],
186
- ['/permissoes', 'auto | perguntar | livre — quando ele pede licença'],
193
+ ['/permissoes', 'auto | perguntar | livre | plano — quando ele pede licença'],
187
194
  ['/compact', 'resume a conversa e devolve o contexto'],
188
195
  ['/init', 'estuda o projeto e escreve o PRIMO.md (as instruções dele)'],
189
196
  ['/attach', 'anexa arquivo ao pedido'],
@@ -197,6 +204,7 @@ const SLASH_COMANDOS = [
197
204
  ['/open', 'abre no navegador'],
198
205
  ['/abrir', 'abre o projeto atual: o servidor de pé, ou a index.html'],
199
206
  ['/copiar', 'copia a última resposta'],
207
+ ['/videos', 'seus vídeos prontos (abrir n · pasta n)'],
200
208
  ['/historico', 'procura nos seus pedidos antigos: /historico login'],
201
209
  ['/login', 'entra na sua conta (abre o navegador) · /login senha: e-mail e senha aqui'],
202
210
  ['/account', 'sua conta e o seu plano'],
@@ -218,6 +226,7 @@ const SLASH_COMANDOS = [
218
226
  ['/atalhos', 'as teclas que ajudam'],
219
227
  ['/connectors', 'quem está atendendo os seus pedidos'],
220
228
  ['/name', 'como ele te chama'],
229
+ ['/marca', 'kit de marca: cores, fontes e logo'],
221
230
  ['/mascot', 'galeria dos mascotes · escolha por nome ou número'],
222
231
  ['/config', 'suas preferências'],
223
232
  ['/clear', 'limpa a conversa'],
@@ -1804,6 +1813,47 @@ async function continuarUltima() {
1804
1813
  return true;
1805
1814
  }
1806
1815
 
1816
+ /* /resume e --resume: sem referência lista as conversas; com número ou id, retoma. */
1817
+ async function retomarConversa(ref, { listar = true } = {}) {
1818
+ if (!conta.atual()) { console.log(c.muted(' Não conectado. ') + c.brand('/login') + c.muted(' para retomar conversas.')); return false; }
1819
+ const l = await nuvem.listarConversas({ limite: 20 });
1820
+ if (!l.ok) { console.log(c.err(` ${l.error}`)); return false; }
1821
+ if (!l.conversas.length) { console.log(c.muted(' Nenhuma conversa salva ainda.')); return false; }
1822
+ if (!ref) {
1823
+ if (!listar) return false;
1824
+ console.log(c.white('\n Conversas recentes'));
1825
+ l.conversas.forEach((x, i) => console.log(' ' + c.dim(String(i + 1).padStart(2)) + ' ' + c.white(x.titulo.slice(0, 52).padEnd(52))
1826
+ + c.dim(`${String(x.mensagens).padStart(3)} msg ${x.atualizada ? new Date(x.atualizada).toLocaleDateString('pt-BR') : '—'}`)));
1827
+ console.log(c.dim('\n /resume <número ou id> retoma.'));
1828
+ return false;
1829
+ }
1830
+ const alvo = retomar.escolher(l.conversas, ref);
1831
+ if (!alvo) { console.log(c.err(` Não achei a conversa ${ref}.`)); return false; }
1832
+ const conv = await abrirConversa(alvo);
1833
+ if (!conv.ok) { console.log(c.err(` ${conv.error}`)); return false; }
1834
+ console.log(c.ok(` ${alvo.titulo}`) + c.muted(` · ${conv.mensagens.length} mensagens carregadas`));
1835
+ return true;
1836
+ }
1837
+
1838
+ /* `--resume` sem id: mostra a lista e pergunta o número (só em terminal). */
1839
+ async function escolherNaAbertura() {
1840
+ await retomarConversa(null);
1841
+ if (!process.stdin.isTTY) return false;
1842
+ const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
1843
+ const resp = await new Promise((res) => rl.question(' Qual? (número, Enter cancela) ', res));
1844
+ rl.close();
1845
+ return resp.trim() ? retomarConversa(resp.trim()) : false;
1846
+ }
1847
+
1848
+ /* /worktree <nome>: cria e entra. */
1849
+ function entrarNoWorktree(nome) {
1850
+ const w = retomar.criarWorktree(nome, projectDir());
1851
+ if (!w.ok) { console.log(c.err(` ${w.error}`)); return false; }
1852
+ entrarNoProjeto(w.path, '/dir');
1853
+ console.log(c.ok(` worktree ${shortPath(w.path)}`) + c.muted(` · branch ${w.branch}`));
1854
+ return true;
1855
+ }
1856
+
1807
1857
  async function handleSlash(text) {
1808
1858
  const [cmd] = text.trim().slice(1).split(/\s+/);
1809
1859
  const rest = text.trim().slice(1 + cmd.length).trim();
@@ -1818,6 +1868,12 @@ async function handleSlash(text) {
1818
1868
  switch (nome) {
1819
1869
  case 'help': console.log(HELP); break;
1820
1870
 
1871
+ case 'resume': await retomarConversa(rest); break;
1872
+ case 'worktree':
1873
+ if (!rest) console.log(c.muted(' Uso: /worktree <nome> (cria ../<repo>-<nome> numa branch nova e entra)'));
1874
+ else entrarNoWorktree(rest.split(/\s+/)[0]);
1875
+ break;
1876
+
1821
1877
  /* /projetos e /project sem nome são a MESMA lista: antes o /project só
1822
1878
  listava os nomes, em ordem alfabética, e quem procurava "o de ontem"
1823
1879
  tinha de abrir pasta por pasta. Agora vem do mais recente, com
@@ -1908,23 +1964,36 @@ async function handleSlash(text) {
1908
1964
 
1909
1965
  /* Quando ele pergunta antes de agir. Os três modos e o porquê de cada
1910
1966
  um estão em lib/perigo.js. */
1967
+ case 'sandbox': {
1968
+ const sb = require('../lib/sandbox.js');
1969
+ const [a, b] = String(rest || '').toLowerCase().split(/\s+/);
1970
+ if (a === 'on' || a === 'off') sb.salvar({ ligado: a === 'on' });
1971
+ else if (a === 'rede' && (b === 'on' || b === 'off')) sb.salvar({ rede: b === 'on' });
1972
+ else if (a && a !== 'status') { console.log(c.err('Uso: /sandbox [on|off|rede on|rede off|status]')); break; }
1973
+ const e = sb.estado();
1974
+ console.log(c.muted(' Sandbox: ') + (e.ativo ? c.ok('ligado') : c.warn(e.ligado ? 'ligado, mas sem mecanismo neste computador' : 'desligado'))
1975
+ + c.muted(` · mecanismo: ${e.mecanismo || 'nenhum'} · rede: ${e.rede ? 'aberta' : 'bloqueada'} · modo: ${e.modo}`));
1976
+ console.log(c.dim(' Escreve só no projeto, /tmp e ~/.primocode/cache; não lê ~/.ssh, ~/.aws, ~/.npmrc, .env*. Detalhes: docs/sandbox.md'));
1977
+ break;
1978
+ }
1911
1979
  case 'permissoes':
1912
1980
  case 'permissions': {
1913
1981
  const DIZ = {
1914
1982
  auto: 'roda tudo, mas pergunta antes do que não tem volta (apagar fora do projeto, push --force, sudo…)',
1915
1983
  perguntar: 'pergunta antes de todo comando e de toda escrita de arquivo',
1916
1984
  livre: 'nunca pergunta — nem antes do que não tem volta',
1985
+ plano: 'só lê: recusa escrita e comando que muda coisa; investiga e entrega o plano',
1917
1986
  };
1918
1987
  const atual = perigo.normalizarModo(state.config.permissoes);
1919
1988
  if (!rest) {
1920
1989
  console.log(c.muted(' Agora: ') + c.brand(atual) + c.muted(' — ' + DIZ[atual]));
1921
1990
  for (const m of perigo.MODOS) console.log(` ${m === atual ? c.brand('● ' + m) : c.muted('○ ' + m)} ${c.dim(DIZ[m])}`);
1922
- console.log(c.dim(' Use /permissoes auto|perguntar|livre'));
1991
+ console.log(c.dim(' Use /permissoes auto|perguntar|livre|plano'));
1923
1992
  break;
1924
1993
  }
1925
1994
  const pedido = rest.toLowerCase();
1926
1995
  if (!perigo.MODOS.includes(perigo.normalizarModo(pedido)) || (perigo.normalizarModo(pedido) === 'auto' && pedido !== 'auto')) {
1927
- console.log(c.err('Uso: /permissoes auto|perguntar|livre'));
1996
+ console.log(c.err('Uso: /permissoes auto|perguntar|livre|plano'));
1928
1997
  break;
1929
1998
  }
1930
1999
  const m = perigo.normalizarModo(pedido);
@@ -2165,6 +2234,14 @@ async function handleSlash(text) {
2165
2234
  /* A galeria: todos lado a lado, número e nome em cima. Escolhe por
2166
2235
  nome ou número — na hora, se há teclado; senão pelo /mascot <n>.
2167
2236
  Num pipe não pergunta: a "resposta" seria o próximo comando. */
2237
+ // O kit de marca (lib/marca-kit.js): /marca criar|listar|usar <nome> [aqui]|mostrar|apagar <nome>
2238
+ case 'marca': {
2239
+ const kit = require('../lib/marca-kit.js');
2240
+ const perguntar = async (t, p) => (String(await ask(`${c.brand('?')} ${t}${p ? c.muted(` [${p}]`) : ''}: `)).trim() || p || '');
2241
+ await kit.main((rest || 'mostrar').split(/\s+/), { perguntar, dizer: (t) => console.log(c.muted(t)), pasta: state.workDir || process.cwd() });
2242
+ break;
2243
+ }
2244
+
2168
2245
  case 'mascot': case 'mascote': {
2169
2246
  const reac = rest.match(/^(reac(?:oes|ões)?|rea[cç][aã]o|reactions?)\s*(on|off|liga[r]?|desliga[r]?|sim|n[aã]o)?$/i);
2170
2247
  if (reac) {
@@ -2243,7 +2320,7 @@ async function handleSlash(text) {
2243
2320
 
2244
2321
  /* O /status olha só o servidor. O /doctor olha tudo o que costuma
2245
2322
  ser o motivo de "não funciona" — e cada ✗ diz como consertar. */
2246
- case 'doctor': case 'medico': case 'médico': {
2323
+ case 'doctor': case 'doutor': case 'medico': case 'médico': {
2247
2324
  const sp = new Spinner('conferindo').start();
2248
2325
  const linhas = await extra.diagnosticar({
2249
2326
  saude: () => api.getHealth(state.config.server, state.config.token),
@@ -2261,6 +2338,7 @@ async function handleSlash(text) {
2261
2338
  console.log(` ${marca} ${ui.padRight(c.white(l.item), 18)}${ui.truncate(c.muted(l.detalhe || ''), Math.max(20, (process.stdout.columns || 80) - 23))}`);
2262
2339
  if (l.conserto) console.log(` ${c.dim('→ ' + l.conserto)}`);
2263
2340
  }
2341
+ for (const l of require('../lib/preparar.js').estudioLinhas()) linhas.push(l), console.log(` ${l.ok === true ? c.ok('✓') : l.ok === false ? c.err('✗') : c.dim('·')} ${ui.padRight(c.white(l.item), 18)}${c.muted(l.detalhe || '')}`), l.conserto && console.log(` ${c.dim('→ ' + l.conserto)}`);
2264
2342
  const ruins = linhas.filter((l) => l.ok === false).length;
2265
2343
  console.log(ruins ? c.warn(`\n ${ruins} ${ruins === 1 ? 'coisa para arrumar' : 'coisas para arrumar'}.`) : c.ok('\n Tudo certo.'));
2266
2344
  break;
@@ -2282,6 +2360,11 @@ async function handleSlash(text) {
2282
2360
  break;
2283
2361
  }
2284
2362
 
2363
+ case 'videos': case 'vídeos': {
2364
+ console.log(require('../lib/estudio/exportar.js').main(rest.split(/\s+/).filter(Boolean)).texto);
2365
+ break;
2366
+ }
2367
+
2285
2368
  case 'copiar': case 'copy': {
2286
2369
  const r = extra.copiar(state.lastResponse);
2287
2370
  if (r.ok) console.log(c.ok(' Copiado.') + c.dim(` ${state.lastResponse.length} caracteres · é só colar`));
@@ -2792,9 +2875,27 @@ async function main() {
2792
2875
  process.exit(await require('../lib/video/cli.js').main(process.argv.slice(3)));
2793
2876
  }
2794
2877
  // `primocode mcp …`: os servidores MCP pelo run_shell (lib/mcp.js).
2878
+ if (process.argv[2] === 'videos') {
2879
+ const r = require('../lib/estudio/exportar.js').main(process.argv.slice(3));
2880
+ console.log(r.texto); process.exit(r.ok ? 0 : 1);
2881
+ }
2795
2882
  if (process.argv[2] === 'skill' || process.argv[2] === 'skills') {
2796
2883
  process.exit(require('../lib/skills.js').main(process.argv.slice(3)));
2797
2884
  }
2885
+ // `primocode planos`: a tabela comparativa, sem abrir a sessão.
2886
+ if (['doutor', 'doctor', 'preparar'].includes(process.argv[2])) {
2887
+ process.exit(await require('../lib/preparar.js').main(process.argv.slice(2)));
2888
+ }
2889
+ if (process.argv[2] === 'planos') {
2890
+ const { loadConfig } = require('../lib/config');
2891
+ const r = await planosTela.buscarPlanos(loadConfig().server);
2892
+ const eu = conta.atual();
2893
+ for (const l of planosTela.desenhar(r, { cores: c, atual: eu && eu.pro ? eu.plano : null, site: conta.SITE })) console.log(l);
2894
+ process.exit(0);
2895
+ }
2896
+ if (process.argv[2] === 'marca') {
2897
+ process.exit(await require('../lib/marca-kit.js').main(process.argv.slice(3)));
2898
+ }
2798
2899
  if (process.argv[2] === 'mcp') {
2799
2900
  process.exit(await require('../lib/mcp.js').main(process.argv.slice(3)));
2800
2901
  }
@@ -2825,7 +2926,16 @@ async function main() {
2825
2926
  // resto (one-shot, -f) segue sendo lido do jeito de sempre.
2826
2927
  const todos = process.argv.slice(2);
2827
2928
  const continuar = todos.some((a) => a === '-c' || a === '--continue');
2828
- const args = todos.filter((a) => a !== '-c' && a !== '--continue');
2929
+ const fl = retomar.analisar(todos.filter((a) => a !== '-c' && a !== '--continue'));
2930
+ const args = fl.args;
2931
+ if (fl.erro) { console.error(fl.erro); process.exit(1); }
2932
+ if (fl.worktree) {
2933
+ // Antes de tudo: a sessão inteira nasce dentro do worktree.
2934
+ const w = retomar.criarWorktree(fl.worktree, process.cwd());
2935
+ if (!w.ok) { console.error(w.error); process.exit(1); }
2936
+ process.chdir(w.path);
2937
+ console.log(c.dim(` ⎿ worktree ${w.path} (branch ${w.branch})`));
2938
+ }
2829
2939
 
2830
2940
  // Flags padrão de CLI — funcionam em qualquer máquina, úteis em scripts/CI.
2831
2941
  if (args[0] === '--version' || args[0] === '-v' || args[0] === '-V') {
@@ -2838,6 +2948,33 @@ async function main() {
2838
2948
  process.exit(0);
2839
2949
  }
2840
2950
 
2951
+ /* `-p` / `--print`: o modo de script e CI (lib/headless.js). Sem tela, o
2952
+ stdin entra no pedido, e --output-format json devolve um JSON só. */
2953
+ {
2954
+ const H = require('../lib/headless.js');
2955
+ const h = H.analisar(args);
2956
+ if (h.ativo) {
2957
+ if (h.erro) { console.error(h.erro); process.exit(1); }
2958
+ if (h.permissao) {
2959
+ const perm = perigo.normalizarModo(h.permissao);
2960
+ state.config.permissoes = perm; // só nesta execução, não grava
2961
+ }
2962
+ const inicio = Date.now();
2963
+ const pedido = H.montarPedido(h.resto.filter((a) => a !== '-f'), await H.lerStdin());
2964
+ if (!pedido) { console.error('Uso: primocode -p "pedido" [--output-format json] [--permission-mode plan]'); process.exit(1); }
2965
+ if (typeof fl.resume === 'string') await retomarConversa(fl.resume);
2966
+ else if (continuar) await continuarUltima();
2967
+ state.semTerminal = true;
2968
+ let erro = null;
2969
+ const texto = await H.capturar(async () => {
2970
+ try { await sendPrompt(pedido); } catch (e) { erro = e.message || String(e); }
2971
+ }, { calar: h.formato === 'json' });
2972
+ if (h.formato === 'json') console.log(H.resultadoJson({ texto, erro, inicio, permissao: state.config.permissoes }));
2973
+ else if (erro) console.error(erro);
2974
+ process.exit(erro ? 1 : 0);
2975
+ }
2976
+ }
2977
+
2841
2978
  // Modo one-shot: primocode "crie um jogo de carro"
2842
2979
  if (args.length && !args[0].startsWith('-')) {
2843
2980
  const fileFlag = args.indexOf('-f');
@@ -2852,7 +2989,8 @@ async function main() {
2852
2989
  }
2853
2990
 
2854
2991
  // Com -c, o pedido continua a última conversa em vez de chegar sozinho.
2855
- if (continuar) await continuarUltima();
2992
+ if (typeof fl.resume === 'string') await retomarConversa(fl.resume);
2993
+ else if (continuar) await continuarUltima();
2856
2994
  /* O MESMO caminho do pedido digitado (sendPrompt). Este modo tinha um
2857
2995
  runAct próprio, e por isso ficava sem tudo o que vive lá: o plano em
2858
2996
  passos, a retomada automática, o @arquivo, o checkpoint do /desfazer. */
@@ -3088,7 +3226,9 @@ async function main() {
3088
3226
  }).catch(() => { state.liberado = true; })
3089
3227
  // Depois da abertura, porque é nela que a pessoa entra na conta — e
3090
3228
  // sem conta não há conversa para achar.
3091
- .then(() => (continuar ? continuarUltima().catch(() => false) : null))
3229
+ .then(() => (fl.resume === true ? escolherNaAbertura().catch(() => false)
3230
+ : fl.resume ? retomarConversa(fl.resume).catch(() => false)
3231
+ : continuar ? continuarUltima().catch(() => false) : null))
3092
3232
  .then(() => {
3093
3233
  // O que sobrou de adiantado não era resposta de nada: vira comando,
3094
3234
  // na ordem em que foi digitado. Nada se perde, só esperou a vez.
package/lib/act.js CHANGED
@@ -13,6 +13,8 @@
13
13
  const { c, Spinner, PENSANDO } = require('./ui');
14
14
  const fala = require('./fala');
15
15
  const especialistas = require('./especialistas');
16
+ const limites = require('./limites');
17
+ let avisouParalelo = false;
16
18
  const tools = require('./tools');
17
19
  const api = require('./api');
18
20
  const cfg = require('./config');
@@ -24,6 +26,7 @@ const permissao = require('./permissao');
24
26
  const perigo = require('./perigo');
25
27
  const skills = require('./skills');
26
28
  const checkpoint = require('./checkpoint');
29
+ const hooks = require('./hooks');
27
30
  const tempos = require('./tempos');
28
31
  const preLeitura = require('./pre-leitura');
29
32
  const travado = require('./travado');
@@ -795,6 +798,9 @@ async function runAct(userPrompt, opts) {
795
798
  };
796
799
  const falhasPendentes = [];
797
800
  let tentativasDeEncerrar = 0;
801
+ // Portão de prova fresca (Gravitas, comum/desmontados/udaysharmadev-Gravitas.md): o
802
+ // `finish` depois de editar código sem rodar nada depois volta UMA vez.
803
+ const prova = novoPortaoDeProva();
798
804
  /* ── CONFERIR ANTES DE DIZER "PRONTO" ───────────────────────────────
799
805
  * Mexeu em página (html, css, js) nesta tarefa? Antes de aceitar o
800
806
  * `finish`, o PrimoCode abre a página num navegador invisível e olha: tela
@@ -1318,8 +1324,12 @@ async function runAct(userPrompt, opts) {
1318
1324
  const spawns = toolCalls.filter((tc) => tc.name === 'spawn_agent');
1319
1325
  const resultadosSpawn = new Map();
1320
1326
  if (spawns.length && !PARAR) garantirPasta();
1321
- if (spawns.length > 1 && !PARAR) {
1322
- const juntos = spawns.slice(0, 8);
1327
+ /* O plano decide quantos subagentes rodam JUNTOS (lib/limites.js; grátis e
1328
+ Primo: um de cada vez, o resto continua em fila). Nunca recusa: só diz o porquê. */
1329
+ const par = spawns.length > 1 ? await limites.paralelo() : null;
1330
+ if (par) { const av = par.aviso(spawns.length); if (av && !avisouParalelo) { avisouParalelo = true; console.log(` ${av}`); } }
1331
+ if (spawns.length > 1 && par.max > 1 && !PARAR) {
1332
+ const juntos = spawns.slice(0, par.max);
1323
1333
  fala.paralelo(juntos.length, profundidade);
1324
1334
  const ids = juntos.map((tc) => anotar(tc, 'permitida'));
1325
1335
  const rs = await Promise.all(juntos.map((tc) =>
@@ -1361,7 +1371,13 @@ async function runAct(userPrompt, opts) {
1361
1371
  let motivoTravou = null;
1362
1372
  for (const tc of toolCalls) {
1363
1373
  if (PARAR) break;
1374
+ prova.ver(tc);
1364
1375
  if (tc.name === 'finish') {
1376
+ const faltaProva = prova.barrar();
1377
+ if (faltaProva) {
1378
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: faltaProva });
1379
+ continue;
1380
+ }
1365
1381
  const relato = await conferirPagina();
1366
1382
  if (relato) {
1367
1383
  messages.push({
@@ -1588,6 +1604,17 @@ async function runAct(userPrompt, opts) {
1588
1604
  continue;
1589
1605
  }
1590
1606
 
1607
+ /* Hook PreToolUse (lib/hooks.js): exit 2 bloqueia a ferramenta. */
1608
+ const gancho = hooks.rodar('PreToolUse', { nome: tc.name, args: tc.arguments, projectDir: pastaAtual });
1609
+ if (gancho.bloqueado) {
1610
+ anotar(tc, 'recusada', gancho.motivo);
1611
+ fala.acao(tc.name, tc.arguments, profundidade);
1612
+ console.log(fala.resultado(profundidade) + c.warn(`bloqueado por hook — ${gancho.motivo.slice(0, 80)}`));
1613
+ registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: gancho.motivo });
1614
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: `Bloqueado por hook: ${gancho.motivo}` }) });
1615
+ continue;
1616
+ }
1617
+
1591
1618
  algoRodou = true;
1592
1619
  rodouTool = true;
1593
1620
  fala.acao(tc.name, tc.arguments, profundidade);
@@ -1604,6 +1631,7 @@ async function runAct(userPrompt, opts) {
1604
1631
  const result = adiantadas.has(tc.id) ? adiantadas.get(tc.id)
1605
1632
  : await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api });
1606
1633
  auditoria.depois(idAcao, result);
1634
+ for (const aviso of hooks.rodar('PostToolUse', { nome: tc.name, args: tc.arguments, resultado: result, projectDir: pastaAtual }).avisos) console.log(c.dim(` ⎿ ${aviso}`));
1607
1635
  traco(`${tc.name} devolveu ok=${result && result.ok}`);
1608
1636
  if (/^estudio_|^planejar_producao$/.test(tc.name) && result && result.ok !== false) {
1609
1637
  const id = result.id || (result.projeto && result.projeto.id) || (tc.arguments && tc.arguments.id);
@@ -1986,5 +2014,30 @@ async function rodarPipeline(args, ctx) {
1986
2014
  });
1987
2015
  }
1988
2016
 
1989
- module.exports = { runAct, videoForaDeEdicao, entregaDoAgente, lembreteDeParalelo, buildMemoryContext, leituraPeloShell, historicoEnxuto, interromper, mandarDuranteATarefa, trabalhando, sobrasDaCaixa, entregarRecados, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
2017
+ /**
2018
+ * Portão de prova fresca: marca a edição de código e o comando rodado, nessa
2019
+ * ordem. `barrar()` devolve a frase de "AINDA NÃO" (uma vez só) quando o
2020
+ * último código editado não tem comando depois dele; senão null. Página
2021
+ * (html/css) fica com o conferirPagina.
2022
+ */
2023
+ const CODIGO = /\.(m?[jt]sx?|cjs|py|rb|go|rs|java|php|sh|c|cpp|cs)$/i;
2024
+ function novoPortaoDeProva() {
2025
+ let seq = 0, edicao = 0, rodou = 0, avisou = false;
2026
+ return {
2027
+ ver(tc) {
2028
+ const a = (tc && tc.arguments) || {};
2029
+ if (tc.name === 'write_file' || tc.name === 'edit_file' || tc.name === 'multi_edit') {
2030
+ if (CODIGO.test(String(a.path || a.file_path || ''))) edicao = ++seq;
2031
+ } else if (tc.name === 'run_shell' || tc.name === 'run_in_new_terminal') rodou = ++seq;
2032
+ },
2033
+ barrar() {
2034
+ if (!edicao || rodou > edicao || avisou) return null;
2035
+ avisou = true;
2036
+ return 'AINDA NÃO: você editou código e não rodou nada depois. Rode o teste ou o comando que prova '
2037
+ + 'que funciona, leia o resultado e chame finish de novo (ou diga o que não conseguiu conferir).';
2038
+ },
2039
+ };
2040
+ }
2041
+
2042
+ module.exports = { novoPortaoDeProva, runAct, videoForaDeEdicao, entregaDoAgente, lembreteDeParalelo, buildMemoryContext, leituraPeloShell, historicoEnxuto, interromper, mandarDuranteATarefa, trabalhando, sobrasDaCaixa, entregarRecados, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
1990
2043
  anotacaoDoQueJaFoi, encolherParaContinuar, MAX_ITERATIONS };
package/lib/catalogo.js CHANGED
@@ -111,7 +111,8 @@ const GRUPOS = [
111
111
  + 'o render empurra para dentro, encolhe o texto largo e afasta o colado; enfeite de fundo marque decorativo: true.' },
112
112
 
113
113
  { tool: 'estudio_midia', escreve: true, faz: 'procura e baixa ícone, imagem, música e efeito sonoro da internet, e lista as fontes',
114
- args: 'acao: biblioteca | estilos | salvar_estilo | movimentos | cenarios | salvar_cenario | imagem | icones | icones3d | animacoes | som | trilha | efeito | fontes | print | elementos | elemento | recortar. termo. id (para baixar). '
114
+ args: 'acao: biblioteca | projetos | projeto | estilos | salvar_estilo | movimentos | cenarios | salvar_cenario | imagem | icones | icones3d | animacoes | som | trilha | efeito | fontes | print | elementos | elemento | recortar. termo. id (para baixar). '
115
+ + 'projetos termo categoria:narrado|produto|saas|sound|elementos|ae: receitas de projeto completo (ficha, etapas, prompt); projeto nome:"<id>" traz a receita inteira. '
115
116
  + 'movimentos termo:"quique" categoria:entrada|saida|enfase|loop: os 100+ movimentos prontos com nome; no elemento, movimento:"<nome>" (ou {nome, em, duracao}, ou uma lista) vira quadros-chave sozinho. Varie: não use o mesmo em todo elemento. Pode inventar o seu em anima. '
116
117
  + 'estilos: as peças do gerativo (humor, paleta, fontes, trilha, ambiente, acento, material) — dezenas de milhares de combinações, e os estilos que a pessoa salvou. '
117
118
  + 'Monte o do vídeo pelo ASSUNTO em roteiro.estiloVisual; misture um salvo com {base:"<nome>", ...}; crie paleta nova com 5 cores. '
@@ -136,6 +137,16 @@ const GRUPOS = [
136
137
  + 'icones: sem "nome", procura; com "nome" ("lucide:zap"), baixa. '
137
138
  + 'Tudo é BAIXADO para a pasta do projeto — o plano nunca aponta para a internet.' },
138
139
 
140
+ { tool: 'estudio_ae', escreve: true, faz: 'monta e renderiza uma composição em After Effects (JSX, via EffectCraft), com keyframes, expressões e efeitos',
141
+ args: 'id. script (o JSX: app.project.items.addComp, camadas, setValueAtTime com ease). comp (o nome da composição). fontes [{familia, peso}]. '
142
+ + 'audio (caminho relativo, juntado ao vídeo). conferir [segundos]: quadros PNG para olhar antes de dar por pronto. Leia a skill after-effects. Sai em video-ae.mp4.' },
143
+ { tool: 'estudio_motion', escreve: true, faz: 'a máquina de motion: do assunto ao vídeo de motion pronto (cenas no After Effects, sons no tempo, trilha e voz opcionais, −14 LUFS)',
144
+ args: 'id. assunto. formato (9:16|1:1|16:9). duracao (6 a 60 s). receita (id de estudio_midia acao:"projetos": usa o formato e a duração da ficha). '
145
+ + 'marca {cores:{fundo,destaque,tinta}, fontes:{titulo,texto,destaque}}. cenas [{papel, tipo?, fala?, titulo, itens, valor, rotulo, antes, depois, texto, autor, botao}] (o diretor escolhe entre 38 primitivas, 15 transições, 8 câmeras e 8 tipografias, com assinatura própria; a palavra-chave vai entre *asteriscos*). '
146
+ + 'elementos (termos reais do assunto). transicoes. camera. tipografia. semente. voz (texto ou {}; o whisper mede cada palavra). trilha (true ou o que buscar; false desliga). Sem cenas, o diretor monta. Prefira esta a escrever JSX. Sai em video-motion.mp4. previa:true = rascunho em segundos (meia resolução, sem mix final) para aprovar antes do final.' },
147
+ { tool: 'estudio_editar', escreve: true, faz: 'o editor por comando: muda um vídeo do estudio_motion (texto, cor, fonte, ritmo, cenas, trilha, volume, formato) e guarda versões',
148
+ args: 'id_video. mudancas [{op, ...}]: texto {cena,valor} · cor {fundo,destaque,tinta|paleta} · fonte {titulo,texto,destaque} · ritmo {fator} · duracao {cena,segundos|total} · remover/mover/duplicar {cena,para} · trilha {valor:false|busca} · volume {alvo:voz|trilha|sfx,db} · formato {valor}. '
149
+ + 'previa (rascunho rápido). final (render final da versão atual). voltar_para (n da versão). listar. "Deixa mais rápido" = ritmo fator 0.8; "tira a música" = trilha valor false. Sai em versoes/vN.' },
139
150
  { tool: 'estudio_narrar', escreve: true, faz: 'gera a narração em voz neural e devolve o tempo MEDIDO de cada fala',
140
151
  args: 'id. texto. voz (antonio|francisca|thalita). estilo (normal|anuncio|calmo|dramatico). '
141
152
  + 'vozes: true lista as vozes. '
package/lib/config.js CHANGED
@@ -400,6 +400,7 @@ function esquecerProjeto() {
400
400
  }
401
401
 
402
402
  module.exports = {
403
+ tokenDaConta,
403
404
  tokensDoAssunto, GENERICAS,
404
405
  lembrarProjeto, projetoLembrado, esquecerProjeto, PROJETO_ATIVO_FILE, pastasRecentes, nomeDaPasta,
405
406
  loadConfig, saveConfig, appendHistory, servidor, SERVIDOR,