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.
- package/README.md +66 -12
- package/bin/primocode.js +148 -8
- package/lib/act.js +56 -3
- package/lib/catalogo.js +12 -1
- package/lib/config.js +1 -0
- package/lib/estudio/cota.js +157 -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/editor.js +290 -0
- package/lib/estudio/effectcraft.js +152 -0
- package/lib/estudio/exportar.js +181 -0
- package/lib/estudio/index.js +97 -2
- 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/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 +72 -0
- package/lib/headless.js +71 -0
- package/lib/hooks.js +72 -0
- package/lib/limites.js +100 -0
- package/lib/local/cerebro.json +398 -12
- package/lib/local/entrada.js +46 -2
- package/lib/marca-kit.js +312 -0
- package/lib/mcp.js +286 -6
- package/lib/perigo.js +31 -3
- package/lib/planos.js +49 -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/package.json +4 -2
- package/skills/after-effects/SKILL.md +52 -0
- package/skills/maquina-de-motion/SKILL.md +84 -0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.14.1",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
@@ -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 && node test/landing-planos.test.js && node test/limites.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
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
nome: Máquina de motion
|
|
3
|
+
descricao: o assunto vira um vídeo de motion profissional, com cenas, sons no tempo e áudio a -14 LUFS, sem você escrever JSX
|
|
4
|
+
quando: motion, reel, reels, anúncio animado, anuncio animado, vinheta, motion design, after effects, vídeo de motion, video de motion
|
|
5
|
+
autor: PrimoCode
|
|
6
|
+
---
|
|
7
|
+
Para vídeo de motion, **prefira `estudio_motion`** a escrever JSX com `estudio_ae`. Você
|
|
8
|
+
preenche um JSON; o kit faz o trabalho fino (curva em toda chave, mola com quique, câmera que
|
|
9
|
+
nunca para, botão que afunda, palavra-chave em itálico) e põe os sons no tempo.
|
|
10
|
+
|
|
11
|
+
## O caminho
|
|
12
|
+
|
|
13
|
+
1. **Procure um método.** `estudio_midia acao:"projetos"` lista as receitas do Clube do Motion.
|
|
14
|
+
Ache a mais próxima do pedido, leia a ficha (formato, duração, o que ela decide) e passe o
|
|
15
|
+
`id` dela em `receita`: a máquina segue o formato e a duração da ficha.
|
|
16
|
+
2. `estudio_projeto` (criar), se ainda não há projeto.
|
|
17
|
+
3. **Escreva as cenas** (o JSON abaixo). Uma ideia por cena.
|
|
18
|
+
4. `estudio_motion` e **abra os quadros** (`ae/quadro-*.png`). Texto cortado, tela vazia ou
|
|
19
|
+
cor sem contraste: mude o JSON e rode de novo. A máquina já confere um quadro por cena
|
|
20
|
+
e tenta uma vez sozinha, mas olhe você também.
|
|
21
|
+
|
|
22
|
+
## O JSON
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"id": "meu-projeto", "assunto": "Pão quente às 7h na Padaria do Joel",
|
|
27
|
+
"formato": "9:16", "duracao": 15,
|
|
28
|
+
"marca": { "cores": { "fundo": "#FFF1DC", "destaque": "#E8590C", "tinta": "#3B1F0E" },
|
|
29
|
+
"fontes": { "titulo": "Poppins", "texto": "Plus Jakarta Sans", "destaque": "Playfair Display" } },
|
|
30
|
+
"cenas": [
|
|
31
|
+
{ "tipo": "gancho", "titulo": "Pão *quente* às 7h", "sub": "Padaria do Joel" },
|
|
32
|
+
{ "tipo": "celular", "titulo": "Peça em *10 segundos*", "toques": ["Pão francês", "Café", "Confirmar"] },
|
|
33
|
+
{ "tipo": "numero", "valor": "1.200", "rotulo": "pães assados por semana" },
|
|
34
|
+
{ "tipo": "lista", "titulo": "Todo dia fresquinho", "itens": ["Pão francês", "Brioche", "Café coado"] },
|
|
35
|
+
{ "tipo": "cta", "titulo": "Venha *hoje*", "botao": "Peça agora" }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Você não precisa escolher o tipo da cena.** Mande `papel` (gancho · problema · processo · prova · virada · cta) e o
|
|
41
|
+
conteúdo (`titulo`, `itens`, `valor`, `rotulo`, `antes`, `depois`, `texto`, `autor`, `botao`, `fala`): o diretor escolhe a
|
|
42
|
+
primitiva pela metáfora do assunto, sorteia com uma semente por vídeo e foge das assinaturas dos últimos vídeos
|
|
43
|
+
(`~/.primocode/studio/assinaturas.json`). Se quiser mandar, mande: `tipo`, `transicoes`, `camera`, `tipografia`, `metafora`, `semente`.
|
|
44
|
+
|
|
45
|
+
- **38 primitivas** (`tipo`): gancho · cinetico · particulas · odometro · citacao · cta · carimbo · revela · heroi · explosao ·
|
|
46
|
+
icones · leque · parede_logos · celular · mergulho (a tela vira a próxima cena) · notificacoes · chat · interface (recriada
|
|
47
|
+
em camadas) · cursor · prompt · rolagem · numero · barras · linha · medidor · calendario · funil · relogio · lista ·
|
|
48
|
+
cards_pulam · laco · linha_tempo · camera_nulls · mapa · antes_depois · split · ondas · estrelas.
|
|
49
|
+
- **15 transições:** encolhe · empurra · mascara · zoom · flash · persiana · chicote · desfoque · cortina · pontos · giro ·
|
|
50
|
+
tremor · abre · dissolve · corte.
|
|
51
|
+
- **8 câmeras:** deriva · pulso (na batida) · mao · trilho · orbita · push · chicote · cinema.
|
|
52
|
+
- **8 tipografias:** editorial · poster · tecnica · manuscrita · pesada · serifa_fina · arredondada · geometrica.
|
|
53
|
+
- **Elementos reais:** `elementos: ["pão","relógio"]` (ícones 3D, GIF animado do Noto, `logos`, `site`); o diretor completa
|
|
54
|
+
até 3 ou mais. Tudo no vídeo tem a ver com o assunto.
|
|
55
|
+
- **Narração que manda:** `voz: {}` e uma `fala` por cena. O whisper mede cada palavra; nada aparece antes de ser dito;
|
|
56
|
+
legenda por palavra com a palavra-chave em destaque.
|
|
57
|
+
- **Som real:** efeitos CC0 por ação e assunto (cache em `~/.primocode/studio/sfx/`), cortes na batida da trilha, trilha 15 dB
|
|
58
|
+
abaixo da voz com ducking, final em −14 LUFS. Sem rede, cai nos sons leves.
|
|
59
|
+
- **Layout que não quebra:** o texto é medido e encaixado na área segura; depois do render o Node confere quadro vazio,
|
|
60
|
+
texto fora, sobreposição e contraste, e refaz até 2 vezes. A folha de contato fica em `ae/folha-de-contato.png`.
|
|
61
|
+
|
|
62
|
+
## A régua
|
|
63
|
+
|
|
64
|
+
- **A palavra-chave vai entre `*asteriscos*`**: sai em itálico, na cor de destaque, com sublinhado.
|
|
65
|
+
Uma por título.
|
|
66
|
+
- **Ritmo:** de 1 a 2,5 s por cena (`dur`); o celular pode passar um pouco. Para 15 s, 6 cenas ou mais;
|
|
67
|
+
para 20 s, 8. Não repita o mesmo tipo duas vezes seguidas.
|
|
68
|
+
- **Texto curto:** título de 2 a 6 palavras; item de lista com até 4. O kit quebra a linha e encolhe o
|
|
69
|
+
corpo, mas frase longa vira parede.
|
|
70
|
+
- **Cor:** sem `marca`, a paleta sai do assunto (forno, rosé, grafite com lima, floresta, petróleo, coral);
|
|
71
|
+
nunca roxo-azul genérico. Com `marca`, o kit só garante o contraste.
|
|
72
|
+
- **Fonte:** qualquer uma do Google Fonts; o destaque funciona melhor numa serifada itálica.
|
|
73
|
+
- **Som:** whoosh na troca, pop no estalo, clique no toque, subida no número. Nada para escrever.
|
|
74
|
+
- **Voz e trilha são opcionais:** `voz` (o texto a narrar) e `trilha` ("upbeat lofi"): a trilha fica
|
|
75
|
+
15 dB abaixo da voz. O vídeo todo sai a -14 LUFS.
|
|
76
|
+
|
|
77
|
+
Sem `cenas`, a máquina monta o roteiro padrão (gancho, problema, processo, prova, cta) a partir
|
|
78
|
+
do assunto: serve de rascunho, mas um roteiro escrito por você sempre ganha.
|
|
79
|
+
|
|
80
|
+
## Se o JSX for inevitável
|
|
81
|
+
|
|
82
|
+
Um efeito que os 8 tipos não cobrem? Aí sim `estudio_ae`, leia a skill `after-effects` e
|
|
83
|
+
copie o MÉTODO dos scripts do Clube. O kit (`lib/estudio/kit-ae/base.jsx`) pode ser
|
|
84
|
+
concatenado antes do seu script: `novaComp`, `texto`, `forma`, `mola`, `curva`, `camera`, `troca`, `som`.
|