primocode 10.13.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 (195) hide show
  1. package/README.md +66 -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/cota.js +150 -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 +285 -0
  165. package/lib/estudio/effectcraft.js +152 -0
  166. package/lib/estudio/exportar.js +181 -0
  167. package/lib/estudio/index.js +90 -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 +60 -0
  179. package/lib/headless.js +71 -0
  180. package/lib/hooks.js +72 -0
  181. package/lib/local/cerebro.json +398 -12
  182. package/lib/local/entrada.js +46 -2
  183. package/lib/marca-kit.js +309 -0
  184. package/lib/mcp.js +286 -6
  185. package/lib/perigo.js +31 -3
  186. package/lib/planos.js +47 -19
  187. package/lib/preparar.js +111 -0
  188. package/lib/retomar.js +55 -0
  189. package/lib/sandbox.js +197 -0
  190. package/lib/skills.js +3 -1
  191. package/lib/tools.js +13 -4
  192. package/lib/ui.js +1 -1
  193. package/package.json +4 -2
  194. package/skills/after-effects/SKILL.md +52 -0
  195. package/skills/maquina-de-motion/SKILL.md +84 -0
@@ -88,6 +88,13 @@ const GRUPOS = [
88
88
  tools: ['estudio_video'],
89
89
  gatilho: /\b(grava[çc][ãa]o|gravei|filmagem|filmei|meu v[íi]deo|edit\w* (o |a |meu |minha |esse |essa |este |esta )?(v[íi]deo|grava[çc][ãa]o)|\.mp4|\.mov|legendas?|cort(a|ar|e) (o )?sil[êe]ncio|recort(a|ar|e) (a )?pessoa|transcrev\w*|importa\w* (o )?v[íi]deo)\b/i,
90
90
  },
