primocode 10.15.2 → 10.17.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/LICENSES/edit-ia-pro.txt +32 -0
- package/LICENSES/fontes-OFL.txt +476 -0
- package/LICENSES/hyperframes-Apache-2.0.txt +205 -0
- package/LICENSES/permanent-marker-Apache-2.0.txt +204 -0
- package/LICENSES/selfie-multiclass-Apache-2.0.txt +227 -0
- package/LICENSES/three-MIT.txt +23 -0
- package/LICENSES/video-use-MIT.txt +24 -0
- package/LICENSES/woven-sfx-CC0.txt +16 -0
- package/LICENSES/yunet-MIT.txt +21 -0
- package/README.md +2 -2
- package/bin/primocode.js +74 -9
- package/editor/LICENSE.md +11 -0
- package/editor/README.md +321 -0
- package/editor/SKILL.md +307 -0
- package/editor/THIRD_PARTY_NOTICES.md +228 -0
- package/editor/assets/baixar.json +591 -0
- package/editor/assets/fonts/anton/OFL.txt +93 -0
- package/editor/assets/fonts/caveat/OFL.txt +93 -0
- package/editor/assets/fonts/figtree/OFL.txt +93 -0
- package/editor/assets/fonts/fonts.json +10 -0
- package/editor/assets/fonts/montserrat/OFL.txt +93 -0
- package/editor/assets/fonts/permanentmarker/LICENSE.txt +202 -0
- package/editor/assets/models/SELFIE_MULTICLASS_LICENSE.txt +227 -0
- package/editor/assets/models/YUNET_LICENSE.txt +21 -0
- package/editor/assets/sfx/licencas/bass_drop.txt +32 -0
- package/editor/assets/sfx/licencas/cash.txt +76 -0
- package/editor/assets/sfx/licencas/charge.txt +31 -0
- package/editor/assets/sfx/licencas/click.txt +46 -0
- package/editor/assets/sfx/licencas/coin.txt +50 -0
- package/editor/assets/sfx/licencas/count.txt +47 -0
- package/editor/assets/sfx/licencas/data.txt +31 -0
- package/editor/assets/sfx/licencas/error.txt +32 -0
- package/editor/assets/sfx/licencas/flash.txt +31 -0
- package/editor/assets/sfx/licencas/gears.txt +82 -0
- package/editor/assets/sfx/licencas/keyboard.txt +48 -0
- package/editor/assets/sfx/licencas/metal_hit.txt +62 -0
- package/editor/assets/sfx/licencas/metal_riser.txt +61 -0
- package/editor/assets/sfx/licencas/mouse_click.txt +50 -0
- package/editor/assets/sfx/licencas/notification.txt +54 -0
- package/editor/assets/sfx/licencas/pop.txt +38 -0
- package/editor/assets/sfx/licencas/rewind.txt +32 -0
- package/editor/assets/sfx/licencas/rise.txt +46 -0
- package/editor/assets/sfx/licencas/sinteticos.txt +21 -0
- package/editor/assets/sfx/licencas/snap.txt +50 -0
- package/editor/assets/sfx/licencas/success.txt +40 -0
- package/editor/assets/sfx/licencas/synth_hit.txt +32 -0
- package/editor/assets/sfx/licencas/tick.txt +14 -0
- package/editor/assets/sfx/licencas/whoosh.txt +47 -0
- package/editor/assets/sfx/licencas/wind.txt +31 -0
- package/editor/assets/sfx/sfx.json +832 -0
- package/editor/assets/sfx/sfx_sinteticos.json +317 -0
- package/editor/assets/sfx/sfx_woven.json +387 -0
- package/editor/assets/sfx/woven/LICENCA.md +16 -0
- package/editor/fichas/briefing-do-video.md +45 -0
- package/editor/fichas/conferencia-da-amostra.md +56 -0
- package/editor/fichas/matriz-de-ganchos.csv +3 -0
- package/editor/fichas/padrao.json +16 -0
- package/editor/fichas/planilha-de-cenas.csv +4 -0
- package/editor/fichas/planilha-de-versoes.csv +4 -0
- package/editor/fichas/plano-em-blocos.md +32 -0
- package/editor/fichas/registro-de-materiais.json +27 -0
- package/editor/helpers/assets_ocr/ocr_vision.swift +41 -0
- package/editor/helpers/bordas_precisas.py +272 -0
- package/editor/helpers/camera_viva.py +426 -0
- package/editor/helpers/captions.py +601 -0
- package/editor/helpers/common.py +308 -0
- package/editor/helpers/compose_final.py +3555 -0
- package/editor/helpers/conferir_corte.py +234 -0
- package/editor/helpers/conferir_dados.py +405 -0
- package/editor/helpers/contact_sheet.py +282 -0
- package/editor/helpers/corte_por_onda.py +433 -0
- package/editor/helpers/corte_seco.py +595 -0
- package/editor/helpers/denoise.py +971 -0
- package/editor/helpers/efeitos.py +408 -0
- package/editor/helpers/efeitos_gerar.py +159 -0
- package/editor/helpers/elevenlabs_api.py +442 -0
- package/editor/helpers/face_layout.py +570 -0
- package/editor/helpers/fetch_images.py +423 -0
- package/editor/helpers/ganho_por_trecho.py +214 -0
- package/editor/helpers/gen_image.py +312 -0
- package/editor/helpers/gen_music.py +316 -0
- package/editor/helpers/gen_sfx.py +148 -0
- package/editor/helpers/grade.py +307 -0
- package/editor/helpers/icloud.py +119 -0
- package/editor/helpers/make_sfx.py +940 -0
- package/editor/helpers/matte.py +1342 -0
- package/editor/helpers/medir_referencia.py +2232 -0
- package/editor/helpers/mixar_trilha.py +354 -0
- package/editor/helpers/montar_corte.py +295 -0
- package/editor/helpers/motion/__init__.py +13 -0
- package/editor/helpers/motion/engine.py +1915 -0
- package/editor/helpers/motion/registry.py +510 -0
- package/editor/helpers/motion/render.py +665 -0
- package/editor/helpers/motion/templates/barra_progresso.py +370 -0
- package/editor/helpers/motion/templates/carimbo.py +132 -0
- package/editor/helpers/motion/templates/checklist.py +271 -0
- package/editor/helpers/motion/templates/comentario_cta.py +380 -0
- package/editor/helpers/motion/templates/contador.py +222 -0
- package/editor/helpers/motion/templates/destaque.py +263 -0
- package/editor/helpers/motion/templates/etiqueta_preco.py +461 -0
- package/editor/helpers/motion/templates/grafico_barras.py +510 -0
- package/editor/helpers/motion/templates/grafico_linha.py +462 -0
- package/editor/helpers/motion/templates/notificacao.py +266 -0
- package/editor/helpers/motion/templates/palavra_impacto.py +410 -0
- package/editor/helpers/motion/templates/passos.py +321 -0
- package/editor/helpers/motion/templates/riscado.py +298 -0
- package/editor/helpers/motion/templates/selo_garantia.py +150 -0
- package/editor/helpers/motion_render.py +241 -0
- package/editor/helpers/motion_sugerir.py +961 -0
- package/editor/helpers/noise_check.py +134 -0
- package/editor/helpers/pack_transcripts.py +223 -0
- package/editor/helpers/palavras_na_onda.py +176 -0
- package/editor/helpers/plano_sfx.py +839 -0
- package/editor/helpers/portao.py +427 -0
- package/editor/helpers/preview_server.py +1177 -0
- package/editor/helpers/rastrear_rosto.py +298 -0
- package/editor/helpers/render_cut.py +1256 -0
- package/editor/helpers/respiros.py +89 -0
- package/editor/helpers/sincronizar_cameras.py +571 -0
- package/editor/helpers/speech_regions.py +423 -0
- package/editor/helpers/style_catalog.json +231 -0
- package/editor/helpers/style_samples.py +246 -0
- package/editor/helpers/tela_verde.py +2113 -0
- package/editor/helpers/text_render.py +2059 -0
- package/editor/helpers/texto_na_tela.py +366 -0
- package/editor/helpers/timeline_view.py +292 -0
- package/editor/helpers/tira_de_quadros.py +113 -0
- package/editor/helpers/transcribe.py +734 -0
- package/editor/helpers/transcribe_batch.py +129 -0
- package/editor/helpers/trilhas.py +1146 -0
- package/editor/helpers/trilhas_humores.json +66 -0
- package/editor/helpers/valores.py +187 -0
- package/editor/helpers/verify_cut.py +820 -0
- package/editor/helpers/voice_levels.py +233 -0
- package/editor/helpers/voz_check.py +386 -0
- package/editor/helpers/voz_limpa.py +81 -0
- package/editor/helpers/watch_preview.py +372 -0
- package/editor/modelo-projeto/LEIA.md +402 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Caveat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-JetBrainsMono.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Montserrat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/larguras.json +1 -0
- package/editor/modelo-projeto/assets/three/BufferGeometryUtils.js +1438 -0
- package/editor/modelo-projeto/assets/three/GLTFLoader.js +4895 -0
- package/editor/modelo-projeto/assets/three/LICENSE +21 -0
- package/editor/modelo-projeto/assets/three/ORIGEM.txt +17 -0
- package/editor/modelo-projeto/assets/three/RoomEnvironment.js +185 -0
- package/editor/modelo-projeto/assets/three/three.core.min.js +6 -0
- package/editor/modelo-projeto/assets/three/three.module.min.js +6 -0
- package/editor/modelo-projeto/build.py +4828 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_antes_depois.html +199 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_arquivo.html +232 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_busca.html +315 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_comando.html +304 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_conversa.html +217 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_esquema.html +289 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_medidor.html +249 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_notas.html +233 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_caminho.html +197 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_explodida.html +152 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_tela.html +167 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_volume.html +124 -0
- package/editor/modelo-projeto/compositions/cenas/3d/motor-three.js +728 -0
- package/editor/modelo-projeto/compositions/cenas/LEIA.md +177 -0
- package/editor/modelo-projeto/ferramentas/medir_fontes.py +65 -0
- package/editor/modelo-projeto/ferramentas/pessoa_recortada.py +176 -0
- package/editor/modelo-projeto/ferramentas/rastrear_maos.py +189 -0
- package/editor/modelo-projeto/hyperframes.json +12 -0
- package/editor/modelo-projeto/package.json +11 -0
- package/editor/modelo-projeto/projeto.json +16 -0
- package/editor/preview/app.css +1009 -0
- package/editor/preview/app.js +2122 -0
- package/editor/preview/index.html +307 -0
- package/editor/pyproject.toml +23 -0
- package/editor/references/3d.md +236 -0
- package/editor/references/animacao-catalogo.md +672 -0
- package/editor/references/animacao.md +498 -0
- package/editor/references/comandos.md +92 -0
- package/editor/references/corte-seco-como-funciona.md +50 -0
- package/editor/references/corte-seco.md +88 -0
- package/editor/references/corte.md +290 -0
- package/editor/references/direcao-por-blocos.md +218 -0
- package/editor/references/enquadramento.md +654 -0
- package/editor/references/estilos-prontos.md +355 -0
- package/editor/references/estudio.md +100 -0
- package/editor/references/exemplo-eventos.json +67 -0
- package/editor/references/lettering.md +252 -0
- package/editor/references/modelos.md +494 -0
- package/editor/references/phase2.md +1007 -0
- package/editor/references/referencia-aplicar.md +203 -0
- package/editor/references/referencia-medir.md +375 -0
- package/editor/references/revisao.md +335 -0
- package/editor/references/som-e-trilha.md +146 -0
- package/editor/references/tabela-de-sons.md +130 -0
- package/editor/references/variacoes.md +234 -0
- package/editor/uv.lock +1141 -0
- package/lib/act.js +38 -8
- package/lib/app-chat.js +97 -0
- package/lib/catalogo.js +7 -1
- package/lib/chamada-torta.js +81 -0
- package/lib/editor-pro.js +786 -0
- package/lib/entrega.js +58 -0
- package/lib/estudio/conferencia-visual.js +76 -0
- package/lib/estudio/diretor.js +6 -1
- package/lib/estudio/exportar.js +3 -1
- package/lib/estudio/gosto/tela.html +1349 -0
- package/lib/estudio/gosto.js +305 -0
- package/lib/estudio/index.js +12 -1
- package/lib/estudio/kit-ae/base.jsx +119 -13
- package/lib/estudio/kit-ae/cenas.jsx +47 -15
- package/lib/estudio/kit-ae/guarda.jsx +274 -0
- package/lib/estudio/kit-ae/primitivas.jsx +180 -24
- package/lib/estudio/maquina-diretor.js +70 -12
- package/lib/estudio/maquina-foto.js +177 -0
- package/lib/estudio/maquina-guarda.js +207 -0
- package/lib/estudio/maquina-layout.js +22 -1
- package/lib/estudio/maquina.js +64 -11
- package/lib/estudio/referencias.js +1 -1
- package/lib/estudio/visao/visao.swift +97 -0
- package/lib/estudio/visao.js +60 -0
- package/lib/headless.js +15 -3
- package/lib/local/cerebro.json +285 -7
- package/lib/local/motor.js +5 -2
- package/lib/memoria-usuario.js +164 -0
- package/lib/nuvem.js +33 -2
- package/lib/passagem.js +50 -0
- package/lib/perigo.js +19 -11
- package/lib/protecao.js +134 -0
- package/lib/repeticao.js +1 -1
- package/lib/sandbox.js +57 -9
- package/lib/titulo-conversa.js +46 -0
- package/lib/tools.js +23 -4
- package/lib/ui.js +2 -2
- package/package.json +3 -2
- package/skills/editar-meu-video/SKILL.md +6 -2
- package/skills/editar-video/SKILL.md +113 -0
- package/studio/pessoa.py +52 -0
- package/voz/primo_voz/__main__.py +55 -40
- package/voz/primo_voz/app.py +500 -4
- package/voz/primo_voz/chat.html +1773 -0
- package/voz/primo_voz/conversa.py +0 -26
- package/voz/primo_voz/hud.html +8 -2
- package/voz/primo_voz/hud.py +129 -5
- package/voz/primo_voz/identidade.py +1 -1
- package/voz/primo_voz/painel_dados.py +134 -0
package/lib/sandbox.js
CHANGED
|
@@ -8,13 +8,18 @@
|
|
|
8
8
|
* hard_requirement (recusa)
|
|
9
9
|
*
|
|
10
10
|
* O que o comando pode: escrever na pasta do projeto, em /tmp e em
|
|
11
|
-
* ~/.primocode/cache; ler quase tudo, MENOS
|
|
12
|
-
*
|
|
11
|
+
* ~/.primocode/cache; ler quase tudo, MENOS os arquivos de credencial da
|
|
12
|
+
* LEITURA_NEGADA_HOME (chaves, tokens, a conta do PrimoCode, o Keychain) e
|
|
13
|
+
* .env*. Rede ligada (o `npm install` precisa), com opção de cortar.
|
|
14
|
+
* No macOS ainda fica de fora: osascript, o comando `security` (Keychain) e
|
|
15
|
+
* escrever hook em .git/hooks (rodaria FORA da cerca no próximo commit).
|
|
16
|
+
* O ssh-agent da pessoa fica fora por padrão (review do Arthur, #162): sem
|
|
17
|
+
* ele, "não lê ~/.ssh" vale de verdade, e não só para o arquivo.
|
|
13
18
|
* Quando bloqueia, a mensagem diz POR QUE, para o modelo aprender.
|
|
14
19
|
*
|
|
15
20
|
* Config em ~/.primocode/config.json:
|
|
16
21
|
* "sandbox": { "ligado": true, "modo": "best_effort"|"hard_requirement",
|
|
17
|
-
* "rede": true, "env_permitidas": ["MINHA_KEY"],
|
|
22
|
+
* "rede": true, "ssh_agent": false, "env_permitidas": ["MINHA_KEY"],
|
|
18
23
|
* "escrita_extra": ["~/.npm"] }
|
|
19
24
|
*/
|
|
20
25
|
'use strict';
|
|
@@ -25,10 +30,14 @@ const path = require('path');
|
|
|
25
30
|
const { execFile, spawnSync } = require('child_process');
|
|
26
31
|
|
|
27
32
|
const MODOS = ['best_effort', 'hard_requirement'];
|
|
28
|
-
const LEITURA_NEGADA_HOME = ['.ssh', '.aws', '.config/gcloud', '.npmrc'
|
|
33
|
+
const LEITURA_NEGADA_HOME = ['.ssh', '.aws', '.config/gcloud', '.npmrc',
|
|
34
|
+
'.primocode/conta.json', '.netrc', '.git-credentials', '.config/gh', '.docker', '.kube', 'Library/Keychains'];
|
|
35
|
+
// Socket do ssh-agent do macOS (launchd). O caminho exato vem do SSH_AUTH_SOCK; a regex pega os outros.
|
|
36
|
+
const SOCKET_SSH_MAC = '^/private/(tmp|var/run)/com\\.apple\\.launchd\\.[^/]+/Listeners$';
|
|
29
37
|
const SECRETA = /(_KEY|_TOKEN|SECRET)/i;
|
|
30
38
|
|
|
31
39
|
let _avisou = false;
|
|
40
|
+
let _avisouDesligado = false;
|
|
32
41
|
let _forcaMecanismo; // teste: undefined = detectar; null = ausente; 'seatbelt'|'bwrap'
|
|
33
42
|
|
|
34
43
|
function arquivoConfig(home) { return path.join(home || os.homedir(), '.primocode', 'config.json'); }
|
|
@@ -43,6 +52,7 @@ function config(home) {
|
|
|
43
52
|
ligado: typeof s.ligado === 'boolean' ? s.ligado : process.platform === 'darwin',
|
|
44
53
|
modo: MODOS.includes(s.modo) ? s.modo : 'best_effort',
|
|
45
54
|
rede: s.rede !== false,
|
|
55
|
+
ssh_agent: s.ssh_agent === true,
|
|
46
56
|
env_permitidas: Array.isArray(s.env_permitidas) ? s.env_permitidas.map(String) : [],
|
|
47
57
|
escrita_extra: Array.isArray(s.escrita_extra) ? s.escrita_extra.map(String) : [],
|
|
48
58
|
};
|
|
@@ -68,6 +78,17 @@ function mecanismo() {
|
|
|
68
78
|
return null;
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
/** Linha para a PESSOA ver no terminal (o stderr do comando só o modelo lê). Vai pro
|
|
82
|
+
* stderr do processo para não sujar a saída do `-p`. */
|
|
83
|
+
function avisarPessoa(msg) {
|
|
84
|
+
try { console.error(require('./ui.js').c.warn(` ⎿ ${msg}`)); } catch { /* sem tela */ }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function socketSsh() {
|
|
88
|
+
const s = process.env.SSH_AUTH_SOCK;
|
|
89
|
+
try { return s && fs.statSync(s).isSocket() ? real(s) : null; } catch { return null; }
|
|
90
|
+
}
|
|
91
|
+
|
|
71
92
|
function real(p) { try { return fs.realpathSync(p); } catch { return path.resolve(p); } }
|
|
72
93
|
function expandir(p, home) { return p.startsWith('~') ? path.join(home, p.slice(1)) : p; }
|
|
73
94
|
const sbq = (s) => '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
@@ -86,8 +107,20 @@ function perfilSeatbelt(projectDir, cfg = config(), home = os.homedir()) {
|
|
|
86
107
|
const l = ['(version 1)', '(allow default)', '(deny file-write*)'];
|
|
87
108
|
for (const p of escrita) l.push(`(allow file-write* (subpath ${sbq(p)}))`);
|
|
88
109
|
l.push('(allow file-write* (literal "/dev/null") (literal "/dev/tty") (literal "/dev/dtracehelper") (regex #"^/dev/fd/"))');
|
|
110
|
+
// Hook em .git/hooks roda fora da cerca no próximo `git commit` da pessoa. O .sample do `git init` passa.
|
|
111
|
+
l.push('(deny file-write* (regex #"/\\.git/hooks/[^/]+$"))');
|
|
112
|
+
l.push('(allow file-write* (regex #"/\\.git/hooks/[^/]+\\.sample$"))');
|
|
89
113
|
for (const p of negadosLeitura) l.push(`(deny file-read* (subpath ${sbq(real(p))}) (subpath ${sbq(p)}))`);
|
|
90
114
|
l.push('(deny file-read* (regex #"/\\.env($|\\.[^/]*$)"))');
|
|
115
|
+
// osascript manda o Terminal/Finder fazer o que a cerca não deixa; `security` lê o Keychain.
|
|
116
|
+
l.push('(deny process-exec (literal "/usr/bin/osascript") (literal "/usr/bin/security"))');
|
|
117
|
+
l.push('(deny appleevent-send)');
|
|
118
|
+
l.push('(deny mach-lookup (global-name "com.apple.SecurityServer") (global-name "com.apple.securityd"))');
|
|
119
|
+
if (!cfg.ssh_agent) {
|
|
120
|
+
const sock = socketSsh();
|
|
121
|
+
l.push(`(deny network-outbound (remote unix-socket (path-regex #"${SOCKET_SSH_MAC}")))`);
|
|
122
|
+
if (sock) l.push(`(deny network-outbound (remote unix-socket (path-literal ${sbq(sock)})))`);
|
|
123
|
+
}
|
|
91
124
|
if (!cfg.rede) l.push('(deny network*)');
|
|
92
125
|
return l.join('\n');
|
|
93
126
|
}
|
|
@@ -106,6 +139,11 @@ function argsBwrap(projectDir, cfg = config(), home = os.homedir()) {
|
|
|
106
139
|
if (/^\.env($|\.)/.test(f)) a.push('--ro-bind', '/dev/null', path.join(real(projectDir), f));
|
|
107
140
|
}
|
|
108
141
|
} catch { /* pasta sumiu */ }
|
|
142
|
+
// Montado DEPOIS do --bind do projeto, para valer por cima dele. [VERIFICAR] no Linux.
|
|
143
|
+
const hooks = path.join(real(projectDir), '.git', 'hooks');
|
|
144
|
+
if (fs.existsSync(hooks)) a.push('--ro-bind', hooks, hooks);
|
|
145
|
+
const sock = cfg.ssh_agent ? null : socketSsh();
|
|
146
|
+
if (sock) a.push('--ro-bind', '/dev/null', sock);
|
|
109
147
|
if (!cfg.rede) a.push('--unshare-net');
|
|
110
148
|
a.push('--die-with-parent', '--chdir', real(projectDir));
|
|
111
149
|
return a;
|
|
@@ -115,7 +153,16 @@ function argsBwrap(projectDir, cfg = config(), home = os.homedir()) {
|
|
|
115
153
|
function envolver(command, projectDir, opts = {}) {
|
|
116
154
|
const home = opts.home || os.homedir();
|
|
117
155
|
const cfg = opts.cfg || config(home);
|
|
118
|
-
if (!cfg.ligado)
|
|
156
|
+
if (!cfg.ligado) {
|
|
157
|
+
let aviso = null;
|
|
158
|
+
if (!_avisouDesligado) {
|
|
159
|
+
_avisouDesligado = true;
|
|
160
|
+
aviso = process.platform === 'darwin'
|
|
161
|
+
? 'aviso: o sandbox está desligado; os comandos do agente rodam sem cerca. /sandbox on liga.'
|
|
162
|
+
: 'aviso: o sandbox vem desligado fora do macOS; os comandos do agente rodam sem cerca. /sandbox on liga (no Linux precisa do bwrap).';
|
|
163
|
+
}
|
|
164
|
+
return { semCerca: true, motivo: 'desligado', aviso };
|
|
165
|
+
}
|
|
119
166
|
const mec = mecanismo();
|
|
120
167
|
if (!mec) {
|
|
121
168
|
if (cfg.modo === 'hard_requirement') {
|
|
@@ -132,14 +179,15 @@ function envolver(command, projectDir, opts = {}) {
|
|
|
132
179
|
/** Se o comando falhou por causa da cerca, diz por quê (e qual caminho). */
|
|
133
180
|
function explicar(stderr, projectDir, cfg, home) {
|
|
134
181
|
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)
|
|
182
|
+
const m = /(?:^|\n)(?:[^\n:]*:\s*)?(\/[^\n:]+?|\.{1,2}\/[^\n:]+?|[^\s:'"]+?):?\s*(?:(?:open|read|stat|lstat|opendir|write):\s*)?(?:Operation not permitted|Permission denied|Read-only file system)/.exec(txt)
|
|
136
183
|
|| /(?:Operation not permitted|Permission denied|Read-only file system)[^\n]*?['"]([^'"\n]+)['"]/.exec(txt);
|
|
137
184
|
if (!m) return null;
|
|
138
185
|
const alvo = path.resolve(projectDir, m[1].trim());
|
|
139
186
|
const { negadosLeitura } = caminhos(projectDir, cfg, home);
|
|
140
187
|
const secreto = negadosLeitura.some((p) => alvo === p || alvo.startsWith(p + path.sep) || alvo.startsWith(real(p) + path.sep))
|
|
141
188
|
|| /(^|\/)\.env($|\.)/.test(alvo);
|
|
142
|
-
if (secreto) return `o sandbox bloqueou leitura de segredo: ${alvo} (
|
|
189
|
+
if (secreto) return `o sandbox bloqueou leitura de segredo: ${alvo} (${LEITURA_NEGADA_HOME.map((p) => '~/' + p).join(', ')} e .env* ficam fora do alcance). Não tente ler isso; peça ao usuário o que precisa.`;
|
|
190
|
+
if (/\/\.git\/hooks\//.test(alvo)) return `o sandbox bloqueou escrita em hook do git: ${alvo}. Hook roda fora da cerca; se o usuário quiser um, ele instala.`;
|
|
143
191
|
if (/Read-only|Operation not permitted|Permission denied/.test(txt)) {
|
|
144
192
|
return `o sandbox bloqueou escrita fora do projeto: ${alvo}. Escreva só dentro de ${projectDir}, em /tmp ou em ~/.primocode/cache.`;
|
|
145
193
|
}
|
|
@@ -168,7 +216,7 @@ function executar(command, { projectDir, timeout = 60000, env = process.env, hom
|
|
|
168
216
|
const termina = (error, stdout, stderr) => {
|
|
169
217
|
let err = stderr || '';
|
|
170
218
|
const info = { mecanismo: e.mec || null };
|
|
171
|
-
if (e.aviso) err = e.aviso + '\n' + err;
|
|
219
|
+
if (e.aviso) { err = e.aviso + '\n' + err; avisarPessoa(e.aviso); }
|
|
172
220
|
if (error && e.mec) {
|
|
173
221
|
const why = explicar(err, cwd, cfg, e.home);
|
|
174
222
|
if (why) { err = err + '\n[sandbox] ' + why; info.bloqueou = why; }
|
|
@@ -191,7 +239,7 @@ function estado(home) {
|
|
|
191
239
|
|
|
192
240
|
function _teste({ mecanismo: m, avisou } = {}) {
|
|
193
241
|
if (m !== undefined) _forcaMecanismo = m === 'auto' ? undefined : m;
|
|
194
|
-
if (avisou !== undefined) _avisou = avisou;
|
|
242
|
+
if (avisou !== undefined) { _avisou = avisou; _avisouDesligado = avisou; }
|
|
195
243
|
}
|
|
196
244
|
|
|
197
245
|
module.exports = { config, salvar, estado, mecanismo, envolver, executar, explicar, filtrarEnv, perfilSeatbelt, argsBwrap, MODOS, _teste };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* titulo-conversa.js — quem dá título à conversa é quem leu a primeira troca.
|
|
3
|
+
*
|
|
4
|
+
* Antes o título era o começo da primeira mensagem ("Oi.", "E ele tem o…"): o
|
|
5
|
+
* gravador (lib/nuvem.js) usava `tituloDe`. Agora, depois da primeira resposta
|
|
6
|
+
* de uma conversa NOVA, o modelo escreve um título de 2 a 5 palavras e o
|
|
7
|
+
* gravador troca o do documento na nuvem. O app (Mac e desktop) lê esse título
|
|
8
|
+
* em `primocode conversas --json`, então a lateral muda sozinha.
|
|
9
|
+
*
|
|
10
|
+
* Se o modelo não responder a tempo, ou vier lixo, vale o título de antes:
|
|
11
|
+
* nenhum erro aparece. Puro, menos `escolher`, que recebe quem pergunta de fora.
|
|
12
|
+
*/
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const TEMPO_MAXIMO_MS = 8000;
|
|
16
|
+
|
|
17
|
+
/** A pergunta. Curta: vai numa chamada à parte. */
|
|
18
|
+
function pergunta(pedido, resposta) {
|
|
19
|
+
return 'Dê um título curto para esta conversa, de 2 a 5 palavras, em português do Brasil. '
|
|
20
|
+
+ 'O título diz o ASSUNTO (ex.: Site da padaria, Vídeo de lançamento, Erro no login), '
|
|
21
|
+
+ 'nunca "Conversa" nem "Oi". Sem aspas, sem ponto final e sem explicação. Responda SÓ com o título.\n\n'
|
|
22
|
+
+ `PESSOA: ${String(pedido || '').slice(0, 500)}\n\nPRIMO: ${String(resposta || '').slice(0, 500)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Limpa a resposta do modelo. Devolve null quando não dá pra confiar nela. */
|
|
26
|
+
function limpar(resposta) {
|
|
27
|
+
const linha = String(resposta || '').trim().split('\n').map((l) => l.trim()).find(Boolean) || '';
|
|
28
|
+
const t = linha.replace(/^(t[ií]tulo)\s*:\s*/i, '').replace(/^[`"'“”*#\s]+|[`"'“”*.\s]+$/g, '').replace(/\s+/g, ' ');
|
|
29
|
+
const palavras = t.split(' ').filter(Boolean);
|
|
30
|
+
if (!t || t.length > 60 || palavras.length < 1 || palavras.length > 7) return null;
|
|
31
|
+
if (/^(conversa|oi|ol[aá]|t[ií]tulo|sem t[ií]tulo)$/i.test(t)) return null;
|
|
32
|
+
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Pergunta ao modelo e devolve o título limpo, ou null (falhou, demorou ou veio lixo). Nunca rejeita. */
|
|
36
|
+
async function escolher({ pedido, resposta, perguntar, tempoMs = TEMPO_MAXIMO_MS }) {
|
|
37
|
+
try {
|
|
38
|
+
let timer;
|
|
39
|
+
const limite = new Promise((ok) => { timer = setTimeout(() => ok(null), tempoMs); });
|
|
40
|
+
const r = await Promise.race([Promise.resolve(perguntar(pergunta(pedido, resposta))), limite]);
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
return r == null ? null : limpar(r);
|
|
43
|
+
} catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { pergunta, limpar, escolher, TEMPO_MAXIMO_MS };
|
package/lib/tools.js
CHANGED
|
@@ -274,10 +274,13 @@ function grepFiles({ pattern, glob, ignoreCase, contexto = 0, maxResultados = 10
|
|
|
274
274
|
USER, NODE_*, nvm etc. continuam — sem eles `npm`, `node` e `git` quebram.
|
|
275
275
|
Na dúvida o nome sai: um token a mais escondido custa pouco, um a menos
|
|
276
276
|
vaza. Só o caminho do agente (execute → run_shell) é filtrado; os usos
|
|
277
|
-
internos (abrir terminal, desinstalar) seguem com o ambiente inteiro.
|
|
277
|
+
internos (abrir terminal, desinstalar) seguem com o ambiente inteiro.
|
|
278
|
+
O SSH_AUTH_SOCK também sai (review do Arthur, #162): com ele o agente usa o
|
|
279
|
+
ssh-agent da pessoa (`git push`, `ssh host`) sem ler ~/.ssh. Volta só com
|
|
280
|
+
`/sandbox ssh on`. */
|
|
278
281
|
const NOME_SECRETO = /(TOKEN|SECRET|KEY|PASSWORD|PASSWD|CREDENTIAL|AUTH|COOKIE|SESSION)/i;
|
|
279
282
|
const PREFIXO_SECRETO = /^(AWS_|OPENAI_|ANTHROPIC_|GEMINI_|GOOGLE_API|GROQ_|OPENROUTER_|AZURE_|HF_|HUGGING)/i;
|
|
280
|
-
const SEMPRE_PASSA = new Set(['PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'LANG', 'TERM', 'TMPDIR', 'PWD'
|
|
283
|
+
const SEMPRE_PASSA = new Set(['PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'LANG', 'TERM', 'TMPDIR', 'PWD']);
|
|
281
284
|
|
|
282
285
|
function ambienteLimpo(env = process.env, permitidas = []) {
|
|
283
286
|
const limpo = {};
|
|
@@ -289,6 +292,10 @@ function ambienteLimpo(env = process.env, permitidas = []) {
|
|
|
289
292
|
return limpo;
|
|
290
293
|
}
|
|
291
294
|
|
|
295
|
+
function permitidasNoShell(cfg = sandbox.config()) {
|
|
296
|
+
return cfg.ssh_agent ? [...cfg.env_permitidas, 'SSH_AUTH_SOCK'] : cfg.env_permitidas;
|
|
297
|
+
}
|
|
298
|
+
|
|
292
299
|
async function runShell(command, timeout = 60000, cwd, env) {
|
|
293
300
|
return new Promise((resolve) => {
|
|
294
301
|
exec(command, { timeout, cwd: cwd || process.cwd(), maxBuffer: 10 * 1024 * 1024, env: env || process.env }, (error, stdout, stderr) => {
|
|
@@ -668,7 +675,7 @@ async function despachar(name, args, ctx) {
|
|
|
668
675
|
case 'run_shell':
|
|
669
676
|
// Dentro da cerca (lib/sandbox.js); desligada ou sem mecanismo cai no exec de sempre.
|
|
670
677
|
return sandbox.executar(args.command, { projectDir, timeout: args.timeout_ms || 60000,
|
|
671
|
-
env: ambienteLimpo(process.env,
|
|
678
|
+
env: ambienteLimpo(process.env, permitidasNoShell()) });
|
|
672
679
|
case 'run_in_new_terminal':
|
|
673
680
|
return runInNewTerminal(args.command, args.cwd || projectDir);
|
|
674
681
|
case 'open_in_browser': {
|
|
@@ -712,6 +719,15 @@ async function despachar(name, args, ctx) {
|
|
|
712
719
|
return { ok: true, gravado: entry };
|
|
713
720
|
}
|
|
714
721
|
|
|
722
|
+
// A memória que dura entre sessões (lib/memoria-usuario.js).
|
|
723
|
+
case 'lembrar': {
|
|
724
|
+
const mem = require('./memoria-usuario.js');
|
|
725
|
+
const acao = String(args.acao || 'salvar').toLowerCase();
|
|
726
|
+
if (acao === 'apagar') return mem.apagar(args.nome);
|
|
727
|
+
if (acao === 'salvar') return mem.salvar({ nome: args.nome, descricao: args.descricao, tipo: args.tipo, texto: args.texto });
|
|
728
|
+
return { ok: false, error: 'Ação inválida. Use "salvar" ou "apagar".' };
|
|
729
|
+
}
|
|
730
|
+
|
|
715
731
|
// ── MCP: o serviço pela API dele, antes do mouse (lib/mcp.js) ──
|
|
716
732
|
case 'mcp_procurar':
|
|
717
733
|
return require('./mcp.js').procurar(args.busca || args.query || args.servico || '');
|
|
@@ -846,6 +862,9 @@ async function despachar(name, args, ctx) {
|
|
|
846
862
|
// A transcrição precisa do servidor (a chave é dele), então o
|
|
847
863
|
// contexto do pedido atravessa até aqui.
|
|
848
864
|
return estudio.ferramentaVideo(args, ctx);
|
|
865
|
+
case 'editor_pro':
|
|
866
|
+
// A edição completa de vídeo FALADO: o motor do EDIT IA PRO (lib/editor-pro.js, editor/).
|
|
867
|
+
return require('./editor-pro.js').ferramenta(args, ctx);
|
|
849
868
|
case 'estudio_render':
|
|
850
869
|
// A pasta de trabalho vai junto: a composição do Hyperframes que a IA escreveu mora nela.
|
|
851
870
|
return estudio.ferramentaRender({ ...args, __pasta: projectDir }, {
|
|
@@ -978,7 +997,7 @@ async function uninstall() {
|
|
|
978
997
|
}
|
|
979
998
|
|
|
980
999
|
module.exports = {
|
|
981
|
-
fmtBytes, treeDir, runShell, ambienteLimpo, openInBrowser, runInNewTerminal,
|
|
1000
|
+
fmtBytes, treeDir, runShell, ambienteLimpo, permitidasNoShell, openInBrowser, runInNewTerminal,
|
|
982
1001
|
execute, uninstall, resolveInProject, globToRegex, globFiles, grepFiles, editFile,
|
|
983
1002
|
multiEdit, writeTodos, getTodos,
|
|
984
1003
|
liberarLeitura, pastasDeConsulta, esquecerConsultas, resolverParaLer,
|
package/lib/ui.js
CHANGED
|
@@ -552,12 +552,12 @@ const ROTULOS = {
|
|
|
552
552
|
legítimo — e só a linha de baixo revelava que tinha falhado. */
|
|
553
553
|
planejar_producao:'Diretor', estudio_projeto:'Estúdio·projeto',
|
|
554
554
|
estudio_plano:'Estúdio·plano', estudio_narrar:'Estúdio·narração',
|
|
555
|
-
estudio_video:'Estúdio·vídeo', estudio_render:'Estúdio·render',
|
|
555
|
+
estudio_video:'Estúdio·vídeo', estudio_render:'Estúdio·render', editor_pro:'Editor·vídeo',
|
|
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
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
|
-
remember_general:'Lembrar',
|
|
560
|
+
remember_general:'Lembrar', lembrar:'Memória',
|
|
561
561
|
mcp_procurar:'MCP·procurar', mcp_conectar:'MCP·conectar', mcp_usar:'MCP',
|
|
562
562
|
};
|
|
563
563
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.17.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": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
11
|
"postinstall": "node lib/preparar.js || true",
|
|
12
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/primo-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"
|
|
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/primo-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 && node test/app-chat-cli.test.js && node test/app-chat-ponte.test.js && node test/gosto-motion.test.js && node test/desktop-vivo.test.js && node test/desktop-chat.test.js && node test/desktop-voz.test.js && node test/desktop-painel-dados.test.js && node test/titulo-conversa.test.js && node test/memoria-usuario.test.js && node test/landing-instalar.test.js && node test/foto-na-cena.test.js && node test/protecao.test.js && node test/entrega.test.js && node test/chamada-torta.test.js && node test/passagem.test.js && node test/editor-pro.test.js && node test/gosto-app.test.js && node test/guarda-layout.test.js && node test/acabamento.test.js"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=18.17.0"
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"lib/",
|
|
32
32
|
"studio/",
|
|
33
33
|
"skills/",
|
|
34
|
+
"editor/",
|
|
34
35
|
"voz/",
|
|
35
36
|
"LICENSES/",
|
|
36
37
|
"README.md",
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
nome: Editar o vídeo gravado
|
|
3
|
-
descricao:
|
|
4
|
-
quando:
|
|
3
|
+
descricao: operações soltas numa gravação (importar, cortar silêncio, recortar a pessoa) — a edição completa é a skill editar-video
|
|
4
|
+
quando: cortar silêncio, cortar silencio, recortar a pessoa, trocar o fundo, b-roll, broll, importar a gravação
|
|
5
5
|
autor: PrimoCode
|
|
6
6
|
---
|
|
7
|
+
A EDIÇÃO COMPLETA de um vídeo falado (corte, legenda animada, gancho, peças,
|
|
8
|
+
sons, trilha) é do `editor_pro` — carregue a skill `editar-video`. Esta aqui é
|
|
9
|
+
para as operações soltas no Estúdio.
|
|
10
|
+
|
|
7
11
|
O vídeo é da pessoa. Ela apareceu, falou e errou — o seu trabalho é tirar o
|
|
8
12
|
que atrapalha, não refazer o que ela fez.
|
|
9
13
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
nome: Editar vídeo falado (motor EDIT IA PRO)
|
|
3
|
+
descricao: a edição completa da gravação da pessoa falando — corte, voz, legenda, gancho, peças, b-roll, sons, trilha e portão
|
|
4
|
+
quando: edita meu vídeo, editar meu vídeo, edite esse vídeo, editar o vídeo, edição do vídeo, edicao do video, reels, tiktok, shorts, criativo, legenda e efeitos, legendas, gancho, cortar os respiros, tirar os erros, melhor tomada, vídeo falado, gravei, gravação, gravacao, vsl, anúncio
|
|
5
|
+
autor: PrimoCode (adaptada do EDIT IA PRO, SHK Soluções Digitais, com autorização)
|
|
6
|
+
---
|
|
7
|
+
Você é o editor e o diretor. O motor (ferramenta `editor_pro`, ou
|
|
8
|
+
`primocode editor <acao> --json '{...}'` pelo run_shell) mede e faz o
|
|
9
|
+
trabalho pesado; você decide o que aparece em cada momento e por quê.
|
|
10
|
+
|
|
11
|
+
## Como falar com a pessoa
|
|
12
|
+
|
|
13
|
+
- Português simples, frases curtas. Nada de JSON, comando ou caminho comprido
|
|
14
|
+
na conversa: "estou ouvindo as tomadas", "tirei os respiros".
|
|
15
|
+
- Tempos no formato 00:12. Antes de passo demorado (transcrição, render),
|
|
16
|
+
diga mais ou menos quanto leva.
|
|
17
|
+
- No máximo UMA pergunta, e só se não der para deduzir ("é anúncio, Reels ou
|
|
18
|
+
aula?"). O resto tem padrão; conte as suposições numa frase no fim.
|
|
19
|
+
|
|
20
|
+
## Princípios
|
|
21
|
+
|
|
22
|
+
1. **A fala gravada manda.** Tudo entra no tempo da palavra que chama (pode
|
|
23
|
+
antecipar até 0,15 s). Tempo vem da fala que o `cortar` devolve, nunca
|
|
24
|
+
de estimativa.
|
|
25
|
+
2. **Fidelidade.** Número, nome, prova e oferta só se a pessoa disse. Faltou
|
|
26
|
+
material: diga o que falta, não invente.
|
|
27
|
+
3. **Respiro é erro.** O corte já tira silêncio de 0,25 s ou mais e fica com a
|
|
28
|
+
última tomada inteira de cada frase.
|
|
29
|
+
4. **Uma ideia por vez na tela; rosto livre.** Nada sobre olhos e boca.
|
|
30
|
+
Alguma coisa muda a cada 2 a 4 s.
|
|
31
|
+
5. **Só entrega com o portão PASSOU.** Reprovou: conserte o plano e monte de
|
|
32
|
+
novo. Nunca mostre um vídeo reprovado.
|
|
33
|
+
|
|
34
|
+
## O caminho
|
|
35
|
+
|
|
36
|
+
1. `editor_pro acao:"cortar" video:"<arquivo>" prompt:"nomes, de, marca"`.
|
|
37
|
+
Na primeira vez ele baixa os arquivos do editor e cria o Python dele
|
|
38
|
+
(avise: alguns minutos). Volta `pasta`, `duracao`, `volume` e `fala`: as
|
|
39
|
+
frases do corte com o tempo (`3.42-5.10 texto`). O original nunca é tocado.
|
|
40
|
+
2. **Leia a fala e escreva o plano.** Divida por ideia e decida o que entra
|
|
41
|
+
em cada bloco (tabela "que peça para cada fala", abaixo).
|
|
42
|
+
3. `editor_pro acao:"montar" pasta:"<pasta>" plano:{...} trilha:"auto"`.
|
|
43
|
+
Ele cria o projeto, põe a câmera viva, os sons casados com cada animação,
|
|
44
|
+
renderiza, mixa a trilha com ducking e roda o portão. Volta `video`
|
|
45
|
+
(`<pasta>/final.mp4`), `quadros` (6 PNGs), `audio` e `portao`.
|
|
46
|
+
4. **Olhe os quadros** (read_file nos PNGs) antes de entregar. Algo feio
|
|
47
|
+
(texto cortado, peça no rosto, b-roll sem nada a ver): mude o plano e
|
|
48
|
+
monte de novo.
|
|
49
|
+
5. Entregue com uma nota curta: onde está o vídeo, o que foi feito, o que
|
|
50
|
+
faltou. Atalho sem plano: `acao:"editar"` (o plano automático põe um
|
|
51
|
+
gancho e as peças que a fala pede).
|
|
52
|
+
|
|
53
|
+
## O plano
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{"gancho": "O que é *IA*",
|
|
57
|
+
"pecas": [
|
|
58
|
+
{"id": "def", "tipo": "lettering", "principal": "Máquinas *aprendem*", "apoio": "e decidem", "em": 6.9, "sai": 10.6, "posicao": "topo"},
|
|
59
|
+
{"id": "passos", "tipo": "passo_a_passo", "depois_de": 12.3, "sai": 23.1,
|
|
60
|
+
"passos": [{"texto": "Recebe exemplos", "em": 12.6}, {"texto": "Ajusta parâmetros", "em": 16.9}]},
|
|
61
|
+
{"id": "lista", "tipo": "lista_check", "titulo": "Tudo isso é *IA*", "marca": "check", "depois_de": 31, "sai": 35,
|
|
62
|
+
"itens": [{"texto": "Assistentes", "em": 31.05}, {"texto": "Carros sozinhos", "em": 32.9}]}],
|
|
63
|
+
"broll": [{"busca": "self-driving car", "de": 26.1, "ate": 28.4, "rotulo": "Carro *autônomo*"}]}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `gancho`: até 4 palavras fortes da primeira frase; `*palavra*` sai em
|
|
67
|
+
destaque. `false` desliga.
|
|
68
|
+
- `pecas`: as do catálogo. `em`, `sai`, `depois_de` em segundos do corte.
|
|
69
|
+
- `broll`: imagem livre (Wikimedia, CC0) buscada pelo que a pessoa DISSE
|
|
70
|
+
naquele instante (busca em inglês acha mais), 1,5 a 3 s, com `rotulo`
|
|
71
|
+
curto. `"modo": "tela_verde"` recorta a pessoa na frente da imagem.
|
|
72
|
+
- Opcionais: `estilo` (`laranja` padrão, `papelaria`, `noite`), `legenda`
|
|
73
|
+
(`padrao` | `discreta`), `correcoes` (`{"palavra errada": "certa"}` quando a
|
|
74
|
+
transcrição errou um nome), `blocos` (enquadramento à mão), `trilha_abaixo`
|
|
75
|
+
(dB da trilha abaixo da voz, 20), `sons: false`, `camera_viva: false`.
|
|
76
|
+
- `trilha`: `"auto"` (busca uma faixa com energia), o caminho de um áudio da
|
|
77
|
+
pessoa, ou `"nenhuma"`.
|
|
78
|
+
|
|
79
|
+
## Que peça para cada fala
|
|
80
|
+
|
|
81
|
+
| a fala | a peça |
|
|
82
|
+
|---|---|
|
|
83
|
+
| uma expressão que muda o sentido | `lettering` (`principal`, `apoio`) |
|
|
84
|
+
| enumera ("espaço, estoque, frete") | `lista_check` (`marca` check ou x), cada item na sua palavra, até 22 letras |
|
|
85
|
+
| etapas em ordem | `passo_a_passo` |
|
|
86
|
+
| um número que importa | `contador` (`valor_final`, `sufixo`, `rotulo`) |
|
|
87
|
+
| nome de uma coisa a fixar | `rotulo` (em cima da cabeça) |
|
|
88
|
+
| afirmação curta que fecha a ideia | `carimbo` (1 ou 2 palavras) |
|
|
89
|
+
| cita algo concreto que se pode mostrar | `broll` |
|
|
90
|
+
|
|
91
|
+
Peça grande (lista, passos) ganha moldura sozinha. Lettering com mais de 6
|
|
92
|
+
palavras é erro. Campos de cada peça: `<editor>/references/animacao-catalogo.md`;
|
|
93
|
+
enquadramento: `references/enquadramento.md`; sons e trilha:
|
|
94
|
+
`references/som-e-trilha.md` (`<editor>` é a pasta que o `preparar` devolve).
|
|
95
|
+
|
|
96
|
+
## O som
|
|
97
|
+
|
|
98
|
+
O motor mede a voz de cada trecho e acerta o ganho antes do volume final:
|
|
99
|
+
gravação baixa (-38 LUFS) sai em -14 LUFS, pico até -1 dBTP, 48 kHz, sem o
|
|
100
|
+
modo dinâmico que bombeia a voz. A limpeza de ruído é leve por padrão
|
|
101
|
+
(`limpar: "forte"` no cortar, `false` desliga). O portão reprova volume fora,
|
|
102
|
+
modo dinâmico, amostra clipada, respiro, quadro preto e texto fora da zona
|
|
103
|
+
segura do Reels.
|
|
104
|
+
|
|
105
|
+
## Quando algo dá errado
|
|
106
|
+
|
|
107
|
+
- Erro de um passo: a mensagem diz qual e por quê (em português). Corrija a
|
|
108
|
+
entrada e tente de novo; não repita o mesmo pedido esperando outro
|
|
109
|
+
resultado.
|
|
110
|
+
- Palavra errada na legenda: `correcoes` no plano, ou `prompt` no cortar.
|
|
111
|
+
- A mesma correção falhou duas vezes: pare e conte à pessoa o que testou.
|
|
112
|
+
- Operação solta (só cortar um trecho, juntar, exportar): é o editor por
|
|
113
|
+
linha do tempo (`primocode video help`), não este.
|
package/studio/pessoa.py
CHANGED
|
@@ -175,6 +175,42 @@ def recortar(entrada, saida, suavizar=7, encolher=2, limiar=0.6):
|
|
|
175
175
|
"largura": largura, "altura": altura, "fps": round(fps, 2)}
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
def recortar_foto(entrada, saida, suavizar=7, encolher=2, limiar=0.6, minimo=0.08):
|
|
179
|
+
"""Recorta a pessoa de UMA FOTO e grava um PNG com alfa (foto na cena do motion).
|
|
180
|
+
|
|
181
|
+
Se a pessoa ocupa menos de `minimo` do quadro (ou não há pessoa), devolve
|
|
182
|
+
semPessoa: quem chamou usa a foto inteira com moldura arredondada.
|
|
183
|
+
"""
|
|
184
|
+
import cv2
|
|
185
|
+
import numpy as np
|
|
186
|
+
import mediapipe as mp
|
|
187
|
+
|
|
188
|
+
quadro = cv2.imread(entrada, cv2.IMREAD_COLOR)
|
|
189
|
+
if quadro is None:
|
|
190
|
+
return {"ok": False, "error": f"não consegui abrir a foto {entrada}"}
|
|
191
|
+
segmentador = mp.solutions.selfie_segmentation.SelfieSegmentation(model_selection=0)
|
|
192
|
+
try:
|
|
193
|
+
resultado = segmentador.process(cv2.cvtColor(quadro, cv2.COLOR_BGR2RGB))
|
|
194
|
+
finally:
|
|
195
|
+
segmentador.close()
|
|
196
|
+
mascara = (resultado.segmentation_mask > limiar).astype(np.uint8) * 255
|
|
197
|
+
cobertura = float((mascara > 0).mean())
|
|
198
|
+
if cobertura < minimo:
|
|
199
|
+
return {"ok": False, "semPessoa": True, "cobertura": round(cobertura, 3),
|
|
200
|
+
"error": "não achei uma pessoa nesta foto."}
|
|
201
|
+
if encolher > 0:
|
|
202
|
+
mascara = cv2.erode(mascara, np.ones((3, 3), np.uint8), iterations=int(encolher))
|
|
203
|
+
if suavizar > 0:
|
|
204
|
+
k = int(suavizar) | 1
|
|
205
|
+
mascara = cv2.GaussianBlur(mascara, (k, k), 0)
|
|
206
|
+
bgra = cv2.cvtColor(quadro, cv2.COLOR_BGR2BGRA)
|
|
207
|
+
bgra[:, :, 3] = mascara
|
|
208
|
+
if not cv2.imwrite(saida, bgra):
|
|
209
|
+
return {"ok": False, "error": f"não consegui gravar o recorte em {saida}"}
|
|
210
|
+
return {"ok": True, "arquivo": saida, "cobertura": round(cobertura, 3),
|
|
211
|
+
"largura": int(quadro.shape[1]), "altura": int(quadro.shape[0])}
|
|
212
|
+
|
|
213
|
+
|
|
178
214
|
def main():
|
|
179
215
|
p = argparse.ArgumentParser(description="Recorte de pessoa do PrimoCode Studio")
|
|
180
216
|
sub = p.add_subparsers(dest="comando", required=True)
|
|
@@ -185,10 +221,26 @@ def main():
|
|
|
185
221
|
r.add_argument("--suavizar", type=int, default=7)
|
|
186
222
|
r.add_argument("--encolher", type=int, default=2)
|
|
187
223
|
r.add_argument("--limiar", type=float, default=0.6)
|
|
224
|
+
f = sub.add_parser("recortar-foto")
|
|
225
|
+
f.add_argument("--entrada", required=True)
|
|
226
|
+
f.add_argument("--saida", required=True)
|
|
227
|
+
f.add_argument("--suavizar", type=int, default=7)
|
|
228
|
+
f.add_argument("--encolher", type=int, default=2)
|
|
229
|
+
f.add_argument("--limiar", type=float, default=0.6)
|
|
188
230
|
args = p.parse_args()
|
|
189
231
|
|
|
190
232
|
falta = faltando()
|
|
191
233
|
|
|
234
|
+
if args.comando == "recortar-foto":
|
|
235
|
+
# Dentro do motion não se instala nada (o MediaPipe passa de 100 MB): sem ele, a foto vai inteira, com moldura.
|
|
236
|
+
if falta:
|
|
237
|
+
saida = {"ok": False, "semMediaPipe": True, "falta": falta,
|
|
238
|
+
"error": "o recorte de pessoa precisa de " + ", ".join(falta) + " (a foto segue inteira, com moldura)."}
|
|
239
|
+
else:
|
|
240
|
+
saida = recortar_foto(args.entrada, args.saida, args.suavizar, args.encolher, args.limiar)
|
|
241
|
+
print(json.dumps(saida, ensure_ascii=False))
|
|
242
|
+
sys.exit(0 if saida.get("ok") else 1)
|
|
243
|
+
|
|
192
244
|
if args.comando == "conferir":
|
|
193
245
|
saida = {"ok": not falta, "falta": falta,
|
|
194
246
|
"comoResolver": (f"{sys.executable} -m pip install --user " + " ".join(falta)
|