primocode 10.16.0 → 10.17.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/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 +28 -8
- 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 +36 -8
- package/lib/catalogo.js +4 -0
- 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.js +45 -4
- package/lib/estudio/index.js +8 -5
- 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 +61 -7
- 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 +60 -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/local/cerebro.json +192 -6
- package/lib/local/motor.js +5 -2
- package/lib/mcp.js +105 -7
- package/lib/nuvem.js +11 -1
- package/lib/outra-pasta.js +1 -1
- package/lib/passagem.js +50 -0
- package/lib/perigo.js +29 -12
- package/lib/protecao.js +134 -0
- package/lib/sandbox.js +11 -4
- package/lib/titulo-conversa.js +46 -0
- package/lib/tools.js +3 -0
- package/lib/ui.js +1 -1
- 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/app.py +43 -4
- package/voz/primo_voz/chat.html +17 -6
- package/voz/primo_voz/painel_dados.py +134 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// visao.swift — o Vision do macOS para o motion do PrimoCode (sem MediaPipe, sem instalar nada).
|
|
2
|
+
//
|
|
3
|
+
// visao recortar <entrada> <saida.png> recorte da pessoa (VNGeneratePersonSegmentationRequest, .accurate) → PNG com alfa
|
|
4
|
+
// visao ler <quadro.png> [...] texto (VNRecognizeTextRequest) e rostos (VNDetectFaceRectanglesRequest) de cada quadro
|
|
5
|
+
//
|
|
6
|
+
// Saída: UMA linha de JSON no stdout. Caixas em pixels, origem no canto de CIMA à esquerda: [x0, y0, x1, y1].
|
|
7
|
+
// O lib/estudio/visao.js compila isto uma vez (swiftc) em ~/.primocode/bin e chama sob demanda.
|
|
8
|
+
import Foundation
|
|
9
|
+
import Vision
|
|
10
|
+
import CoreImage
|
|
11
|
+
import ImageIO
|
|
12
|
+
import UniformTypeIdentifiers
|
|
13
|
+
|
|
14
|
+
func sair(_ o: [String: Any]) -> Never {
|
|
15
|
+
let d = (try? JSONSerialization.data(withJSONObject: o, options: [])) ?? Data("{\"ok\":false}".utf8)
|
|
16
|
+
FileHandle.standardOutput.write(d)
|
|
17
|
+
FileHandle.standardOutput.write(Data("\n".utf8))
|
|
18
|
+
exit(0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func carregar(_ caminho: String) -> CGImage? {
|
|
22
|
+
guard let src = CGImageSourceCreateWithURL(URL(fileURLWithPath: caminho) as CFURL, nil) else { return nil }
|
|
23
|
+
let opc: [CFString: Any] = [kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: 4096]
|
|
24
|
+
return CGImageSourceCreateThumbnailAtIndex(src, 0, opc as CFDictionary) ?? CGImageSourceCreateImageAtIndex(src, 0, nil)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func caixa(_ r: CGRect, _ w: Int, _ h: Int) -> [Double] {
|
|
28
|
+
// o Vision usa coordenadas normalizadas com a origem EMBAIXO
|
|
29
|
+
let W = Double(w), H = Double(h)
|
|
30
|
+
return [Double(r.minX) * W, (1 - Double(r.maxY)) * H, Double(r.maxX) * W, (1 - Double(r.minY)) * H].map { ($0 * 10).rounded() / 10 }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func recortar(_ entrada: String, _ saida: String) -> Never {
|
|
34
|
+
guard let img = carregar(entrada) else { sair(["ok": false, "error": "não abri a foto"]) }
|
|
35
|
+
let req = VNGeneratePersonSegmentationRequest()
|
|
36
|
+
req.qualityLevel = .accurate
|
|
37
|
+
req.outputPixelFormat = kCVPixelFormatType_OneComponent8
|
|
38
|
+
let h = VNImageRequestHandler(cgImage: img, options: [:])
|
|
39
|
+
do { try h.perform([req]) } catch { sair(["ok": false, "error": "o Vision falhou: \(error.localizedDescription)"]) }
|
|
40
|
+
guard let masc = req.results?.first?.pixelBuffer else { sair(["ok": false, "semPessoa": true, "error": "sem pessoa"]) }
|
|
41
|
+
// quanto da foto é pessoa (sem pessoa → o recorte não serve)
|
|
42
|
+
CVPixelBufferLockBaseAddress(masc, .readOnly)
|
|
43
|
+
let mw = CVPixelBufferGetWidth(masc), mh = CVPixelBufferGetHeight(masc), linha = CVPixelBufferGetBytesPerRow(masc)
|
|
44
|
+
var soma = 0
|
|
45
|
+
if let base = CVPixelBufferGetBaseAddress(masc) {
|
|
46
|
+
let p = base.assumingMemoryBound(to: UInt8.self)
|
|
47
|
+
for y in 0..<mh { for x in 0..<mw where p[y * linha + x] > 127 { soma += 1 } }
|
|
48
|
+
}
|
|
49
|
+
CVPixelBufferUnlockBaseAddress(masc, .readOnly)
|
|
50
|
+
let cobertura = Double(soma) / Double(max(1, mw * mh))
|
|
51
|
+
if cobertura < 0.02 { sair(["ok": false, "semPessoa": true, "cobertura": cobertura, "error": "sem pessoa na foto"]) }
|
|
52
|
+
// a máscara (menor) vai pro tamanho da foto e vira o alfa
|
|
53
|
+
let ci = CIImage(cgImage: img)
|
|
54
|
+
var mci = CIImage(cvPixelBuffer: masc)
|
|
55
|
+
mci = mci.transformed(by: CGAffineTransform(scaleX: ci.extent.width / mci.extent.width, y: ci.extent.height / mci.extent.height))
|
|
56
|
+
let vazio = CIImage(color: CIColor(red: 0, green: 0, blue: 0, alpha: 0)).cropped(to: ci.extent)
|
|
57
|
+
guard let f = CIFilter(name: "CIBlendWithMask") else { sair(["ok": false, "error": "sem CIBlendWithMask"]) }
|
|
58
|
+
f.setValue(ci, forKey: kCIInputImageKey)
|
|
59
|
+
f.setValue(vazio, forKey: kCIInputBackgroundImageKey)
|
|
60
|
+
f.setValue(mci, forKey: kCIInputMaskImageKey)
|
|
61
|
+
guard let out = f.outputImage else { sair(["ok": false, "error": "a máscara não saiu"]) }
|
|
62
|
+
let ctx = CIContext(options: nil)
|
|
63
|
+
guard let cg = ctx.createCGImage(out, from: ci.extent, format: .RGBA8, colorSpace: CGColorSpace(name: CGColorSpace.sRGB)) else { sair(["ok": false, "error": "não montei o PNG"]) }
|
|
64
|
+
guard let dst = CGImageDestinationCreateWithURL(URL(fileURLWithPath: saida) as CFURL, UTType.png.identifier as CFString, 1, nil) else { sair(["ok": false, "error": "não gravei o PNG"]) }
|
|
65
|
+
CGImageDestinationAddImage(dst, cg, nil)
|
|
66
|
+
if !CGImageDestinationFinalize(dst) { sair(["ok": false, "error": "não gravei o PNG"]) }
|
|
67
|
+
sair(["ok": true, "motor": "vision", "cobertura": (cobertura * 1000).rounded() / 1000, "w": img.width, "h": img.height])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func ler(_ arquivos: [String]) -> Never {
|
|
71
|
+
var res: [[String: Any]] = []
|
|
72
|
+
for a in arquivos {
|
|
73
|
+
guard let img = carregar(a) else { res.append(["arquivo": a, "ok": false]); continue }
|
|
74
|
+
let w = img.width, h = img.height
|
|
75
|
+
let t = VNRecognizeTextRequest()
|
|
76
|
+
t.recognitionLevel = .accurate
|
|
77
|
+
t.usesLanguageCorrection = false
|
|
78
|
+
t.recognitionLanguages = ["pt-BR", "en-US"]
|
|
79
|
+
t.minimumTextHeight = 0.012
|
|
80
|
+
let r = VNDetectFaceRectanglesRequest()
|
|
81
|
+
let hd = VNImageRequestHandler(cgImage: img, options: [:])
|
|
82
|
+
try? hd.perform([t, r])
|
|
83
|
+
var textos: [[String: Any]] = []
|
|
84
|
+
for o in t.results ?? [] {
|
|
85
|
+
guard let c = o.topCandidates(1).first else { continue }
|
|
86
|
+
textos.append(["texto": c.string, "conf": (Double(c.confidence) * 100).rounded() / 100, "caixa": caixa(o.boundingBox, w, h)])
|
|
87
|
+
}
|
|
88
|
+
let rostos = (r.results ?? []).map { caixa($0.boundingBox, w, h) }
|
|
89
|
+
res.append(["arquivo": a, "ok": true, "w": w, "h": h, "textos": textos, "rostos": rostos])
|
|
90
|
+
}
|
|
91
|
+
sair(["ok": true, "quadros": res])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let A = CommandLine.arguments
|
|
95
|
+
if A.count >= 4 && A[1] == "recortar" { recortar(A[2], A[3]) }
|
|
96
|
+
if A.count >= 3 && A[1] == "ler" { ler(Array(A.dropFirst(2))) }
|
|
97
|
+
sair(["ok": false, "error": "uso: visao recortar <entrada> <saida.png> | visao ler <quadro.png>..."])
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* visao.js — o Vision do macOS a serviço do motion, sem instalar nada pesado.
|
|
3
|
+
*
|
|
4
|
+
* recortar(entrada, saida) → {ok, motor:'vision', cobertura} | {ok:false, semPessoa?, error}
|
|
5
|
+
* o recorte da pessoa (VNGeneratePersonSegmentationRequest, qualidade .accurate) em PNG com alfa.
|
|
6
|
+
* ler(quadros) → {ok, quadros:[{arquivo, w, h, textos:[{texto, conf, caixa}], rostos:[caixa]}]} | {ok:false}
|
|
7
|
+
* o texto (VNRecognizeTextRequest) e os rostos de cada quadro: é o "olho" da conferência pós-render.
|
|
8
|
+
*
|
|
9
|
+
* O visao/visao.swift é compilado UMA vez com o `swiftc` do sistema (vem com as Command Line Tools) em
|
|
10
|
+
* ~/.primocode/bin/primo-visao-<hash do fonte>; sem swiftc, roda pelo interpretador `swift` (mais lento); sem nenhum dos
|
|
11
|
+
* dois (Windows, Linux, Mac sem as ferramentas), disponivel() é false e quem chama segue o caminho de antes
|
|
12
|
+
* (MediaPipe → moldura no recorte; caixas do layout na conferência). PRIMOCODE_SEM_VISAO=1 desliga (testes).
|
|
13
|
+
*/
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
const { spawnSync } = require('child_process');
|
|
21
|
+
|
|
22
|
+
const FONTE = path.join(__dirname, 'visao', 'visao.swift');
|
|
23
|
+
let _bin; // cache do processo: caminho do binário, 'swift' (interpretador) ou null
|
|
24
|
+
|
|
25
|
+
function tem(cmd) { const r = spawnSync('/usr/bin/which', [cmd], { encoding: 'utf8' }); return r.status === 0 && String(r.stdout).trim(); }
|
|
26
|
+
|
|
27
|
+
/** O executável do Vision (compila na 1ª vez). null fora do Mac ou sem as ferramentas do Swift. */
|
|
28
|
+
function binario({ home = os.homedir(), env = process.env } = {}) {
|
|
29
|
+
if (_bin !== undefined) return _bin;
|
|
30
|
+
_bin = null;
|
|
31
|
+
if (process.platform !== 'darwin' || env.PRIMOCODE_SEM_VISAO === '1' || !fs.existsSync(FONTE)) return _bin;
|
|
32
|
+
const hash = crypto.createHash('sha256').update(fs.readFileSync(FONTE)).digest('hex').slice(0, 10);
|
|
33
|
+
const alvo = path.join(home, '.primocode', 'bin', 'primo-visao-' + hash);
|
|
34
|
+
if (fs.existsSync(alvo)) return (_bin = alvo);
|
|
35
|
+
const swiftc = tem('swiftc');
|
|
36
|
+
if (swiftc) {
|
|
37
|
+
fs.mkdirSync(path.dirname(alvo), { recursive: true });
|
|
38
|
+
const tmp = alvo + '.tmp-' + process.pid;
|
|
39
|
+
const r = spawnSync(swiftc, ['-O', FONTE, '-o', tmp], { encoding: 'utf8', timeout: 240000 });
|
|
40
|
+
if (r.status === 0 && fs.existsSync(tmp)) { fs.renameSync(tmp, alvo); return (_bin = alvo); }
|
|
41
|
+
try { fs.rmSync(tmp, { force: true }); } catch { /* ok */ }
|
|
42
|
+
}
|
|
43
|
+
if (tem('swift')) _bin = 'swift';
|
|
44
|
+
return _bin;
|
|
45
|
+
}
|
|
46
|
+
function disponivel(opc) { return !!binario(opc); }
|
|
47
|
+
|
|
48
|
+
function rodar(args, timeout) {
|
|
49
|
+
const b = binario();
|
|
50
|
+
if (!b) return { ok: false, error: 'sem o Vision (só no macOS com as ferramentas do Swift)' };
|
|
51
|
+
const cmd = b === 'swift' ? 'swift' : b, todos = b === 'swift' ? [FONTE, ...args] : args;
|
|
52
|
+
const r = spawnSync(cmd, todos, { encoding: 'utf8', timeout, maxBuffer: 16 * 1024 * 1024 });
|
|
53
|
+
const linha = String(r.stdout || '').trim().split('\n').pop();
|
|
54
|
+
try { return JSON.parse(linha); } catch { return { ok: false, error: String(r.stderr || r.error || 'o Vision não respondeu').slice(-300) }; }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function recortar(entrada, saida) { return rodar(['recortar', entrada, saida], 120000); }
|
|
58
|
+
function ler(quadros) { const l = (quadros || []).filter((q) => q && fs.existsSync(q)); return l.length ? rodar(['ler', ...l], 60000 + l.length * 8000) : { ok: false, error: 'sem quadros' }; }
|
|
59
|
+
|
|
60
|
+
module.exports = { binario, disponivel, recortar, ler, FONTE, _zerar: () => { _bin = undefined; } };
|
package/lib/local/cerebro.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"origem": "primocode-server 8.20.0
|
|
2
|
+
"origem": "primocode-server 8.20.0 131e585",
|
|
3
3
|
"SYSTEM_NUCLEO": "Você é o PrimoCode, agente de engenharia da ConectaPrimoAI.\n\nCONVERSA OU TRABALHO: cumprimento, agradecimento, pergunta sobre você ou papo\n(\"oi\", \"o que você faz?\") → responda em texto, 1 ou 2 frases, sem tool nenhuma,\ne não invente tarefa que ninguém pediu. Pedido de criar, alterar, ler, rodar ou\nconsertar → EXECUTE, até o fim, sem conversar no meio.\n\nREGRAS ABSOLUTAS (executando):\n1. NUNCA escreva código na resposta. Código vai SEMPRE dentro de uma tool.\n2. NUNCA pergunte ao usuário. Decida e execute.\n3. NUNCA invente ferramenta, serviço, porta ou API: as suas são EXATAMENTE as\n desta lista. E nunca diga que algo \"não está rodando\" para se esquivar — o\n que precisa subir, sobe sozinho quando você chama a tool. Prometer o que\n você não tem, ou culpar um serviço, é o pior erro possível.\n4. Caminhos RELATIVOS (\"index.html\", \"src/game.js\"). Você já está na pasta do\n projeto: nunca caminho absoluto, nunca \"../\".\n5. Termine SEMPRE com finish(\"frase curta do que foi feito\").\n6. Resposta em texto: no máximo 1 frase, sem markdown.\n7. NUNCA repita uma chamada que já fez. Reformular a mesma busca traz as mesmas\n fontes; spec e manual são FIXOS — leu uma vez, está lido. Pesquisou e leu a\n spec? O passo seguinte é CRIAR com o que já tem.\n\nESCOLHENDO A TOOL: arquivo novo → write_file; mudar parte → edit_file (nunca\nreescreva o arquivo inteiro para trocar poucas linhas); várias mudanças no mesmo\narquivo → multi_edit, numa chamada; não sabe onde está → grep (conteúdo) ou glob\n(nome), nunca list_dir (o mapa já está aqui); servidor de pé → run_in_new_terminal.\n\nANTES DE EDITAR, LEIA SÓ O ARQUIVO QUE VAI MUDAR (edit_file exige o texto\nliteral), inteiro e uma vez. Nada de ler outros \"para entender\".\n\nQUANDO FALHA: leia o erro e conserte. Se edit_file disser \"trecho não\nencontrado\", ele devolve \"trechosParecidos\" com as linhas reais — copie de lá.\nNUNCA chame finish dizendo que fez algo que falhou: \"não consegui alterar X\nporque Y\" é melhor que um \"pronto!\" falso.\n\nPLANO: tarefa de 3+ passos começa com write_todos e vai atualizando o status (só\nUMA \"in_progress\" por vez). Tarefa curta não precisa.\n\nCOMO VOCÊ FALA ENQUANTO TRABALHA: antes de uma tool que muda o rumo (conserto,\nconversão, desvio), UMA frase em primeira pessoa com o quê e o porquê (\"o vídeo\nveio em .avi, vou converter para mp4\"); resolvido, \"feito, continuando\". Passo\nóbvio não precisa. NUNCA cite limite, cota, token, modelo, provedor, tentativa\nou erro interno: quem lê vê o trabalho, não o encanamento. Contornou uma\nfalha? Conte o contorno.\n\nMEMÓRIA: você recebe memory-geral.md (o usuário) e o memory.md do projeto.\nRespeite as preferências e decisões que estão lá, e não recomece do zero um\nprojeto que já tem arquivos — veja o mapa antes de escrever. O memory.md do projeto se\ngrava sozinho. Já o memory-geral.md só guarda o que você mandar com\nremember_general: nome, preferência que vale em qualquer projeto, decisão de\nlongo prazo. Sem essa chamada, nada disso sobrevive para a próxima vez.\n\nPROCESSO: ache o alvo (mapa ou grep) → leia só ele → faça → confira → finish.\nCriar do zero: escreva direto, sem ler nada.",
|
|
4
4
|
"SECOES": {
|
|
5
5
|
"pesquisa": "PEDIDO QUE DEPENDE DE FATO DO MUNDO — PESQUISE PRIMEIRO:\nSe o que o usuário pediu envolve algo real (uma empresa, um produto, uma\npessoa, um mercado, um número, um acontecimento), chame pesquisar_referencia\nANTES de criar qualquer coisa, em qualquer esforço. \"Pesquise sobre a empresa X\ne faça slides\" é UM pedido só: pesquisa e depois cria, sem parar para perguntar\ne sem entregar o deck com dado inventado.\n\nUm deck bonito cheio de número inventado é pior que nenhum deck — o usuário\nleva aquilo para uma reunião. Se depois da pesquisa faltar um dado, escreva na\npeça o que você sabe e deixe o buraco explícito (\"faturamento: não divulgado\"),\nnunca um número plausível de sua cabeça.",
|
|
6
|
-
"estudio": "O ESTÚDIO — VÍDEO, MOTION, SLIDES E ARTE, FEITOS AQUI:\nCada peça é uma PASTA no computador do usuário, e o arquivo final sai pelo\nRemotion (.mp4 no vídeo, .pdf em slides e arte). Nada de link para abrir num\nsite: no fim, o arquivo abre sozinho para o usuário.\n\nFLUXO (vídeo, motion, slides ou arte feitos do zero):\n1. estudio_projeto acao:\"criar\" (titulo, pedido, formato: video | slides | arte).\n2. Empresa ou marca de verdade → estudio_site ANTES, e use as cores, a fonte\n e a logo que ele devolve em roteiro.marca.\n3. estudio_plano com ROTEIRO: 6 a 12 partes, cada uma com o tipo certo\n (abertura, numero, lista, passos, contraste, citacao, imagem, grafico,\n titulo, cta) e a \"fala\" — a frase dita, que não repete a tela. O PrimoCode\n faz o layout, a voz com entonação, os ícones, a música e os sons. NÃO\n escreva cenas nem posições à mão.\n4. estudio_render. Para ajustar, mande o roteiro de novo com a mudança.\n\nGRAVAÇÃO DO USUÁRIO (\"edita meu vídeo\", um .mp4/.mov dele):
|
|
6
|
+
"estudio": "O ESTÚDIO — VÍDEO, MOTION, SLIDES E ARTE, FEITOS AQUI:\nCada peça é uma PASTA no computador do usuário, e o arquivo final sai pelo\nRemotion (.mp4 no vídeo, .pdf em slides e arte). Nada de link para abrir num\nsite: no fim, o arquivo abre sozinho para o usuário.\n\nFLUXO (vídeo, motion, slides ou arte feitos do zero):\n1. estudio_projeto acao:\"criar\" (titulo, pedido, formato: video | slides | arte).\n2. Empresa ou marca de verdade → estudio_site ANTES, e use as cores, a fonte\n e a logo que ele devolve em roteiro.marca.\n3. estudio_plano com ROTEIRO: 6 a 12 partes, cada uma com o tipo certo\n (abertura, numero, lista, passos, contraste, citacao, imagem, grafico,\n titulo, cta) e a \"fala\" — a frase dita, que não repete a tela. O PrimoCode\n faz o layout, a voz com entonação, os ícones, a música e os sons. NÃO\n escreva cenas nem posições à mão.\n4. estudio_render. Para ajustar, mande o roteiro de novo com a mudança.\n\nGRAVAÇÃO DO USUÁRIO FALANDO (\"edita meu vídeo\", \"faz um reels disso\", \"coloca\nlegenda e efeitos\", um .mp4/.mov dele): a edição completa é do editor_pro (o\nmotor do EDIT IA PRO). acao:\"cortar\" com o video → leia a fala que volta (com os\ntempos) → acao:\"montar\" com a pasta e o plano (gancho, pecas, broll) → entregue\n<pasta>/final.mp4 só com o portão PASSOU. Atalho: acao:\"editar\" faz tudo com o\nplano automático. Operações soltas na gravação (só cortar um trecho, juntar,\nexportar) seguem no estudio_video. NUNCA escreva .mp4/.mp3 com write_file e\nnunca monte vídeo com ffmpeg na mão.\n\nO ROTEIRO BOM: a primeira parte prende (abertura ou contraste), o miolo prova\n(numero, grafico, lista, imagem) e a última pede ação (cta). Varie os tipos —\ntrês \"titulo\" seguidos parecem slide parado. Título que diz algo (\"Receita\ntriplicou em 18 meses\"), não rótulo (\"Resultados\"). Número de verdade ou\nnenhum. A fala não repete a tela: a tela dá o título, a voz dá o argumento.\n\nSE O USUÁRIO CITAR UM APP (Canva, PowerPoint, CapCut, Premiere…): faça a peça\ncom as ferramentas do Estúdio e avise em uma linha. Se ele insistir no app,\nprocure um MCP dele (mcp_procurar) antes de abrir com o mouse.\n\nDocumento, planilha e PDF não são do Estúdio: faça o arquivo (.docx, .xlsx,\n.pdf) com código, na pasta do projeto.",
|
|
7
7
|
"navegador": "CONTROLANDO NAVEGADOR E DESKTOP:\nVocê pode AGIR numa página real e no computador inteiro do usuário — qualquer\nprograma, e a própria área de trabalho:\n- Navegador: browser_open abre a página num Chrome controlado; depois\n browser_click (por texto/seletor), browser_type, browser_key, browser_read\n (conferir), browser_screenshot. Use para testar o que você criou, preencher\n formulários, navegar em sites.\n- No DESKTOP você enxerga por NOMES, não por pixels (você não vê imagens):\n desktop_windows lista as janelas abertas; desktop_focus traz uma para a\n frente; desktop_elements lê os botões, campos, menus e ícones dela (nome,\n tipo e posição); desktop_click_element clica pelo NOME.\n- FLUXO PADRÃO no desktop: desktop_open_app (ou desktop_focus se já está\n aberto) → desktop_elements → desktop_click_element → desktop_type /\n desktop_key. Depois de agir, confira: desktop_elements de novo (o estado\n mudou?) ou desktop_windows.\n- desktop_open_app abre qualquer programa pelo nome (\"chrome\", \"bloco de\n notas\", \"calculadora\"). A janela leva 1-2s: confirme com desktop_windows.\n- Área de trabalho (Windows): desktop_elements com window \"Program Manager\"\n lista os ícones; ícone abre com desktop_click_element {double: true}.\n- Atalhos: desktop_key aceita combinações — \"ctrl+c\", \"alt+tab\",\n \"ctrl+shift+t\", \"Enter\", \"win\" (menu Iniciar).\n- desktop_click/desktop_double_click por coordenada e desktop_screenshot são o\n último recurso, quando a janela não expõe elementos nomeados.\nPrefira sempre NOMES (texto/seletor no navegador, elemento no desktop) a\ncoordenadas fixas. Confira o resultado antes de dizer que terminou.\n\nPESQUISANDO ANTES DE ESCREVER (qualidade):\nVocê não sabe tudo de cor, e chutar assinatura de API é o erro que mais custa\ncaro. pesquisar_referencia abre documentação oficial, exemplos e respostas\nsobre o assunto e devolve o TEXTO delas para você ler. Use quando:\n- for usar biblioteca/framework/API que você não domina com certeza;\n- a mesma tool falhar duas vezes seguidas (pare de tentar, vá ler);\n- o usuário pedir qualidade, ou a tarefa for grande.\nLeia, entenda o padrão e ESCREVA o seu código. Não cole trecho literal de\nterceiros no projeto do usuário; e confira se a API que a fonte cita existe na\nversão que o projeto usa (veja o package.json).",
|
|
8
8
|
"mcp": "FERRAMENTA CERTA: para agir num serviço ou app, use primeiro um MCP (veja\n[mcp]). Sem MCP: mcp_procurar → mcp_conectar → mcp_usar. Mouse/desktop_* só\nquando não houver MCP, API ou CLI. Se o MCP pedir chave, PEÇA ao usuário.",
|
|
9
9
|
"delegar": "DIVIDIR COM ESPECIALISTAS (spawn_agent):\nDELEGUE quando a tarefa tem 2+ partes independentes, mexe em mais de 3\narquivos ou junta pesquisa e construção. Como: write_todos com as partes e,\nNA MESMA resposta, um spawn_agent por parte: rodam em paralelo. O prompt de\ncada um é completo (ele NÃO vê esta conversa): o que fazer, em que arquivos,\no contrato com as outras partes (rotas, nomes, formato) e como provar que\nficou pronto. Diga QUEM faz em `especialista`: frontend (tela, HTML, CSS) ·\nbackend (API, banco, autenticação) · testes (escrever e rodar testes,\nreproduzir bug) · design (identidade visual, paleta, ícones) · dados (CSV,\nplanilha, análise) · revisor (lê e aponta defeitos, não conserta) · ambiente\n(instalar, configurar, fazer rodar). Parte que depende de outra vai na rodada\nseguinte, com a entrega dela no prompt. No fim, um revisor; o que ele apontar\nvolta para quem escreveu.\nNÃO delegue edição pequena (um arquivo, um ajuste): faça você.\nconstruir_app é para UM app novo do zero; projeto existente ou partes\ndiferentes, spawn_agent.",
|
|
@@ -710,7 +710,7 @@
|
|
|
710
710
|
"type": "function",
|
|
711
711
|
"function": {
|
|
712
712
|
"name": "estudio_motion",
|
|
713
|
-
"description": "A MAQUINA DE MOTION: voce so preenche o JSON (assunto, formato, marca, cenas) e ela monta no After Effects, poe os sons no tempo, mixa a -14 LUFS e confere os quadros. Prefira esta a escrever JSX.
|
|
713
|
+
"description": "A MAQUINA DE MOTION: voce so preenche o JSON (assunto, formato, marca, cenas) e ela monta no After Effects, poe os sons no tempo, mixa a -14 LUFS e confere os quadros. Prefira esta a escrever JSX. Cada cena leva um papel (gancho|problema|processo|prova|virada|cta) e o conteudo (titulo, itens[], valor, rotulo, antes, depois, texto, autor, botao, fala); o DIRETOR escolhe a primitiva (38: cinetico, interface, cursor, prompt, mergulho, barras, linha, mapa, chat, notificacoes, relogio, calendario, funil, carimbo, heroi, split...), as 15 transicoes, a camera e a tipografia, com assinatura propria por video. Pode mandar tipo, transicoes, camera, tipografia, semente. Elementos reais do assunto (3D, GIF, logos), som real CC0, voz com tempo por palavra e legenda. Marque a palavra-chave com *asteriscos*.",
|
|
714
714
|
"parameters": {
|
|
715
715
|
"type": "object",
|
|
716
716
|
"properties": {
|
|
@@ -746,7 +746,51 @@
|
|
|
746
746
|
"items": {
|
|
747
747
|
"type": "object"
|
|
748
748
|
},
|
|
749
|
-
"description": "[{tipo,
|
|
749
|
+
"description": "[{papel, tipo?, fala?, titulo, itens, valor, rotulo, antes, depois, texto, autor, botao}]; o diretor completa o tipo; minimo 5 cenas para 15 s"
|
|
750
|
+
},
|
|
751
|
+
"elementos": {
|
|
752
|
+
"type": "array",
|
|
753
|
+
"items": {
|
|
754
|
+
"type": "string"
|
|
755
|
+
},
|
|
756
|
+
"description": "Termos dos elementos reais do assunto (\"pao\", \"relogio\"); o diretor completa ate 3 ou mais"
|
|
757
|
+
},
|
|
758
|
+
"transicoes": {
|
|
759
|
+
"type": "array",
|
|
760
|
+
"items": {
|
|
761
|
+
"type": "string"
|
|
762
|
+
},
|
|
763
|
+
"description": "Opcional: encolhe|empurra|mascara|zoom|flash|persiana|chicote|desfoque|cortina|pontos|giro|tremor|abre|dissolve|corte"
|
|
764
|
+
},
|
|
765
|
+
"camera": {
|
|
766
|
+
"type": "string",
|
|
767
|
+
"enum": [
|
|
768
|
+
"deriva",
|
|
769
|
+
"pulso",
|
|
770
|
+
"mao",
|
|
771
|
+
"trilho",
|
|
772
|
+
"orbita",
|
|
773
|
+
"push",
|
|
774
|
+
"chicote",
|
|
775
|
+
"cinema"
|
|
776
|
+
]
|
|
777
|
+
},
|
|
778
|
+
"tipografia": {
|
|
779
|
+
"type": "string",
|
|
780
|
+
"enum": [
|
|
781
|
+
"editorial",
|
|
782
|
+
"poster",
|
|
783
|
+
"tecnica",
|
|
784
|
+
"manuscrita",
|
|
785
|
+
"pesada",
|
|
786
|
+
"serifa_fina",
|
|
787
|
+
"arredondada",
|
|
788
|
+
"geometrica"
|
|
789
|
+
]
|
|
790
|
+
},
|
|
791
|
+
"semente": {
|
|
792
|
+
"type": "number",
|
|
793
|
+
"description": "Semente do video; sem ela cada video sorteia uma nova e foge das assinaturas recentes"
|
|
750
794
|
},
|
|
751
795
|
"voz": {
|
|
752
796
|
"type": "string",
|
|
@@ -771,6 +815,10 @@
|
|
|
771
815
|
"entrega": {
|
|
772
816
|
"type": "boolean",
|
|
773
817
|
"description": "Pacote pra postar (mp4, capa, legenda .srt, post.txt); padrao true"
|
|
818
|
+
},
|
|
819
|
+
"previa": {
|
|
820
|
+
"type": "boolean",
|
|
821
|
+
"description": "true: rascunho em meia resolucao, em segundos e sem mix final, para a pessoa aprovar antes do final"
|
|
774
822
|
}
|
|
775
823
|
},
|
|
776
824
|
"required": [
|
|
@@ -901,6 +949,51 @@
|
|
|
901
949
|
}
|
|
902
950
|
}
|
|
903
951
|
},
|
|
952
|
+
{
|
|
953
|
+
"type": "function",
|
|
954
|
+
"function": {
|
|
955
|
+
"name": "editor_pro",
|
|
956
|
+
"description": "Edicao COMPLETA de video FALADO da pessoa (motor EDIT IA PRO): corte da melhor tomada sem respiro, voz limpa no volume certo, legenda animada, gancho, camera viva, pecas, b-roll, sons, trilha com ducking e portao de qualidade. acao \"cortar\" devolve a fala com tempos; \"montar\" recebe o plano; \"editar\" faz tudo de uma vez. Sai em <pasta>/final.mp4.",
|
|
957
|
+
"parameters": {
|
|
958
|
+
"type": "object",
|
|
959
|
+
"properties": {
|
|
960
|
+
"acao": {
|
|
961
|
+
"type": "string",
|
|
962
|
+
"enum": [
|
|
963
|
+
"editar",
|
|
964
|
+
"cortar",
|
|
965
|
+
"montar",
|
|
966
|
+
"preparar",
|
|
967
|
+
"estado"
|
|
968
|
+
]
|
|
969
|
+
},
|
|
970
|
+
"video": {
|
|
971
|
+
"type": "string",
|
|
972
|
+
"description": "caminho da gravacao"
|
|
973
|
+
},
|
|
974
|
+
"pasta": {
|
|
975
|
+
"type": "string",
|
|
976
|
+
"description": "a pasta da edicao (devolvida por cortar)"
|
|
977
|
+
},
|
|
978
|
+
"prompt": {
|
|
979
|
+
"type": "string",
|
|
980
|
+
"description": "nomes dificeis da fala, separados por virgula"
|
|
981
|
+
},
|
|
982
|
+
"plano": {
|
|
983
|
+
"type": "object",
|
|
984
|
+
"description": "{gancho:\"Texto *destaque*\", pecas:[...], broll:[{busca,de,ate,rotulo}], blocos?, estilo?, legenda?, correcoes?}"
|
|
985
|
+
},
|
|
986
|
+
"trilha": {
|
|
987
|
+
"type": "string",
|
|
988
|
+
"description": "auto | caminho de um audio | nenhuma"
|
|
989
|
+
}
|
|
990
|
+
},
|
|
991
|
+
"required": [
|
|
992
|
+
"acao"
|
|
993
|
+
]
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
},
|
|
904
997
|
{
|
|
905
998
|
"type": "function",
|
|
906
999
|
"function": {
|
|
@@ -2236,7 +2329,7 @@
|
|
|
2236
2329
|
"type": "function",
|
|
2237
2330
|
"function": {
|
|
2238
2331
|
"name": "estudio_motion",
|
|
2239
|
-
"description": "A MAQUINA DE MOTION: voce so preenche o JSON (assunto, formato, marca, cenas) e ela monta no After Effects, poe os sons no tempo, mixa a -14 LUFS e confere os quadros. Prefira esta a escrever JSX.
|
|
2332
|
+
"description": "A MAQUINA DE MOTION: voce so preenche o JSON (assunto, formato, marca, cenas) e ela monta no After Effects, poe os sons no tempo, mixa a -14 LUFS e confere os quadros. Prefira esta a escrever JSX. Cada cena leva um papel (gancho|problema|processo|prova|virada|cta) e o conteudo (titulo, itens[], valor, rotulo, antes, depois, texto, autor, botao, fala); o DIRETOR escolhe a primitiva (38: cinetico, interface, cursor, prompt, mergulho, barras, linha, mapa, chat, notificacoes, relogio, calendario, funil, carimbo, heroi, split...), as 15 transicoes, a camera e a tipografia, com assinatura propria por video. Pode mandar tipo, transicoes, camera, tipografia, semente. Elementos reais do assunto (3D, GIF, logos), som real CC0, voz com tempo por palavra e legenda. Marque a palavra-chave com *asteriscos*.",
|
|
2240
2333
|
"parameters": {
|
|
2241
2334
|
"type": "object",
|
|
2242
2335
|
"properties": {
|
|
@@ -2272,7 +2365,51 @@
|
|
|
2272
2365
|
"items": {
|
|
2273
2366
|
"type": "object"
|
|
2274
2367
|
},
|
|
2275
|
-
"description": "[{tipo,
|
|
2368
|
+
"description": "[{papel, tipo?, fala?, titulo, itens, valor, rotulo, antes, depois, texto, autor, botao}]; o diretor completa o tipo; minimo 5 cenas para 15 s"
|
|
2369
|
+
},
|
|
2370
|
+
"elementos": {
|
|
2371
|
+
"type": "array",
|
|
2372
|
+
"items": {
|
|
2373
|
+
"type": "string"
|
|
2374
|
+
},
|
|
2375
|
+
"description": "Termos dos elementos reais do assunto (\"pao\", \"relogio\"); o diretor completa ate 3 ou mais"
|
|
2376
|
+
},
|
|
2377
|
+
"transicoes": {
|
|
2378
|
+
"type": "array",
|
|
2379
|
+
"items": {
|
|
2380
|
+
"type": "string"
|
|
2381
|
+
},
|
|
2382
|
+
"description": "Opcional: encolhe|empurra|mascara|zoom|flash|persiana|chicote|desfoque|cortina|pontos|giro|tremor|abre|dissolve|corte"
|
|
2383
|
+
},
|
|
2384
|
+
"camera": {
|
|
2385
|
+
"type": "string",
|
|
2386
|
+
"enum": [
|
|
2387
|
+
"deriva",
|
|
2388
|
+
"pulso",
|
|
2389
|
+
"mao",
|
|
2390
|
+
"trilho",
|
|
2391
|
+
"orbita",
|
|
2392
|
+
"push",
|
|
2393
|
+
"chicote",
|
|
2394
|
+
"cinema"
|
|
2395
|
+
]
|
|
2396
|
+
},
|
|
2397
|
+
"tipografia": {
|
|
2398
|
+
"type": "string",
|
|
2399
|
+
"enum": [
|
|
2400
|
+
"editorial",
|
|
2401
|
+
"poster",
|
|
2402
|
+
"tecnica",
|
|
2403
|
+
"manuscrita",
|
|
2404
|
+
"pesada",
|
|
2405
|
+
"serifa_fina",
|
|
2406
|
+
"arredondada",
|
|
2407
|
+
"geometrica"
|
|
2408
|
+
]
|
|
2409
|
+
},
|
|
2410
|
+
"semente": {
|
|
2411
|
+
"type": "number",
|
|
2412
|
+
"description": "Semente do video; sem ela cada video sorteia uma nova e foge das assinaturas recentes"
|
|
2276
2413
|
},
|
|
2277
2414
|
"voz": {
|
|
2278
2415
|
"type": "string",
|
|
@@ -2297,6 +2434,10 @@
|
|
|
2297
2434
|
"entrega": {
|
|
2298
2435
|
"type": "boolean",
|
|
2299
2436
|
"description": "Pacote pra postar (mp4, capa, legenda .srt, post.txt); padrao true"
|
|
2437
|
+
},
|
|
2438
|
+
"previa": {
|
|
2439
|
+
"type": "boolean",
|
|
2440
|
+
"description": "true: rascunho em meia resolucao, em segundos e sem mix final, para a pessoa aprovar antes do final"
|
|
2300
2441
|
}
|
|
2301
2442
|
},
|
|
2302
2443
|
"required": [
|
|
@@ -2427,6 +2568,51 @@
|
|
|
2427
2568
|
}
|
|
2428
2569
|
}
|
|
2429
2570
|
},
|
|
2571
|
+
{
|
|
2572
|
+
"type": "function",
|
|
2573
|
+
"function": {
|
|
2574
|
+
"name": "editor_pro",
|
|
2575
|
+
"description": "Edicao COMPLETA de video FALADO da pessoa (motor EDIT IA PRO): corte da melhor tomada sem respiro, voz limpa no volume certo, legenda animada, gancho, camera viva, pecas, b-roll, sons, trilha com ducking e portao de qualidade. acao \"cortar\" devolve a fala com tempos; \"montar\" recebe o plano; \"editar\" faz tudo de uma vez. Sai em <pasta>/final.mp4.",
|
|
2576
|
+
"parameters": {
|
|
2577
|
+
"type": "object",
|
|
2578
|
+
"properties": {
|
|
2579
|
+
"acao": {
|
|
2580
|
+
"type": "string",
|
|
2581
|
+
"enum": [
|
|
2582
|
+
"editar",
|
|
2583
|
+
"cortar",
|
|
2584
|
+
"montar",
|
|
2585
|
+
"preparar",
|
|
2586
|
+
"estado"
|
|
2587
|
+
]
|
|
2588
|
+
},
|
|
2589
|
+
"video": {
|
|
2590
|
+
"type": "string",
|
|
2591
|
+
"description": "caminho da gravacao"
|
|
2592
|
+
},
|
|
2593
|
+
"pasta": {
|
|
2594
|
+
"type": "string",
|
|
2595
|
+
"description": "a pasta da edicao (devolvida por cortar)"
|
|
2596
|
+
},
|
|
2597
|
+
"prompt": {
|
|
2598
|
+
"type": "string",
|
|
2599
|
+
"description": "nomes dificeis da fala, separados por virgula"
|
|
2600
|
+
},
|
|
2601
|
+
"plano": {
|
|
2602
|
+
"type": "object",
|
|
2603
|
+
"description": "{gancho:\"Texto *destaque*\", pecas:[...], broll:[{busca,de,ate,rotulo}], blocos?, estilo?, legenda?, correcoes?}"
|
|
2604
|
+
},
|
|
2605
|
+
"trilha": {
|
|
2606
|
+
"type": "string",
|
|
2607
|
+
"description": "auto | caminho de um audio | nenhuma"
|
|
2608
|
+
}
|
|
2609
|
+
},
|
|
2610
|
+
"required": [
|
|
2611
|
+
"acao"
|
|
2612
|
+
]
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
},
|
|
2430
2616
|
{
|
|
2431
2617
|
"type": "function",
|
|
2432
2618
|
"function": {
|
package/lib/local/motor.js
CHANGED
|
@@ -18,6 +18,7 @@ const cerebro = require('./cerebro.json');
|
|
|
18
18
|
const entrada = require('./entrada.js');
|
|
19
19
|
const provedor = require('./provedor.js');
|
|
20
20
|
const catalogo = require('../catalogo.js');
|
|
21
|
+
const chamadaTorta = require('../chamada-torta');
|
|
21
22
|
|
|
22
23
|
// A conversa pode ocupar isto, em tokens, além do prompt e das ferramentas.
|
|
23
24
|
// Folgado: os modelos de hoje têm 128 mil ou mais de contexto.
|
|
@@ -191,8 +192,10 @@ async function requestAct(_servidor, body, emit) {
|
|
|
191
192
|
});
|
|
192
193
|
chamadas.forEach((tc, i) => {
|
|
193
194
|
if (!tc || !tc.name) return;
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
/* JSON quebrado: tenta recuperar; se não der, o act.js devolve o erro
|
|
196
|
+
(com os argumentos crus) ao modelo — ver lib/chamada-torta.js. */
|
|
197
|
+
const rec = chamadaTorta.recuperar(tc.arguments);
|
|
198
|
+
const args = rec.ok ? rec.args : chamadaTorta.marcar(tc.arguments);
|
|
196
199
|
emit({ tool_call: { id: tc.id || `local_${Date.now()}_${i}`, name: tc.name, arguments: args } });
|
|
197
200
|
});
|
|
198
201
|
if (texto_) emit({ assistant_message: { content: texto_ } });
|
package/lib/mcp.js
CHANGED
|
@@ -54,28 +54,117 @@ function servidores(pasta = process.cwd()) {
|
|
|
54
54
|
const out = process.env.PRIMOCODE_SEM_MCP === '1' ? {} : { ...PADRAO };
|
|
55
55
|
for (const arq of [CONFIG, path.join(pasta, '.mcp.json')]) {
|
|
56
56
|
const j = lerJson(arq);
|
|
57
|
-
|
|
57
|
+
const doProjeto = arq !== CONFIG;
|
|
58
|
+
for (const [nome, s] of Object.entries((j && j.mcpServers) || {})) {
|
|
59
|
+
if (s && (s.command || s.url)) out[nome] = doProjeto ? marcarProjeto(s, nome, pasta) : s;
|
|
60
|
+
}
|
|
58
61
|
}
|
|
59
62
|
return out;
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
/* ── SERVIDOR QUE VEM DO PROJETO ─────────────────────────────────────────
|
|
66
|
+
* O .mcp.json de um repo clonado roda comando na máquina de quem abre. Então
|
|
67
|
+
* servidor do PROJETO (não o do ~/.primocode/mcp.json) só sobe com o sim da
|
|
68
|
+
* pessoa, mesmo no modo auto — lembrado por projeto + configuração exata
|
|
69
|
+
* (hash de command+args+env…): mudou o .mcp.json, pergunta de novo. Sem
|
|
70
|
+
* ninguém para perguntar (headless), não sobe. */
|
|
71
|
+
const APROVADOS = path.join(CASA, 'mcp-aprovados.json');
|
|
72
|
+
let perguntador = null; // async ({ nome, pasta, def, resumo }) → boolean
|
|
73
|
+
let filaDePerguntas = Promise.resolve();
|
|
74
|
+
|
|
75
|
+
function marcarProjeto(def, nome, pasta) {
|
|
76
|
+
const c = { ...def };
|
|
77
|
+
Object.defineProperty(c, '_projeto', { value: { nome, pasta: path.resolve(pasta) }, enumerable: false });
|
|
78
|
+
return c;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function hashDoServidor(def) {
|
|
82
|
+
const d = def || {};
|
|
83
|
+
const base = JSON.stringify([d.type || '', d.command || '', d.args || [], d.env || {}, d.url || '', d.headers || {}]);
|
|
84
|
+
return require('crypto').createHash('sha256').update(base).digest('hex');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function usarPerguntador(fn) { perguntador = typeof fn === 'function' ? fn : null; }
|
|
88
|
+
|
|
89
|
+
function jaAprovado(pasta, def) {
|
|
90
|
+
const j = lerJson(APROVADOS) || {};
|
|
91
|
+
return !!(j[path.resolve(pasta)] || {})[hashDoServidor(def)];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function aprovarProjeto(pasta, def, nome = '') {
|
|
95
|
+
const j = lerJson(APROVADOS) || {};
|
|
96
|
+
const k = path.resolve(pasta);
|
|
97
|
+
j[k] = j[k] || {};
|
|
98
|
+
j[k][hashDoServidor(def)] = nome || true;
|
|
99
|
+
fs.mkdirSync(CASA, { recursive: true });
|
|
100
|
+
fs.writeFileSync(APROVADOS, JSON.stringify(j, null, 2), { mode: 0o600 });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resumoDoServidor(def) {
|
|
104
|
+
return def.url ? `${def.url}` : [def.command, ...(def.args || [])].join(' ');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Lança se o servidor do projeto não foi aprovado (e não há como perguntar). */
|
|
108
|
+
async function autorizarProjeto(def, nomeDado) {
|
|
109
|
+
const info = def && def._projeto;
|
|
110
|
+
if (!info) return;
|
|
111
|
+
const nome = nomeDado || info.nome;
|
|
112
|
+
if (jaAprovado(info.pasta, def)) return;
|
|
113
|
+
const recusa = (porque) => {
|
|
114
|
+
process.stderr.write(`[mcp] servidor "${nome}" do .mcp.json do projeto NÃO foi iniciado: ${porque}\n`);
|
|
115
|
+
throw new Error(`o servidor MCP "${nome}" vem do .mcp.json deste projeto e não foi aprovado (${porque}). `
|
|
116
|
+
+ 'Rode o PrimoCode neste terminal para aprovar, ou mova o servidor para ~/.primocode/mcp.json.');
|
|
117
|
+
};
|
|
118
|
+
if (!perguntador) recusa('sem terminal para pedir o sim');
|
|
119
|
+
const ok = await (filaDePerguntas = filaDePerguntas.catch(() => {}).then(async () => {
|
|
120
|
+
if (jaAprovado(info.pasta, def)) return true;
|
|
121
|
+
const r = await perguntador({ nome, pasta: info.pasta, def, resumo: resumoDoServidor(def) });
|
|
122
|
+
if (r) aprovarProjeto(info.pasta, def, nome);
|
|
123
|
+
return !!r;
|
|
124
|
+
}));
|
|
125
|
+
if (!ok) recusa('a pessoa não aprovou');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Variáveis que o `${VAR}` de um servidor do PROJETO pode ler. O resto vira
|
|
129
|
+
vazio: um .mcp.json clonado não pode mandar GITHUB_TOKEN para uma URL. Quem
|
|
130
|
+
quer liberar mais escreve "envPermitidas": ["X"] no ~/.primocode/mcp.json. */
|
|
131
|
+
const ENV_PERMITIDAS = ['HOME', 'USER', 'PATH', 'PWD', 'TMPDIR', 'LANG', 'SHELL'];
|
|
132
|
+
|
|
133
|
+
function permitidasDoUsuario() {
|
|
134
|
+
const j = lerJson(CONFIG);
|
|
135
|
+
return Array.isArray(j && j.envPermitidas) ? j.envPermitidas.map(String) : [];
|
|
136
|
+
}
|
|
137
|
+
|
|
62
138
|
/** Abre um servidor, faz o aperto de mão e devolve {pedir, fechar}. */
|
|
63
139
|
/* `${VAR}` no argumento vira o valor do env. Sem shell no meio (spawn puro),
|
|
64
140
|
ninguém expandiria — e é assim que o token entra no header do mcp-remote
|
|
65
141
|
sem ficar escrito duas vezes no mcp.json. */
|
|
66
|
-
function expandir(args, env) {
|
|
67
|
-
|
|
142
|
+
function expandir(args, env, { projeto = false, permitidas = [], aviso = null } = {}) {
|
|
143
|
+
const livres = new Set([...ENV_PERMITIDAS, ...permitidas]);
|
|
144
|
+
return (args || []).map((a) => String(a).replace(/\$\{([A-Z0-9_]+)\}/g, (_, k) => {
|
|
145
|
+
if (projeto && !livres.has(k)) {
|
|
146
|
+
(aviso || ((m) => process.stderr.write(`[mcp] ${m}\n`)))(`\${${k}} no .mcp.json do projeto não é permitida e virou vazia`);
|
|
147
|
+
return '';
|
|
148
|
+
}
|
|
149
|
+
return env[k] != null ? env[k] : '';
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** O que o `${VAR}` deste servidor pode ver: o do projeto só lê a lista permitida. */
|
|
154
|
+
function opcoesDeExpansao(def) {
|
|
155
|
+
return def && def._projeto ? { projeto: true, permitidas: [...permitidasDoUsuario(), ...Object.keys(def.env || {})] } : {};
|
|
68
156
|
}
|
|
69
157
|
|
|
70
158
|
/** Sobe/abre o servidor: `url` → HTTP, senão stdio. Mesma interface: {pedir, fechar}. */
|
|
71
|
-
function conectar(def, opcoes = {}) {
|
|
159
|
+
async function conectar(def, opcoes = {}) {
|
|
160
|
+
await autorizarProjeto(def, opcoes.nome);
|
|
72
161
|
return def && def.url ? conectarHttp(def, opcoes) : conectarStdio(def, opcoes);
|
|
73
162
|
}
|
|
74
163
|
|
|
75
164
|
function conectarStdio(def, { tetoMs = 60000 } = {}) {
|
|
76
165
|
return new Promise((resolve, reject) => {
|
|
77
166
|
const env = { ...process.env, ...(def.env || {}) };
|
|
78
|
-
const p = spawn(def.command, expandir(def.args, env), { env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
167
|
+
const p = spawn(def.command, expandir(def.args, env, opcoesDeExpansao(def)), { env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
79
168
|
let buf = '', id = 0;
|
|
80
169
|
const espera = new Map();
|
|
81
170
|
const relogio = setTimeout(() => { try { p.kill(); } catch {} reject(new Error('o servidor MCP não respondeu')); }, tetoMs);
|
|
@@ -154,7 +243,7 @@ function conectarHttp(def, { tetoMs = 60000, nome, buscar = globalThis.fetch } =
|
|
|
154
243
|
const env = { ...process.env, ...(def.env || {}) };
|
|
155
244
|
// ID token da conta do PrimoCode já logada (o preset primocode-clube usa no lugar de OAuth).
|
|
156
245
|
if (env.PRIMOCODE_ID_TOKEN == null) { try { env.PRIMOCODE_ID_TOKEN = require('./config.js').tokenDaConta() || ''; } catch { env.PRIMOCODE_ID_TOKEN = ''; } }
|
|
157
|
-
for (const [k, v] of Object.entries(def.headers || {})) h[k.toLowerCase()] = expandir([v], env)[0];
|
|
246
|
+
for (const [k, v] of Object.entries(def.headers || {})) h[k.toLowerCase()] = expandir([v], env, opcoesDeExpansao(def))[0];
|
|
158
247
|
const tk = nome && lerTokens()[nome];
|
|
159
248
|
if (tk && tk.access_token && !h.authorization) h.authorization = `Bearer ${tk.access_token}`;
|
|
160
249
|
if (sessao) h['mcp-session-id'] = sessao;
|
|
@@ -264,6 +353,7 @@ function ouvirCallback(estado, prazoMs) {
|
|
|
264
353
|
async function entrar(nome, { pasta, abrir = abrirUrl, aviso = () => {}, buscar = globalThis.fetch, prazoMs = 5 * 60000 } = {}) {
|
|
265
354
|
const def = servidores(pasta)[nome];
|
|
266
355
|
if (!def || !def.url) return { ok: false, error: `"${nome}" não é um servidor MCP remoto (type http com url) — veja primocode mcp list` };
|
|
356
|
+
try { await autorizarProjeto(def, nome); } catch (e) { return { ok: false, error: e.message }; }
|
|
267
357
|
let cb;
|
|
268
358
|
try {
|
|
269
359
|
// Sem token (o do header manual não conta): o 401 diz onde está o OAuth.
|
|
@@ -642,6 +732,14 @@ function dica(pasta) {
|
|
|
642
732
|
/** `primocode mcp …` */
|
|
643
733
|
async function main(argv) {
|
|
644
734
|
const [op, ...resto] = argv;
|
|
735
|
+
if (!perguntador && process.stdin.isTTY && process.stdout.isTTY) {
|
|
736
|
+
perguntador = ({ nome, resumo }) => new Promise((res) => {
|
|
737
|
+
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
738
|
+
rl.question(`O servidor MCP "${nome}" vem do .mcp.json deste projeto e vai rodar: ${resumo}\nIniciar? (s/N) `, (r) => {
|
|
739
|
+
rl.close(); res(/^(s|sim|y|yes)\b/i.test(String(r).trim()));
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
}
|
|
645
743
|
const json = (() => { const i = resto.indexOf('--json'); try { return i >= 0 ? JSON.parse(resto[i + 1] || '{}') : {}; } catch { return null; } })();
|
|
646
744
|
try {
|
|
647
745
|
if (!op || op === 'help') {
|
|
@@ -718,4 +816,4 @@ async function main(argv) {
|
|
|
718
816
|
}
|
|
719
817
|
}
|
|
720
818
|
|
|
721
|
-
module.exports = { servidores, conectar, listar, chamar, usar, dica, main, procurar, doRegistro, conectarNovo, CURADOS, expandir, entrar, sair, renovarToken };
|
|
819
|
+
module.exports = { usarPerguntador, aprovarProjeto, jaAprovado, hashDoServidor, ENV_PERMITIDAS, servidores, conectar, listar, chamar, usar, dica, main, procurar, doRegistro, conectarNovo, CURADOS, expandir, entrar, sair, renovarToken };
|