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.
- package/README.md +74 -12
- package/bin/primocode.js +148 -8
- package/lib/act.js +48 -1
- package/lib/catalogo.js +12 -1
- package/lib/config.js +1 -0
- package/lib/estudio/assinaturas/gerativo.json +1 -1
- package/lib/estudio/camadas.js +4 -0
- package/lib/estudio/cota.js +150 -0
- package/lib/estudio/dados/projetos/clube-ad5.json +105 -0
- package/lib/estudio/dados/projetos/clube-ae-sem-memoria.json +107 -0
- package/lib/estudio/dados/projetos/clube-agent.json +120 -0
- package/lib/estudio/dados/projetos/clube-antes-e-agora.json +86 -0
- package/lib/estudio/dados/projetos/clube-apresentando-clube.json +110 -0
- package/lib/estudio/dados/projetos/clube-bluevisa.json +106 -0
- package/lib/estudio/dados/projetos/clube-caught-in-the-fire.json +127 -0
- package/lib/estudio/dados/projetos/clube-chat-cliente-satisfeito.json +98 -0
- package/lib/estudio/dados/projetos/clube-chatgpt-anuncios.json +120 -0
- package/lib/estudio/dados/projetos/clube-claude-dj.json +140 -0
- package/lib/estudio/dados/projetos/clube-claude-mcp.json +142 -0
- package/lib/estudio/dados/projetos/clube-cleanui.json +106 -0
- package/lib/estudio/dados/projetos/clube-clinica-take-unico.json +128 -0
- package/lib/estudio/dados/projetos/clube-clinica.json +104 -0
- package/lib/estudio/dados/projetos/clube-clube-opener.json +125 -0
- package/lib/estudio/dados/projetos/clube-clube.json +108 -0
- package/lib/estudio/dados/projetos/clube-codigo.json +101 -0
- package/lib/estudio/dados/projetos/clube-distilbook.json +104 -0
- package/lib/estudio/dados/projetos/clube-drive-upload.json +115 -0
- package/lib/estudio/dados/projetos/clube-dub.json +119 -0
- package/lib/estudio/dados/projetos/clube-enxovaly.json +105 -0
- package/lib/estudio/dados/projetos/clube-fecha7.json +118 -0
- package/lib/estudio/dados/projetos/clube-filatov.json +128 -0
- package/lib/estudio/dados/projetos/clube-frame.json +108 -0
- package/lib/estudio/dados/projetos/clube-glass.json +118 -0
- package/lib/estudio/dados/projetos/clube-letra-em-apps.json +84 -0
- package/lib/estudio/dados/projetos/clube-letra-globo-3d.json +111 -0
- package/lib/estudio/dados/projetos/clube-letra-nos-apps.json +146 -0
- package/lib/estudio/dados/projetos/clube-letra-seis-mundos.json +100 -0
- package/lib/estudio/dados/projetos/clube-lula-3.json +146 -0
- package/lib/estudio/dados/projetos/clube-morph.json +119 -0
- package/lib/estudio/dados/projetos/clube-motion-designer.json +115 -0
- package/lib/estudio/dados/projetos/clube-motion-pacote.json +112 -0
- package/lib/estudio/dados/projetos/clube-movimento.json +103 -0
- package/lib/estudio/dados/projetos/clube-not-like-us.json +117 -0
- package/lib/estudio/dados/projetos/clube-pinterest-inspiracao.json +86 -0
- package/lib/estudio/dados/projetos/clube-pixel-e-tinta.json +108 -0
- package/lib/estudio/dados/projetos/clube-ponte.json +118 -0
- package/lib/estudio/dados/projetos/clube-processo-edit-tonal.json +126 -0
- package/lib/estudio/dados/projetos/clube-processo-motion-100-ia.json +109 -0
- package/lib/estudio/dados/projetos/clube-promo.json +114 -0
- package/lib/estudio/dados/projetos/clube-proposito.json +115 -0
- package/lib/estudio/dados/projetos/clube-rankhog.json +128 -0
- package/lib/estudio/dados/projetos/clube-recursao.json +116 -0
- package/lib/estudio/dados/projetos/clube-revelar.json +118 -0
- package/lib/estudio/dados/projetos/clube-robo.json +125 -0
- package/lib/estudio/dados/projetos/clube-shelby-ep2.json +144 -0
- package/lib/estudio/dados/projetos/clube-shelby.json +97 -0
- package/lib/estudio/dados/projetos/clube-solar-eclipse.json +96 -0
- package/lib/estudio/dados/projetos/clube-soldier.json +128 -0
- package/lib/estudio/dados/projetos/clube-som-de-cena.json +108 -0
- package/lib/estudio/dados/projetos/clube-spotify-candidatura.json +112 -0
- package/lib/estudio/dados/projetos/clube-tda-app.json +118 -0
- package/lib/estudio/dados/projetos/clube-tda-ia.json +75 -0
- package/lib/estudio/dados/projetos/clube-telegram-chat.json +118 -0
- package/lib/estudio/dados/projetos/clube-telegram-ilha.json +106 -0
- package/lib/estudio/dados/projetos/clube-tennessee-nike.json +132 -0
- package/lib/estudio/dados/projetos/clube-tudo-se-move.json +112 -0
- package/lib/estudio/dados/projetos/clube-uiad.json +107 -0
- package/lib/estudio/dados/projetos/clube-viralgrow.json +108 -0
- package/lib/estudio/dados/projetos/clube-whose-side.json +140 -0
- package/lib/estudio/dados/projetos/clube-youtube-inscrever.json +108 -0
- package/lib/estudio/dados/projetos/elementos-do-site-em-movimento.json +49 -0
- package/lib/estudio/dados/projetos/explainer-narrado.json +51 -0
- package/lib/estudio/dados/projetos/lancamento-de-produto.json +51 -0
- package/lib/estudio/dados/projetos/motion-so-com-sons.json +50 -0
- package/lib/estudio/dados/projetos/titulo-em-after-effects.json +51 -0
- package/lib/estudio/dados/projetos/tour-de-saas.json +50 -0
- package/lib/estudio/dados/projetos-ae/antes-e-agora/ae/cenas.jsx +733 -0
- package/lib/estudio/dados/projetos-ae/antes-e-agora/assets/fazer_assets.py +88 -0
- package/lib/estudio/dados/projetos-ae/letra-em-apps/ae/cenas.jsx +402 -0
- package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/preparar_assets.py +17 -0
- package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/svg_para_ae.py +25 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/BRIEF-AGENTES.md +31 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/README.md +53 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ROTEIRO-CENAS.md +69 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/A-agenda.jsx +148 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/B-docs.jsx +174 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/C-alttab.jsx +113 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/D-spotify.jsx +118 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/E-notas.jsx +146 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/F-jornal1.jsx +144 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/G-google.jsx +153 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/H-mapa.jsx +176 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/I-imagens.jsx +132 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/J-jornal2.jsx +191 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/K-maps.jsx +184 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera-movimentos.jsx +48 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera.jsx +57 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/card-spotify.jsx +98 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/digitar.jsx +38 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/jornal-comum.jsx +196 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra.jsx +23 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra_automatica.py +83 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/montagem.jsx +74 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/render_html.py +13 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/util.jsx +162 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-midia-comum.jsx +114 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/wg-util.jsx +360 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/F-jornal1/render_html.py +13 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/render_w-midia.py +26 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/inclin.py +30 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_d.py +36 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_e.py +27 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ajustar_movimentos.py +61 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ancorar.py +74 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/assar.py +47 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/camera3d_jornal.py +172 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/comparar.py +32 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/dados_ae_jornal.py +46 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/folha_contato.py +28 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/homografia_jornal.py +74 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/integrar.py +44 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/medir_quadro_a_quadro.py +32 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/mesa_2d.py +64 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/plot_cenas.py +11 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/retificar_quadros.py +38 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/assar_ab.py +52 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/medir_ab.py +59 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ajustar_h.py +111 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_corrige_cam.py +17 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ecc_cadeia.py +80 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_finalizar_cam.py +65 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_mosaico.py +61 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/01-gramatica-e-roteiro.md +55 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/02-camera.md +66 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/03-montagem-no-ae.md +50 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/04-render-e-conferencia.md +43 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/05-abertura-celular-mergulho.md +43 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces.md +42 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/atraso_imagem.py +31 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/fundo.py +56 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/juntar.py +45 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/montar.py +177 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/rastreio.py +74 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/sincronia.py +18 -0
- package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/ae/cenas.jsx +530 -0
- package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/assets/fazer_assets.py +107 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/assets/pessoas/figurinha.py +46 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/BRIEF-A.md +83 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/audio.py +179 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/confere.py +20 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte1.js +1 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte2.js +1 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/gerar_emojis.py +11 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/img/filmes/_indice.js +122 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/base.js +380 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/main.js +86 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1a.js +169 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1b.js +455 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1c.js +389 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p2.js +379 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/video.py +62 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/README.md +65 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/oraculo_take.py +137 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/render_take.py +388 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/take.js +235 -0
- package/lib/estudio/diretor.js +2 -1
- package/lib/estudio/editor.js +285 -0
- package/lib/estudio/efeitos-fabrica/terminal-digitando.jsx +2 -1
- package/lib/estudio/effectcraft.js +152 -0
- package/lib/estudio/exportar.js +181 -0
- package/lib/estudio/gerativo.js +44 -15
- package/lib/estudio/index.js +126 -5
- package/lib/estudio/kit/gerativo.jsx +15 -0
- package/lib/estudio/kit-ae/base.jsx +1146 -0
- package/lib/estudio/kit-ae/cenas.jsx +395 -0
- package/lib/estudio/kit-ae/primitivas.jsx +911 -0
- package/lib/estudio/kokoro.js +3 -1
- package/lib/estudio/maquina-diretor.js +393 -0
- package/lib/estudio/maquina-elementos.js +134 -0
- package/lib/estudio/maquina-layout.js +198 -0
- package/lib/estudio/maquina-som.js +478 -0
- package/lib/estudio/maquina-trilha.js +160 -0
- package/lib/estudio/maquina-voz.js +429 -0
- package/lib/estudio/maquina.js +516 -0
- package/lib/estudio/projetos.js +60 -0
- package/lib/estudio/remotion.js +6 -4
- package/lib/estudio/revisor.js +61 -0
- package/lib/estudio/voicestudio.js +81 -0
- package/lib/headless.js +71 -0
- package/lib/hooks.js +72 -0
- package/lib/local/cerebro.json +398 -12
- package/lib/local/entrada.js +46 -2
- package/lib/marca-kit.js +309 -0
- package/lib/mcp.js +286 -6
- package/lib/perigo.js +31 -3
- package/lib/planos.js +47 -19
- package/lib/preparar.js +111 -0
- package/lib/retomar.js +55 -0
- package/lib/sandbox.js +197 -0
- package/lib/skills.js +3 -1
- package/lib/tools.js +13 -4
- package/lib/ui.js +1 -1
- package/lib/video/auto.js +18 -3
- package/package.json +4 -2
- package/skills/after-effects/SKILL.md +52 -0
- package/skills/maquina-de-motion/SKILL.md +84 -0
- package/skills/motion-de-primeira/SKILL.md +33 -51
package/lib/preparar.js
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
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
|
+
```
|