91
+ {
92
+ /* O motor After Effects só entra quando o pedido o chama: o esquema
93
+ dele pesa no teto do minuto e "faz um vídeo" não precisa dele. */
94
+ nome: 'after-effects',
95
+ tools: ['estudio_ae', 'estudio_motion', 'estudio_editar'],
96
+ gatilho: /\b(after ?effects?|aftereffects|\bae\b|effectcraft|extendscript|\.jsx|keyframes?|express[ãa]o do ae|motion( design| graphics?)?|an[uú]ncio animado|vinheta|reels?|estudio_motion|estudio_editar|editor de v[íi]deo|pr[ée]via|deixa mais (r[áa]pido|lento)|tira a m[úu]sica|troca (o|a) (t[íi]tulo|cor|fonte|m[úu]sica))\b/i,
97
+ },
91
98
  {
92
99
  nome: 'navegador',
93
100
  tools: ['browser_open', 'browser_click', 'browser_type', 'browser_key',
@@ -154,7 +161,7 @@ const GRUPOS = [
154
161
  ASSUNTO (o que costuma ter partes) e pelo TAMANHO (ver `ehGrande`). */
155
162
  nome: 'delegar',
156
163
  tools: ['spawn_agent'],
157
- gatilho: /\b(v[áa]rios|v[áa]rias|todos os|todas as|em paralelo|projeto inteiro|refator\w*|migra[çc][ãa]o|auditor\w*|e tamb[ée]m|e depois|al[ée]m disso|sistema|plataforma|complet[oa]|backend|frontend|front e back|full.?stack|testes|telas|m[óo]dulos|camadas|arquivos|crud|api e|app|aplicativo|site)\b/i,
164
+ gatilho: /\b(v[áa]rios|v[áa]rias|todos os|todas as|em paralelo|projeto inteiro|refator\w*|migra[çc][ãa]o|auditor\w*|e tamb[ée]m|e depois|al[ée]m disso|sistema|plataforma|complet[oa]|backend|frontend|front e back|full.?stack|testes|telas|m[óo]dulos|camadas|arquivos|crud|api e)\b/i, // sem "site", "app", "aplicativo": casavam com quase todo pedido (13/10)
158
165
  },
159
166
  {
160
167
  nome: 'terminal',
@@ -189,7 +196,14 @@ function gruposDe(texto, forcar = []) {
189
196
  // Pedido grande delega mesmo sem a palavra certa. E quem pode construir
190
197
  // um app inteiro também pode dividir: as duas saídas vão juntas, e a
191
198
  // seção de delegar diz quando é cada uma.
192
- if (ehGrande(t) || escolhidos.has('pipeline')) escolhidos.add('delegar');
199
+ /* O TIME SÓ EM PEDIDO GRANDE (13/10: "como fazer a cota não acabar?").
200
+ "Faz um site de portfólio" ganhava o construir_app e o spawn_agent, e o
201
+ modelo os usava: 22 chamadas, 6 de subagente, para um site de uma página.
202
+ Cada subagente é mais pedidos na cota grátis. Pedido pequeno: um agente. */
203
+ const grande = ehGrande(t);
204
+ if (!grande) escolhidos.delete('pipeline');
205
+ // O spawn_agent segue o gatilho dele (backend e frontend, várias telas…); o pedido grande também o ganha.
206
+ if (grande) escolhidos.add('delegar');
193
207
  // Antes de clicar, o MCP: quem vai mexer no navegador ou no desktop leva os conectores.
194
208
  if (escolhidos.has('navegador') || escolhidos.has('computador')) escolhidos.add('mcp');
195
209
  return escolhidos;
@@ -348,11 +362,27 @@ function referenciasProtegidas(messages) {
348
362
  return new Set(ultima.values());
349
363
  }
350
364
 
365
+ /* O QUE FOI FEITO NÃO SOME (13/10: "continua lendo e listando coisas
366
+ desnecessárias"). Com ~1.900 tokens de conversa, as leituras velhas saíam
367
+ inteiras e o modelo, sem saber que já tinha lido, lia de novo. Agora o
368
+ resultado encurtado diz o que era, e a rodada descartada vira uma linha no
369
+ pedido: "você já fez isto, não repita". */
370
+ const LEITURAS = new Set(['read_file', 'list_dir', 'glob', 'grep']);
371
+ function chamadaCurta(tc) {
372
+ const f = (tc && tc.function) || {};
373
+ let a = {};
374
+ try { a = JSON.parse(f.arguments || '{}'); } catch { /* argumento ilegível */ }
375
+ const alvo = a.path || a.pattern || a.query || a.command || a.id || '';
376
+ return `${f.name || '?'}${alvo ? ' ' + String(alvo).slice(0, 50) : ''}`;
377
+ }
378
+
351
379
  function caberNoTeto(messages, sobra) {
352
380
  const conta = (m) => tokensDe(m);
353
381
  if (conta(messages) <= sobra) return { messages, cortou: false };
354
382
 
355
383
  let atual = messages.map((m) => ({ ...m }));
384
+ const chamadaDe = new Map();
385
+ for (const m of atual) if (m.role === 'assistant' && Array.isArray(m.tool_calls)) for (const tc of m.tool_calls) chamadaDe.set(tc.id, tc);
356
386
 
357
387
  // 1 e 2: encurtar resultados antigos, em dois apertos.
358
388
  const protegidas = referenciasProtegidas(atual);
@@ -369,6 +399,11 @@ function caberNoTeto(messages, sobra) {
369
399
  }
370
400
  if (m.role !== 'tool' || manterInteiros.has(i)) return m;
371
401
  if (texto.length <= limite) return m;
402
+ const tc = chamadaDe.get(m.tool_call_id);
403
+ if (tc && LEITURAS.has((tc.function || {}).name)) {
404
+ const linhas = texto.split('\n').length;
405
+ return { ...m, content: `[já lido: ${chamadaCurta(tc)}, ${linhas} linhas — saiu daqui para caber no plano. Não leia de novo; releia só se for editar ESTE arquivo agora.]` };
406
+ }
372
407
  return { ...m, content: texto.slice(0, limite) + '…(encurtado para caber no limite do plano)' };
373
408
  });
374
409
  if (conta(atual) <= sobra) return { messages: atual, cortou: true, como: 'encurtou resultados antigos' };
@@ -390,6 +425,15 @@ function caberNoTeto(messages, sobra) {
390
425
  while (fim < atual.length && atual[fim].role === 'tool') fim++;
391
426
  // Nunca engolir tudo: para se sobrar só o pedido do usuário.
392
427
  if (fim >= atual.length - 1) break;
428
+ // A rodada sai, o rastro fica: uma linha no último pedido antes dela.
429
+ const feitas = (atual[primeiroAssistente].tool_calls || []).map(chamadaCurta);
430
+ let u = primeiroAssistente - 1;
431
+ while (u >= 0 && atual[u].role !== 'user') u--;
432
+ if (u >= 0 && feitas.length && typeof atual[u].content === 'string') {
433
+ const marca = '\n\n[você já fez, e saiu da conversa para caber no plano: ';
434
+ const c = atual[u].content;
435
+ atual[u] = { ...atual[u], content: c.includes(marca) ? c.replace(/\. Não repita\.\]\s*$/, '; ' + feitas.join('; ') + '. Não repita.]') : c + marca + feitas.join('; ') + '. Não repita.]' };
436
+ }
393
437
  atual = [...atual.slice(0, primeiroAssistente), ...atual.slice(fim)];
394
438
  }
395
439
 
@@ -0,0 +1,309 @@
1
+ /**
2
+ * marca-kit.js — o KIT DE MARCA: a pessoa cria uma vez, todo vídeo e site usa.
3
+ *
4
+ * ~/.primocode/marcas/<nome>/marca.json nome, cores {fundo,destaque,tinta,apoio},
5
+ * fontes {titulo,texto} (Google Fonts), tom, slogan,
6
+ * site, redes, assinatura (motion preferida, opcional)
7
+ * ~/.primocode/marcas/<nome>/logo.png|svg
8
+ * ~/.primocode/marcas/.ativa nome do kit ativo (global)
9
+ * <projeto>/.primocode/marca fixa o kit naquela pasta (ganha do global)
10
+ *
11
+ * Comandos: `primocode marca criar|listar|usar <nome> [aqui]|mostrar|apagar <nome>` e `/marca`.
12
+ * O Estúdio chama `preencher(marca, {pastas})`: o kit entra por baixo, o que o
13
+ * roteiro já trouxe ganha, campo a campo.
14
+ */
15
+ const fs = require('fs');
16
+ const os = require('os');
17
+ const path = require('path');
18
+
19
+ const PAPEIS = ['fundo', 'destaque', 'tinta', 'apoio'];
20
+
21
+ function raiz() { return process.env.PRIMOCODE_MARCAS || path.join(os.homedir(), '.primocode', 'marcas'); }
22
+
23
+ function slug(nome) {
24
+ return String(nome || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()
25
+ .replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40);
26
+ }
27
+ const hexOk = (h) => /^#[0-9a-f]{6}$/i.test(String(h || ''));
28
+ function normHex(h) {
29
+ let s = String(h || '').trim();
30
+ if (/^[0-9a-f]{3}$/i.test(s)) s = '#' + s;
31
+ if (/^#[0-9a-f]{3}$/i.test(s)) s = '#' + s.slice(1).split('').map((x) => x + x).join('');
32
+ if (/^[0-9a-f]{6}$/i.test(s)) s = '#' + s;
33
+ return hexOk(s) ? s.toLowerCase() : null;
34
+ }
35
+
36
+ // ── contraste (WCAG) ─────────────────────────────────────────────────────
37
+ function rgb(h) { const n = parseInt(h.slice(1), 16); return [n >> 16, (n >> 8) & 255, n & 255]; }
38
+ function luz(h) {
39
+ const [r, g, b] = rgb(h).map((v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); });
40
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
41
+ }
42
+ function contraste(a, b) {
43
+ const [x, y] = [luz(a), luz(b)].sort((p, q) => q - p);
44
+ return (x + 0.05) / (y + 0.05);
45
+ }
46
+ function misturar(a, b, t) {
47
+ const A = rgb(a), B = rgb(b);
48
+ return '#' + A.map((v, i) => Math.round(v + (B[i] - v) * t).toString(16).padStart(2, '0')).join('');
49
+ }
50
+ /** A cor mais perto de `cor` que passa de `min` sobre `fundo` (clareia ou escurece em direção ao extremo que contrasta). */
51
+ function ajustar(cor, fundo, min = 4.5) {
52
+ const alvo = luz(fundo) > 0.18 ? '#000000' : '#ffffff';
53
+ for (let t = 0.05; t <= 1.001; t += 0.05) {
54
+ const c = misturar(cor, alvo, t);
55
+ if (contraste(c, fundo) >= min) return c;
56
+ }
57
+ return alvo;
58
+ }
59
+ /** Confere tinta×fundo e destaque×fundo. Devolve [{par, razao, ok, sugestao}]. */
60
+ function validarContraste(cores = {}, min = 4.5) {
61
+ const out = [];
62
+ for (const [par, k] of [['tinta×fundo', 'tinta'], ['destaque×fundo', 'destaque']]) {
63
+ if (!hexOk(cores[k]) || !hexOk(cores.fundo)) continue;
64
+ const razao = Math.round(contraste(cores[k], cores.fundo) * 100) / 100;
65
+ const ok = razao >= min;
66
+ out.push({ par, razao, ok, ...(ok ? {} : { sugestao: { [k]: ajustar(cores[k], cores.fundo, min) } }) });
67
+ }
68
+ return out;
69
+ }
70
+
71
+ // ── disco ────────────────────────────────────────────────────────────────
72
+ function pastaDe(nome) { const s = slug(nome); return s ? path.join(raiz(), s) : null; }
73
+ function ler(nome) {
74
+ const p = pastaDe(nome);
75
+ if (!p) return null;
76
+ try {
77
+ const m = JSON.parse(fs.readFileSync(path.join(p, 'marca.json'), 'utf8'));
78
+ const logo = ['logo.svg', 'logo.png'].map((f) => path.join(p, f)).find((f) => fs.existsSync(f));
79
+ return { ...m, ...(logo ? { logo } : {}), _pasta: p };
80
+ } catch { return null; }
81
+ }
82
+ function listar() {
83
+ try {
84
+ return fs.readdirSync(raiz(), { withFileTypes: true }).filter((d) => d.isDirectory())
85
+ .map((d) => ler(d.name)).filter(Boolean);
86
+ } catch { return []; }
87
+ }
88
+ function validar(m) {
89
+ const erros = [];
90
+ if (!m || !String(m.nome || '').trim()) erros.push('falta o nome.');
91
+ for (const k of PAPEIS) if (m.cores && m.cores[k] && !hexOk(m.cores[k])) erros.push(`cor ${k} inválida: ${m.cores[k]}`);
92
+ return erros;
93
+ }
94
+ /** Grava o kit. `logoArquivo` (opcional) é copiado para logo.png|svg. */
95
+ function salvar(m, { logoArquivo } = {}) {
96
+ const erros = validar(m);
97
+ if (erros.length) return { ok: false, error: erros.join(' ') };
98
+ const p = pastaDe(m.nome);
99
+ if (!p) return { ok: false, error: 'o nome não serve de pasta.' };
100
+ fs.mkdirSync(p, { recursive: true });
101
+ const { logo, _pasta, ...dados } = m;
102
+ fs.writeFileSync(path.join(p, 'marca.json'), JSON.stringify({ ...dados, criada: dados.criada || new Date().toISOString() }, null, 2) + '\n');
103
+ if (logoArquivo && fs.existsSync(logoArquivo)) {
104
+ const ext = /\.svg$/i.test(logoArquivo) ? '.svg' : '.png';
105
+ for (const e of ['.svg', '.png']) try { fs.unlinkSync(path.join(p, 'logo' + e)); } catch { /* não havia */ }
106
+ fs.copyFileSync(logoArquivo, path.join(p, 'logo' + ext));
107
+ }
108
+ return { ok: true, pasta: p, kit: ler(m.nome) };
109
+ }
110
+ function apagar(nome) {
111
+ const p = pastaDe(nome);
112
+ if (!p || !fs.existsSync(path.join(p, 'marca.json'))) return { ok: false, error: `não há kit "${nome}".` };
113
+ fs.rmSync(p, { recursive: true, force: true });
114
+ if (ativaGlobal() === slug(nome)) try { fs.unlinkSync(path.join(raiz(), '.ativa')); } catch { /* ok */ }
115
+ return { ok: true };
116
+ }
117
+
118
+ // ── ativa: pino do projeto ganha da global ────────────────────────────────
119
+ function ativaGlobal() { try { return fs.readFileSync(path.join(raiz(), '.ativa'), 'utf8').trim() || null; } catch { return null; } }
120
+ function usar(nome, { aqui = null } = {}) {
121
+ const k = ler(nome);
122
+ if (!k) return { ok: false, error: `não há kit "${nome}". Veja: primocode marca listar` };
123
+ if (aqui) {
124
+ fs.mkdirSync(path.join(aqui, '.primocode'), { recursive: true });
125
+ fs.writeFileSync(path.join(aqui, '.primocode', 'marca'), slug(nome) + '\n');
126
+ return { ok: true, kit: k, onde: 'projeto' };
127
+ }
128
+ fs.mkdirSync(raiz(), { recursive: true });
129
+ fs.writeFileSync(path.join(raiz(), '.ativa'), slug(nome) + '\n');
130
+ return { ok: true, kit: k, onde: 'global' };
131
+ }
132
+ function fixadaEm(pasta) {
133
+ let d = pasta ? path.resolve(pasta) : null;
134
+ for (let i = 0; d && i < 6; i++) {
135
+ try { const n = fs.readFileSync(path.join(d, '.primocode', 'marca'), 'utf8').trim(); if (n) return n; } catch { /* sobe */ }
136
+ const pai = path.dirname(d);
137
+ if (pai === d) break;
138
+ d = pai;
139
+ }
140
+ return null;
141
+ }
142
+ /** O kit em vigor: pino do primeiro `pastas` que tiver, senão a global. */
143
+ function ativa(pastas = []) {
144
+ for (const p of [].concat(pastas).filter(Boolean)) {
145
+ const n = fixadaEm(p);
146
+ if (n) { const k = ler(n); if (k) return k; }
147
+ }
148
+ const g = ativaGlobal();
149
+ return g ? ler(g) : null;
150
+ }
151
+
152
+ /**
153
+ * O gancho do Estúdio: preenche a `marca` do JSON com o kit, campo a campo
154
+ * (o que a pessoa/roteiro já pôs ganha). Sem kit ativo, devolve a marca intacta.
155
+ */
156
+ function preencher(marca, { pastas = [] } = {}) {
157
+ const k = ativa(pastas);
158
+ if (!k) return marca;
159
+ const m = marca && typeof marca === 'object' ? marca : {};
160
+ const temCores = m.cores && (Array.isArray(m.cores) ? m.cores.length : Object.keys(m.cores).length);
161
+ const temFontes = m.fontes && (Array.isArray(m.fontes) ? m.fontes.length : Object.keys(m.fontes).length);
162
+ return {
163
+ ...m,
164
+ nome: m.nome || k.nome,
165
+ cores: temCores ? (Array.isArray(m.cores) ? m.cores : { ...(k.cores || {}), ...m.cores }) : { ...(k.cores || {}) },
166
+ fontes: temFontes ? (Array.isArray(m.fontes) ? m.fontes : { ...(k.fontes || {}), ...m.fontes }) : { ...(k.fontes || {}) },
167
+ ...(m.logo || k.logo ? { logo: m.logo || k.logo } : {}),
168
+ ...(k.slogan && !m.slogan ? { slogan: k.slogan } : {}),
169
+ ...(k.tom && !m.tom ? { tom: k.tom } : {}),
170
+ ...(k.assinatura && !m.assinatura ? { assinatura: k.assinatura } : {}),
171
+ kit: k.nome,
172
+ };
173
+ }
174
+
175
+ // ── do site: logo e cores com o que já existe ────────────────────────────
176
+ async function doSite(entrada, { identidade = require('./estudio/identidade.js') } = {}) {
177
+ let url = String(entrada || '').trim();
178
+ if (!url) return { ok: false, error: 'sem site.' };
179
+ if (!/^https?:\/\//i.test(url)) {
180
+ if (!/\./.test(url)) { const a = await identidade.acharSite(url); if (!a || !a.ok) return { ok: false, error: (a && a.error) || 'não achei o site.' }; url = a.url || a.site; }
181
+ else url = 'https://' + url;
182
+ }
183
+ const r = await identidade.analisar(url);
184
+ if (!r.ok) return r;
185
+ const lista = ((r.cores && r.cores.marca) || []).map((x) => x.cor || x).filter(hexOk);
186
+ const todas = lista.length ? lista : ((r.cores && r.cores.todas) || []).map((x) => x.cor || x).filter(hexOk);
187
+ const cores = require('./estudio/marca.js').marcaEmObjeto({ cores: todas }).cores || {};
188
+ const f = r.fontes || {};
189
+ const nomes = [...(f.doGoogleFonts || []), ...(f.declaradas || []).map((x) => x.fonte)];
190
+ const unicas = [...new Set(nomes)];
191
+ const fontes = { ...(unicas[0] ? { titulo: unicas[0] } : {}), ...(unicas[0] ? { texto: unicas[1] || unicas[0] } : {}) };
192
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'marca-'));
193
+ let logoArquivo = null;
194
+ try {
195
+ const l = await identidade.pegarLogo(r.html || '', r.url, { pasta: tmp, nome: 'kit' });
196
+ if (l && l.ok && l.src) logoArquivo = path.join(tmp, l.src);
197
+ } catch { /* sem logo: a pessoa manda depois */ }
198
+ return { ok: true, url: r.url, nome: r.marca || '', slogan: r.descricao || '', cores, fontes, logoArquivo };
199
+ }
200
+
201
+ // ── criar (interativo curto) ─────────────────────────────────────────────
202
+ /**
203
+ * `perguntar(texto, padrao) → Promise<string>` e `dizer(texto)` são injetáveis (teste).
204
+ * Fluxo: nome → site (opcional) → confirma cores/fonte/logo → contraste → grava.
205
+ */
206
+ async function criar({ perguntar, dizer = console.log, deps = {} } = {}) {
207
+ const nome = String(await perguntar('Nome da marca', '')).trim();
208
+ if (!nome) return { ok: false, error: 'sem nome, sem kit.' };
209
+ const site = String(await perguntar('Site (opcional, Enter pula)', '')).trim();
210
+ let achado = { cores: {}, fontes: {}, logoArquivo: null, slogan: '' };
211
+ if (site) {
212
+ dizer('Lendo o site...');
213
+ const r = await doSite(site, deps);
214
+ if (r.ok) { achado = r; dizer(`Achei: ${Object.entries(r.cores).map(([k, v]) => `${k} ${v}`).join(' · ') || 'sem cores'}${r.fontes.titulo ? ` · fonte ${r.fontes.titulo}` : ''}${r.logoArquivo ? ' · logo' : ' · sem logo'}`); }
215
+ else dizer(`Não deu pra ler o site (${r.error}). Sigo com o que você disser.`);
216
+ }
217
+ const cores = {};
218
+ for (const k of PAPEIS) {
219
+ const padrao = achado.cores[k] || (k === 'fundo' ? '#0b0b0f' : k === 'tinta' ? '#f5f5f5' : k === 'destaque' ? '#ff5a1f' : '');
220
+ const v = normHex(await perguntar(`Cor ${k}${k === 'apoio' ? ' (opcional)' : ''}`, padrao));
221
+ if (v) cores[k] = v;
222
+ }
223
+ let sugeriu = [];
224
+ for (let volta = 0; volta < 2; volta++) {
225
+ const ruins = validarContraste(cores).filter((x) => !x.ok);
226
+ if (!ruins.length) break;
227
+ for (const x of ruins) {
228
+ const [k, v] = Object.entries(x.sugestao)[0];
229
+ dizer(`Contraste baixo em ${x.par}: ${x.razao} (o mínimo é 4,5). Sugiro ${k} ${v}.`);
230
+ const ok = /^(s|sim|y|yes|)$/i.test(String(await perguntar(`Trocar ${k} por ${v}? (s/n)`, 's')).trim());
231
+ if (ok) { cores[k] = v; sugeriu.push(k); }
232
+ }
233
+ }
234
+ const titulo = String(await perguntar('Fonte do título (Google Fonts)', achado.fontes.titulo || '')).trim();
235
+ const texto = String(await perguntar('Fonte do texto (Google Fonts)', achado.fontes.texto || titulo)).trim();
236
+ const slogan = String(await perguntar('Slogan (opcional)', '')).trim();
237
+ const tom = String(await perguntar('Tom de voz (opcional, ex.: direto e simpático)', '')).trim();
238
+ const redes = String(await perguntar('@redes (opcional)', '')).trim();
239
+ let logoArquivo = achado.logoArquivo;
240
+ if (!logoArquivo) {
241
+ const l = String(await perguntar('Arquivo do logo (png/svg, opcional)', '')).trim().replace(/^~(?=\/)/, os.homedir());
242
+ if (l && fs.existsSync(l)) logoArquivo = l; else if (l) dizer('Não achei esse arquivo; fica sem logo.');
243
+ }
244
+ const r = salvar({
245
+ nome, cores,
246
+ ...((titulo || texto) ? { fontes: { ...(titulo ? { titulo } : {}), ...(texto ? { texto } : {}) } } : {}),
247
+ ...(slogan ? { slogan } : {}), ...(tom ? { tom } : {}), ...(site ? { site } : {}), ...(redes ? { redes: redes.split(/[\s,]+/).filter(Boolean) } : {}),
248
+ }, { logoArquivo });
249
+ if (r.ok) r.contraste = validarContraste(cores);
250
+ return r;
251
+ }
252
+
253
+ // ── texto e CLI ──────────────────────────────────────────────────────────
254
+ function mostrar(k) {
255
+ if (!k) return 'Nenhum kit ativo. Crie um: primocode marca criar';
256
+ const c = k.cores || {}, f = k.fontes || {};
257
+ const lin = [`Marca: ${k.nome}`,
258
+ ` cores ${PAPEIS.filter((p) => c[p]).map((p) => `${p} ${c[p]}`).join(' · ') || '-'}`,
259
+ ` fontes ${f.titulo || '-'} / ${f.texto || '-'}`,
260
+ ` logo ${k.logo || '-'}`];
261
+ for (const [r, v] of [['slogan', k.slogan], ['tom', k.tom], ['site', k.site], ['redes', (k.redes || []).join(' ')], ['motion', k.assinatura]]) if (v) lin.push(` ${r.padEnd(7)} ${v}`);
262
+ const ruins = validarContraste(c).filter((x) => !x.ok);
263
+ for (const x of ruins) lin.push(` ! contraste ${x.par} ${x.razao} (< 4,5)`);
264
+ return lin.join('\n');
265
+ }
266
+ function tela(lista, ativaNome) {
267
+ return lista.length ? lista.map((k) => `${slug(k.nome) === ativaNome ? '●' : ' '} ${k.nome}${k.site ? ' ' + k.site : ''}`).join('\n') : 'Nenhum kit ainda. Crie um: primocode marca criar';
268
+ }
269
+
270
+ function perguntarNoTerminal() {
271
+ const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
272
+ const ask = (t, p) => new Promise((res) => rl.question(`${t}${p ? ` [${p}]` : ''}: `, (a) => res(a.trim() || p || '')));
273
+ return { ask, fechar: () => rl.close() };
274
+ }
275
+
276
+ /** `primocode marca <op>`; o /marca da sessão chama o mesmo, com `perguntar` próprio. */
277
+ async function main(argv, { perguntar, dizer = console.log, pasta = process.cwd(), deps } = {}) {
278
+ const [op = 'mostrar', ...resto] = argv;
279
+ const nome = resto.filter((x) => x !== 'aqui').join(' ').trim();
280
+ const aqui = resto.includes('aqui');
281
+ switch (op) {
282
+ case 'criar': {
283
+ const t = perguntar ? null : perguntarNoTerminal();
284
+ try {
285
+ const r = await criar({ perguntar: perguntar || t.ask, dizer, deps });
286
+ if (!r.ok) { dizer(r.error); return 1; }
287
+ dizer(`Kit "${r.kit.nome}" salvo em ${r.pasta}. Ativar: primocode marca usar ${r.kit.nome}`);
288
+ return 0;
289
+ } finally { if (t) t.fechar(); }
290
+ }
291
+ case 'listar': case 'ls': dizer(tela(listar(), ativaGlobal())); return 0;
292
+ case 'usar': {
293
+ if (!nome) { dizer('Qual? primocode marca usar <nome> [aqui]'); return 1; }
294
+ const r = usar(nome, { aqui: aqui ? pasta : null });
295
+ dizer(r.ok ? `Marca "${r.kit.nome}" ${r.onde === 'projeto' ? 'fixada nesta pasta' : 'ativa'}.` : r.error);
296
+ return r.ok ? 0 : 1;
297
+ }
298
+ case 'mostrar': case 'ver': dizer(mostrar(nome ? ler(nome) : ativa([pasta]))); return 0;
299
+ case 'apagar': case 'rm': {
300
+ if (!nome) { dizer('Qual? primocode marca apagar <nome>'); return 1; }
301
+ const r = apagar(nome);
302
+ dizer(r.ok ? `Kit "${nome}" apagado.` : r.error);
303
+ return r.ok ? 0 : 1;
304
+ }
305
+ default: dizer('Uso: primocode marca criar | listar | usar <nome> [aqui] | mostrar [nome] | apagar <nome>'); return 1;
306
+ }
307
+ }
308
+
309
+ module.exports = { raiz, slug, normHex, contraste, ajustar, validarContraste, ler, listar, salvar, apagar, usar, ativa, fixadaEm, preencher, doSite, criar, mostrar, main };