primocode 10.13.0 → 10.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -12
- package/bin/primocode.js +148 -8
- package/lib/act.js +56 -3
- package/lib/catalogo.js +12 -1
- package/lib/config.js +1 -0
- package/lib/estudio/cota.js +157 -0
- package/lib/estudio/dados/projetos/clube-ad5.json +105 -0
- package/lib/estudio/dados/projetos/clube-ae-sem-memoria.json +107 -0
- package/lib/estudio/dados/projetos/clube-agent.json +120 -0
- package/lib/estudio/dados/projetos/clube-antes-e-agora.json +86 -0
- package/lib/estudio/dados/projetos/clube-apresentando-clube.json +110 -0
- package/lib/estudio/dados/projetos/clube-bluevisa.json +106 -0
- package/lib/estudio/dados/projetos/clube-caught-in-the-fire.json +127 -0
- package/lib/estudio/dados/projetos/clube-chat-cliente-satisfeito.json +98 -0
- package/lib/estudio/dados/projetos/clube-chatgpt-anuncios.json +120 -0
- package/lib/estudio/dados/projetos/clube-claude-dj.json +140 -0
- package/lib/estudio/dados/projetos/clube-claude-mcp.json +142 -0
- package/lib/estudio/dados/projetos/clube-cleanui.json +106 -0
- package/lib/estudio/dados/projetos/clube-clinica-take-unico.json +128 -0
- package/lib/estudio/dados/projetos/clube-clinica.json +104 -0
- package/lib/estudio/dados/projetos/clube-clube-opener.json +125 -0
- package/lib/estudio/dados/projetos/clube-clube.json +108 -0
- package/lib/estudio/dados/projetos/clube-codigo.json +101 -0
- package/lib/estudio/dados/projetos/clube-distilbook.json +104 -0
- package/lib/estudio/dados/projetos/clube-drive-upload.json +115 -0
- package/lib/estudio/dados/projetos/clube-dub.json +119 -0
- package/lib/estudio/dados/projetos/clube-enxovaly.json +105 -0
- package/lib/estudio/dados/projetos/clube-fecha7.json +118 -0
- package/lib/estudio/dados/projetos/clube-filatov.json +128 -0
- package/lib/estudio/dados/projetos/clube-frame.json +108 -0
- package/lib/estudio/dados/projetos/clube-glass.json +118 -0
- package/lib/estudio/dados/projetos/clube-letra-em-apps.json +84 -0
- package/lib/estudio/dados/projetos/clube-letra-globo-3d.json +111 -0
- package/lib/estudio/dados/projetos/clube-letra-nos-apps.json +146 -0
- package/lib/estudio/dados/projetos/clube-letra-seis-mundos.json +100 -0
- package/lib/estudio/dados/projetos/clube-lula-3.json +146 -0
- package/lib/estudio/dados/projetos/clube-morph.json +119 -0
- package/lib/estudio/dados/projetos/clube-motion-designer.json +115 -0
- package/lib/estudio/dados/projetos/clube-motion-pacote.json +112 -0
- package/lib/estudio/dados/projetos/clube-movimento.json +103 -0
- package/lib/estudio/dados/projetos/clube-not-like-us.json +117 -0
- package/lib/estudio/dados/projetos/clube-pinterest-inspiracao.json +86 -0
- package/lib/estudio/dados/projetos/clube-pixel-e-tinta.json +108 -0
- package/lib/estudio/dados/projetos/clube-ponte.json +118 -0
- package/lib/estudio/dados/projetos/clube-processo-edit-tonal.json +126 -0
- package/lib/estudio/dados/projetos/clube-processo-motion-100-ia.json +109 -0
- package/lib/estudio/dados/projetos/clube-promo.json +114 -0
- package/lib/estudio/dados/projetos/clube-proposito.json +115 -0
- package/lib/estudio/dados/projetos/clube-rankhog.json +128 -0
- package/lib/estudio/dados/projetos/clube-recursao.json +116 -0
- package/lib/estudio/dados/projetos/clube-revelar.json +118 -0
- package/lib/estudio/dados/projetos/clube-robo.json +125 -0
- package/lib/estudio/dados/projetos/clube-shelby-ep2.json +144 -0
- package/lib/estudio/dados/projetos/clube-shelby.json +97 -0
- package/lib/estudio/dados/projetos/clube-solar-eclipse.json +96 -0
- package/lib/estudio/dados/projetos/clube-soldier.json +128 -0
- package/lib/estudio/dados/projetos/clube-som-de-cena.json +108 -0
- package/lib/estudio/dados/projetos/clube-spotify-candidatura.json +112 -0
- package/lib/estudio/dados/projetos/clube-tda-app.json +118 -0
- package/lib/estudio/dados/projetos/clube-tda-ia.json +75 -0
- package/lib/estudio/dados/projetos/clube-telegram-chat.json +118 -0
- package/lib/estudio/dados/projetos/clube-telegram-ilha.json +106 -0
- package/lib/estudio/dados/projetos/clube-tennessee-nike.json +132 -0
- package/lib/estudio/dados/projetos/clube-tudo-se-move.json +112 -0
- package/lib/estudio/dados/projetos/clube-uiad.json +107 -0
- package/lib/estudio/dados/projetos/clube-viralgrow.json +108 -0
- package/lib/estudio/dados/projetos/clube-whose-side.json +140 -0
- package/lib/estudio/dados/projetos/clube-youtube-inscrever.json +108 -0
- package/lib/estudio/dados/projetos/elementos-do-site-em-movimento.json +49 -0
- package/lib/estudio/dados/projetos/explainer-narrado.json +51 -0
- package/lib/estudio/dados/projetos/lancamento-de-produto.json +51 -0
- package/lib/estudio/dados/projetos/motion-so-com-sons.json +50 -0
- package/lib/estudio/dados/projetos/titulo-em-after-effects.json +51 -0
- package/lib/estudio/dados/projetos/tour-de-saas.json +50 -0
- package/lib/estudio/dados/projetos-ae/antes-e-agora/ae/cenas.jsx +733 -0
- package/lib/estudio/dados/projetos-ae/antes-e-agora/assets/fazer_assets.py +88 -0
- package/lib/estudio/dados/projetos-ae/letra-em-apps/ae/cenas.jsx +402 -0
- package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/preparar_assets.py +17 -0
- package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/svg_para_ae.py +25 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/BRIEF-AGENTES.md +31 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/README.md +53 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ROTEIRO-CENAS.md +69 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/A-agenda.jsx +148 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/B-docs.jsx +174 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/C-alttab.jsx +113 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/D-spotify.jsx +118 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/E-notas.jsx +146 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/F-jornal1.jsx +144 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/G-google.jsx +153 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/H-mapa.jsx +176 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/I-imagens.jsx +132 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/J-jornal2.jsx +191 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/K-maps.jsx +184 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera-movimentos.jsx +48 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera.jsx +57 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/card-spotify.jsx +98 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/digitar.jsx +38 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/jornal-comum.jsx +196 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra.jsx +23 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra_automatica.py +83 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/montagem.jsx +74 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/render_html.py +13 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/util.jsx +162 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-midia-comum.jsx +114 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/wg-util.jsx +360 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/F-jornal1/render_html.py +13 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/render_w-midia.py +26 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/inclin.py +30 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_d.py +36 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_e.py +27 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ajustar_movimentos.py +61 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ancorar.py +74 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/assar.py +47 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/camera3d_jornal.py +172 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/comparar.py +32 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/dados_ae_jornal.py +46 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/folha_contato.py +28 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/homografia_jornal.py +74 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/integrar.py +44 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/medir_quadro_a_quadro.py +32 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/mesa_2d.py +64 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/plot_cenas.py +11 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/retificar_quadros.py +38 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/assar_ab.py +52 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/medir_ab.py +59 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ajustar_h.py +111 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_corrige_cam.py +17 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ecc_cadeia.py +80 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_finalizar_cam.py +65 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_mosaico.py +61 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/01-gramatica-e-roteiro.md +55 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/02-camera.md +66 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/03-montagem-no-ae.md +50 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/04-render-e-conferencia.md +43 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/05-abertura-celular-mergulho.md +43 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces.md +42 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/atraso_imagem.py +31 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/fundo.py +56 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/juntar.py +45 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/montar.py +177 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/rastreio.py +74 -0
- package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/sincronia.py +18 -0
- package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/ae/cenas.jsx +530 -0
- package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/assets/fazer_assets.py +107 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/assets/pessoas/figurinha.py +46 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/BRIEF-A.md +83 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/audio.py +179 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/confere.py +20 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte1.js +1 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte2.js +1 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/gerar_emojis.py +11 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/img/filmes/_indice.js +122 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/base.js +380 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/main.js +86 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1a.js +169 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1b.js +455 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1c.js +389 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p2.js +379 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/video.py +62 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/README.md +65 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/oraculo_take.py +137 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/render_take.py +388 -0
- package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/take.js +235 -0
- package/lib/estudio/editor.js +290 -0
- package/lib/estudio/effectcraft.js +152 -0
- package/lib/estudio/exportar.js +181 -0
- package/lib/estudio/index.js +97 -2
- package/lib/estudio/kit-ae/base.jsx +1146 -0
- package/lib/estudio/kit-ae/cenas.jsx +395 -0
- package/lib/estudio/kit-ae/primitivas.jsx +911 -0
- package/lib/estudio/maquina-diretor.js +393 -0
- package/lib/estudio/maquina-elementos.js +134 -0
- package/lib/estudio/maquina-layout.js +198 -0
- package/lib/estudio/maquina-som.js +478 -0
- package/lib/estudio/maquina-trilha.js +160 -0
- package/lib/estudio/maquina-voz.js +429 -0
- package/lib/estudio/maquina.js +516 -0
- package/lib/estudio/projetos.js +72 -0
- package/lib/headless.js +71 -0
- package/lib/hooks.js +72 -0
- package/lib/limites.js +100 -0
- package/lib/local/cerebro.json +398 -12
- package/lib/local/entrada.js +46 -2
- package/lib/marca-kit.js +312 -0
- package/lib/mcp.js +286 -6
- package/lib/perigo.js +31 -3
- package/lib/planos.js +49 -19
- package/lib/preparar.js +111 -0
- package/lib/retomar.js +55 -0
- package/lib/sandbox.js +197 -0
- package/lib/skills.js +3 -1
- package/lib/tools.js +13 -4
- package/lib/ui.js +1 -1
- package/package.json +4 -2
- package/skills/after-effects/SKILL.md +52 -0
- package/skills/maquina-de-motion/SKILL.md +84 -0
package/lib/marca-kit.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* marca-kit.js — o KIT DE MARCA: a pessoa cria uma vez, todo vídeo e site usa.
|
|
3
|
+
*
|
|
4
|
+
* ~/.primocode/marcas/<nome>/marca.json nome, cores {fundo,destaque,tinta,apoio},
|
|
5
|
+
* fontes {titulo,texto} (Google Fonts), tom, slogan,
|
|
6
|
+
* site, redes, assinatura (motion preferida, opcional)
|
|
7
|
+
* ~/.primocode/marcas/<nome>/logo.png|svg
|
|
8
|
+
* ~/.primocode/marcas/.ativa nome do kit ativo (global)
|
|
9
|
+
* <projeto>/.primocode/marca fixa o kit naquela pasta (ganha do global)
|
|
10
|
+
*
|
|
11
|
+
* Comandos: `primocode marca criar|listar|usar <nome> [aqui]|mostrar|apagar <nome>` e `/marca`.
|
|
12
|
+
* O Estúdio chama `preencher(marca, {pastas})`: o kit entra por baixo, o que o
|
|
13
|
+
* roteiro já trouxe ganha, campo a campo.
|
|
14
|
+
*/
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const PAPEIS = ['fundo', 'destaque', 'tinta', 'apoio'];
|
|
20
|
+
|
|
21
|
+
function raiz() { return process.env.PRIMOCODE_MARCAS || path.join(os.homedir(), '.primocode', 'marcas'); }
|
|
22
|
+
|
|
23
|
+
function slug(nome) {
|
|
24
|
+
return String(nome || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()
|
|
25
|
+
.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40);
|
|
26
|
+
}
|
|
27
|
+
const hexOk = (h) => /^#[0-9a-f]{6}$/i.test(String(h || ''));
|
|
28
|
+
function normHex(h) {
|
|
29
|
+
let s = String(h || '').trim();
|
|
30
|
+
if (/^[0-9a-f]{3}$/i.test(s)) s = '#' + s;
|
|
31
|
+
if (/^#[0-9a-f]{3}$/i.test(s)) s = '#' + s.slice(1).split('').map((x) => x + x).join('');
|
|
32
|
+
if (/^[0-9a-f]{6}$/i.test(s)) s = '#' + s;
|
|
33
|
+
return hexOk(s) ? s.toLowerCase() : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── contraste (WCAG) ─────────────────────────────────────────────────────
|
|
37
|
+
function rgb(h) { const n = parseInt(h.slice(1), 16); return [n >> 16, (n >> 8) & 255, n & 255]; }
|
|
38
|
+
function luz(h) {
|
|
39
|
+
const [r, g, b] = rgb(h).map((v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); });
|
|
40
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
41
|
+
}
|
|
42
|
+
function contraste(a, b) {
|
|
43
|
+
const [x, y] = [luz(a), luz(b)].sort((p, q) => q - p);
|
|
44
|
+
return (x + 0.05) / (y + 0.05);
|
|
45
|
+
}
|
|
46
|
+
function misturar(a, b, t) {
|
|
47
|
+
const A = rgb(a), B = rgb(b);
|
|
48
|
+
return '#' + A.map((v, i) => Math.round(v + (B[i] - v) * t).toString(16).padStart(2, '0')).join('');
|
|
49
|
+
}
|
|
50
|
+
/** A cor mais perto de `cor` que passa de `min` sobre `fundo` (clareia ou escurece em direção ao extremo que contrasta). */
|
|
51
|
+
function ajustar(cor, fundo, min = 4.5) {
|
|
52
|
+
const alvo = luz(fundo) > 0.18 ? '#000000' : '#ffffff';
|
|
53
|
+
for (let t = 0.05; t <= 1.001; t += 0.05) {
|
|
54
|
+
const c = misturar(cor, alvo, t);
|
|
55
|
+
if (contraste(c, fundo) >= min) return c;
|
|
56
|
+
}
|
|
57
|
+
return alvo;
|
|
58
|
+
}
|
|
59
|
+
/** Confere tinta×fundo e destaque×fundo. Devolve [{par, razao, ok, sugestao}]. */
|
|
60
|
+
function validarContraste(cores = {}, min = 4.5) {
|
|
61
|
+
const out = [];
|
|
62
|
+
for (const [par, k] of [['tinta×fundo', 'tinta'], ['destaque×fundo', 'destaque']]) {
|
|
63
|
+
if (!hexOk(cores[k]) || !hexOk(cores.fundo)) continue;
|
|
64
|
+
const razao = Math.round(contraste(cores[k], cores.fundo) * 100) / 100;
|
|
65
|
+
const ok = razao >= min;
|
|
66
|
+
out.push({ par, razao, ok, ...(ok ? {} : { sugestao: { [k]: ajustar(cores[k], cores.fundo, min) } }) });
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── disco ────────────────────────────────────────────────────────────────
|
|
72
|
+
function pastaDe(nome) { const s = slug(nome); return s ? path.join(raiz(), s) : null; }
|
|
73
|
+
function ler(nome) {
|
|
74
|
+
const p = pastaDe(nome);
|
|
75
|
+
if (!p) return null;
|
|
76
|
+
try {
|
|
77
|
+
const m = JSON.parse(fs.readFileSync(path.join(p, 'marca.json'), 'utf8'));
|
|
78
|
+
const logo = ['logo.svg', 'logo.png'].map((f) => path.join(p, f)).find((f) => fs.existsSync(f));
|
|
79
|
+
return { ...m, ...(logo ? { logo } : {}), _pasta: p };
|
|
80
|
+
} catch { return null; }
|
|
81
|
+
}
|
|
82
|
+
function listar() {
|
|
83
|
+
try {
|
|
84
|
+
return fs.readdirSync(raiz(), { withFileTypes: true }).filter((d) => d.isDirectory())
|
|
85
|
+
.map((d) => ler(d.name)).filter(Boolean);
|
|
86
|
+
} catch { return []; }
|
|
87
|
+
}
|
|
88
|
+
function validar(m) {
|
|
89
|
+
const erros = [];
|
|
90
|
+
if (!m || !String(m.nome || '').trim()) erros.push('falta o nome.');
|
|
91
|
+
for (const k of PAPEIS) if (m.cores && m.cores[k] && !hexOk(m.cores[k])) erros.push(`cor ${k} inválida: ${m.cores[k]}`);
|
|
92
|
+
return erros;
|
|
93
|
+
}
|
|
94
|
+
/** Grava o kit. `logoArquivo` (opcional) é copiado para logo.png|svg. */
|
|
95
|
+
function salvar(m, { logoArquivo } = {}) {
|
|
96
|
+
const erros = validar(m);
|
|
97
|
+
if (erros.length) return { ok: false, error: erros.join(' ') };
|
|
98
|
+
const p = pastaDe(m.nome);
|
|
99
|
+
if (!p) return { ok: false, error: 'o nome não serve de pasta.' };
|
|
100
|
+
fs.mkdirSync(p, { recursive: true });
|
|
101
|
+
const { logo, _pasta, ...dados } = m;
|
|
102
|
+
fs.writeFileSync(path.join(p, 'marca.json'), JSON.stringify({ ...dados, criada: dados.criada || new Date().toISOString() }, null, 2) + '\n');
|
|
103
|
+
if (logoArquivo && fs.existsSync(logoArquivo)) {
|
|
104
|
+
const ext = /\.svg$/i.test(logoArquivo) ? '.svg' : '.png';
|
|
105
|
+
for (const e of ['.svg', '.png']) try { fs.unlinkSync(path.join(p, 'logo' + e)); } catch { /* não havia */ }
|
|
106
|
+
fs.copyFileSync(logoArquivo, path.join(p, 'logo' + ext));
|
|
107
|
+
}
|
|
108
|
+
return { ok: true, pasta: p, kit: ler(m.nome) };
|
|
109
|
+
}
|
|
110
|
+
function apagar(nome) {
|
|
111
|
+
const p = pastaDe(nome);
|
|
112
|
+
if (!p || !fs.existsSync(path.join(p, 'marca.json'))) return { ok: false, error: `não há kit "${nome}".` };
|
|
113
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
114
|
+
if (ativaGlobal() === slug(nome)) try { fs.unlinkSync(path.join(raiz(), '.ativa')); } catch { /* ok */ }
|
|
115
|
+
return { ok: true };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── ativa: pino do projeto ganha da global ────────────────────────────────
|
|
119
|
+
function ativaGlobal() { try { return fs.readFileSync(path.join(raiz(), '.ativa'), 'utf8').trim() || null; } catch { return null; } }
|
|
120
|
+
function usar(nome, { aqui = null } = {}) {
|
|
121
|
+
const k = ler(nome);
|
|
122
|
+
if (!k) return { ok: false, error: `não há kit "${nome}". Veja: primocode marca listar` };
|
|
123
|
+
if (aqui) {
|
|
124
|
+
fs.mkdirSync(path.join(aqui, '.primocode'), { recursive: true });
|
|
125
|
+
fs.writeFileSync(path.join(aqui, '.primocode', 'marca'), slug(nome) + '\n');
|
|
126
|
+
return { ok: true, kit: k, onde: 'projeto' };
|
|
127
|
+
}
|
|
128
|
+
fs.mkdirSync(raiz(), { recursive: true });
|
|
129
|
+
fs.writeFileSync(path.join(raiz(), '.ativa'), slug(nome) + '\n');
|
|
130
|
+
return { ok: true, kit: k, onde: 'global' };
|
|
131
|
+
}
|
|
132
|
+
function fixadaEm(pasta) {
|
|
133
|
+
let d = pasta ? path.resolve(pasta) : null;
|
|
134
|
+
for (let i = 0; d && i < 6; i++) {
|
|
135
|
+
try { const n = fs.readFileSync(path.join(d, '.primocode', 'marca'), 'utf8').trim(); if (n) return n; } catch { /* sobe */ }
|
|
136
|
+
const pai = path.dirname(d);
|
|
137
|
+
if (pai === d) break;
|
|
138
|
+
d = pai;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
/** O kit em vigor: pino do primeiro `pastas` que tiver, senão a global. */
|
|
143
|
+
function ativa(pastas = []) {
|
|
144
|
+
for (const p of [].concat(pastas).filter(Boolean)) {
|
|
145
|
+
const n = fixadaEm(p);
|
|
146
|
+
if (n) { const k = ler(n); if (k) return k; }
|
|
147
|
+
}
|
|
148
|
+
const g = ativaGlobal();
|
|
149
|
+
return g ? ler(g) : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* O gancho do Estúdio: preenche a `marca` do JSON com o kit, campo a campo
|
|
154
|
+
* (o que a pessoa/roteiro já pôs ganha). Sem kit ativo, devolve a marca intacta.
|
|
155
|
+
*/
|
|
156
|
+
function preencher(marca, { pastas = [] } = {}) {
|
|
157
|
+
const k = ativa(pastas);
|
|
158
|
+
if (!k) return marca;
|
|
159
|
+
const m = marca && typeof marca === 'object' ? marca : {};
|
|
160
|
+
const temCores = m.cores && (Array.isArray(m.cores) ? m.cores.length : Object.keys(m.cores).length);
|
|
161
|
+
const temFontes = m.fontes && (Array.isArray(m.fontes) ? m.fontes.length : Object.keys(m.fontes).length);
|
|
162
|
+
return {
|
|
163
|
+
...m,
|
|
164
|
+
nome: m.nome || k.nome,
|
|
165
|
+
cores: temCores ? (Array.isArray(m.cores) ? m.cores : { ...(k.cores || {}), ...m.cores }) : { ...(k.cores || {}) },
|
|
166
|
+
fontes: temFontes ? (Array.isArray(m.fontes) ? m.fontes : { ...(k.fontes || {}), ...m.fontes }) : { ...(k.fontes || {}) },
|
|
167
|
+
...(m.logo || k.logo ? { logo: m.logo || k.logo } : {}),
|
|
168
|
+
...(k.slogan && !m.slogan ? { slogan: k.slogan } : {}),
|
|
169
|
+
...(k.tom && !m.tom ? { tom: k.tom } : {}),
|
|
170
|
+
...(k.assinatura && !m.assinatura ? { assinatura: k.assinatura } : {}),
|
|
171
|
+
kit: k.nome,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── do site: logo e cores com o que já existe ────────────────────────────
|
|
176
|
+
async function doSite(entrada, { identidade = require('./estudio/identidade.js') } = {}) {
|
|
177
|
+
let url = String(entrada || '').trim();
|
|
178
|
+
if (!url) return { ok: false, error: 'sem site.' };
|
|
179
|
+
if (!/^https?:\/\//i.test(url)) {
|
|
180
|
+
if (!/\./.test(url)) { const a = await identidade.acharSite(url); if (!a || !a.ok) return { ok: false, error: (a && a.error) || 'não achei o site.' }; url = a.url || a.site; }
|
|
181
|
+
else url = 'https://' + url;
|
|
182
|
+
}
|
|
183
|
+
const r = await identidade.analisar(url);
|
|
184
|
+
if (!r.ok) return r;
|
|
185
|
+
const lista = ((r.cores && r.cores.marca) || []).map((x) => x.cor || x).filter(hexOk);
|
|
186
|
+
const todas = lista.length ? lista : ((r.cores && r.cores.todas) || []).map((x) => x.cor || x).filter(hexOk);
|
|
187
|
+
const cores = require('./estudio/marca.js').marcaEmObjeto({ cores: todas }).cores || {};
|
|
188
|
+
const f = r.fontes || {};
|
|
189
|
+
const nomes = [...(f.doGoogleFonts || []), ...(f.declaradas || []).map((x) => x.fonte)];
|
|
190
|
+
const unicas = [...new Set(nomes)];
|
|
191
|
+
const fontes = { ...(unicas[0] ? { titulo: unicas[0] } : {}), ...(unicas[0] ? { texto: unicas[1] || unicas[0] } : {}) };
|
|
192
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'marca-'));
|
|
193
|
+
let logoArquivo = null;
|
|
194
|
+
try {
|
|
195
|
+
const l = await identidade.pegarLogo(r.html || '', r.url, { pasta: tmp, nome: 'kit' });
|
|
196
|
+
if (l && l.ok && l.src) logoArquivo = path.join(tmp, l.src);
|
|
197
|
+
} catch { /* sem logo: a pessoa manda depois */ }
|
|
198
|
+
return { ok: true, url: r.url, nome: r.marca || '', slogan: r.descricao || '', cores, fontes, logoArquivo };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── criar (interativo curto) ─────────────────────────────────────────────
|
|
202
|
+
/**
|
|
203
|
+
* `perguntar(texto, padrao) → Promise<string>` e `dizer(texto)` são injetáveis (teste).
|
|
204
|
+
* Fluxo: nome → site (opcional) → confirma cores/fonte/logo → contraste → grava.
|
|
205
|
+
*/
|
|
206
|
+
async function criar({ perguntar, dizer = console.log, deps = {} } = {}) {
|
|
207
|
+
const nome = String(await perguntar('Nome da marca', '')).trim();
|
|
208
|
+
if (!nome) return { ok: false, error: 'sem nome, sem kit.' };
|
|
209
|
+
const site = String(await perguntar('Site (opcional, Enter pula)', '')).trim();
|
|
210
|
+
let achado = { cores: {}, fontes: {}, logoArquivo: null, slogan: '' };
|
|
211
|
+
if (site) {
|
|
212
|
+
dizer('Lendo o site...');
|
|
213
|
+
const r = await doSite(site, deps);
|
|
214
|
+
if (r.ok) { achado = r; dizer(`Achei: ${Object.entries(r.cores).map(([k, v]) => `${k} ${v}`).join(' · ') || 'sem cores'}${r.fontes.titulo ? ` · fonte ${r.fontes.titulo}` : ''}${r.logoArquivo ? ' · logo' : ' · sem logo'}`); }
|
|
215
|
+
else dizer(`Não deu pra ler o site (${r.error}). Sigo com o que você disser.`);
|
|
216
|
+
}
|
|
217
|
+
const cores = {};
|
|
218
|
+
for (const k of PAPEIS) {
|
|
219
|
+
const padrao = achado.cores[k] || (k === 'fundo' ? '#0b0b0f' : k === 'tinta' ? '#f5f5f5' : k === 'destaque' ? '#ff5a1f' : '');
|
|
220
|
+
const v = normHex(await perguntar(`Cor ${k}${k === 'apoio' ? ' (opcional)' : ''}`, padrao));
|
|
221
|
+
if (v) cores[k] = v;
|
|
222
|
+
}
|
|
223
|
+
let sugeriu = [];
|
|
224
|
+
for (let volta = 0; volta < 2; volta++) {
|
|
225
|
+
const ruins = validarContraste(cores).filter((x) => !x.ok);
|
|
226
|
+
if (!ruins.length) break;
|
|
227
|
+
for (const x of ruins) {
|
|
228
|
+
const [k, v] = Object.entries(x.sugestao)[0];
|
|
229
|
+
dizer(`Contraste baixo em ${x.par}: ${x.razao} (o mínimo é 4,5). Sugiro ${k} ${v}.`);
|
|
230
|
+
const ok = /^(s|sim|y|yes|)$/i.test(String(await perguntar(`Trocar ${k} por ${v}? (s/n)`, 's')).trim());
|
|
231
|
+
if (ok) { cores[k] = v; sugeriu.push(k); }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const titulo = String(await perguntar('Fonte do título (Google Fonts)', achado.fontes.titulo || '')).trim();
|
|
235
|
+
const texto = String(await perguntar('Fonte do texto (Google Fonts)', achado.fontes.texto || titulo)).trim();
|
|
236
|
+
const slogan = String(await perguntar('Slogan (opcional)', '')).trim();
|
|
237
|
+
const tom = String(await perguntar('Tom de voz (opcional, ex.: direto e simpático)', '')).trim();
|
|
238
|
+
const redes = String(await perguntar('@redes (opcional)', '')).trim();
|
|
239
|
+
let logoArquivo = achado.logoArquivo;
|
|
240
|
+
if (!logoArquivo) {
|
|
241
|
+
const l = String(await perguntar('Arquivo do logo (png/svg, opcional)', '')).trim().replace(/^~(?=\/)/, os.homedir());
|
|
242
|
+
if (l && fs.existsSync(l)) logoArquivo = l; else if (l) dizer('Não achei esse arquivo; fica sem logo.');
|
|
243
|
+
}
|
|
244
|
+
const r = salvar({
|
|
245
|
+
nome, cores,
|
|
246
|
+
...((titulo || texto) ? { fontes: { ...(titulo ? { titulo } : {}), ...(texto ? { texto } : {}) } } : {}),
|
|
247
|
+
...(slogan ? { slogan } : {}), ...(tom ? { tom } : {}), ...(site ? { site } : {}), ...(redes ? { redes: redes.split(/[\s,]+/).filter(Boolean) } : {}),
|
|
248
|
+
}, { logoArquivo });
|
|
249
|
+
if (r.ok) r.contraste = validarContraste(cores);
|
|
250
|
+
return r;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ── texto e CLI ──────────────────────────────────────────────────────────
|
|
254
|
+
function mostrar(k) {
|
|
255
|
+
if (!k) return 'Nenhum kit ativo. Crie um: primocode marca criar';
|
|
256
|
+
const c = k.cores || {}, f = k.fontes || {};
|
|
257
|
+
const lin = [`Marca: ${k.nome}`,
|
|
258
|
+
` cores ${PAPEIS.filter((p) => c[p]).map((p) => `${p} ${c[p]}`).join(' · ') || '-'}`,
|
|
259
|
+
` fontes ${f.titulo || '-'} / ${f.texto || '-'}`,
|
|
260
|
+
` logo ${k.logo || '-'}`];
|
|
261
|
+
for (const [r, v] of [['slogan', k.slogan], ['tom', k.tom], ['site', k.site], ['redes', (k.redes || []).join(' ')], ['motion', k.assinatura]]) if (v) lin.push(` ${r.padEnd(7)} ${v}`);
|
|
262
|
+
const ruins = validarContraste(c).filter((x) => !x.ok);
|
|
263
|
+
for (const x of ruins) lin.push(` ! contraste ${x.par} ${x.razao} (< 4,5)`);
|
|
264
|
+
return lin.join('\n');
|
|
265
|
+
}
|
|
266
|
+
function tela(lista, ativaNome) {
|
|
267
|
+
return lista.length ? lista.map((k) => `${slug(k.nome) === ativaNome ? '●' : ' '} ${k.nome}${k.site ? ' ' + k.site : ''}`).join('\n') : 'Nenhum kit ainda. Crie um: primocode marca criar';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function perguntarNoTerminal() {
|
|
271
|
+
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
272
|
+
const ask = (t, p) => new Promise((res) => rl.question(`${t}${p ? ` [${p}]` : ''}: `, (a) => res(a.trim() || p || '')));
|
|
273
|
+
return { ask, fechar: () => rl.close() };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** `primocode marca <op>`; o /marca da sessão chama o mesmo, com `perguntar` próprio. */
|
|
277
|
+
async function main(argv, { perguntar, dizer = console.log, pasta = process.cwd(), deps } = {}) {
|
|
278
|
+
const [op = 'mostrar', ...resto] = argv;
|
|
279
|
+
const nome = resto.filter((x) => x !== 'aqui').join(' ').trim();
|
|
280
|
+
const aqui = resto.includes('aqui');
|
|
281
|
+
switch (op) {
|
|
282
|
+
case 'criar': {
|
|
283
|
+
// O plano limita quantos kits se guarda (grátis 1): diz o porquê antes de perguntar qualquer coisa.
|
|
284
|
+
const lim = await (deps && deps.cabeMais ? deps.cabeMais(listar().length) : require('./limites.js').cabeMais('kitsMarca', listar().length));
|
|
285
|
+
if (!lim.ok) { dizer(lim.error); return 1; }
|
|
286
|
+
const t = perguntar ? null : perguntarNoTerminal();
|
|
287
|
+
try {
|
|
288
|
+
const r = await criar({ perguntar: perguntar || t.ask, dizer, deps });
|
|
289
|
+
if (!r.ok) { dizer(r.error); return 1; }
|
|
290
|
+
dizer(`Kit "${r.kit.nome}" salvo em ${r.pasta}. Ativar: primocode marca usar ${r.kit.nome}`);
|
|
291
|
+
return 0;
|
|
292
|
+
} finally { if (t) t.fechar(); }
|
|
293
|
+
}
|
|
294
|
+
case 'listar': case 'ls': dizer(tela(listar(), ativaGlobal())); return 0;
|
|
295
|
+
case 'usar': {
|
|
296
|
+
if (!nome) { dizer('Qual? primocode marca usar <nome> [aqui]'); return 1; }
|
|
297
|
+
const r = usar(nome, { aqui: aqui ? pasta : null });
|
|
298
|
+
dizer(r.ok ? `Marca "${r.kit.nome}" ${r.onde === 'projeto' ? 'fixada nesta pasta' : 'ativa'}.` : r.error);
|
|
299
|
+
return r.ok ? 0 : 1;
|
|
300
|
+
}
|
|
301
|
+
case 'mostrar': case 'ver': dizer(mostrar(nome ? ler(nome) : ativa([pasta]))); return 0;
|
|
302
|
+
case 'apagar': case 'rm': {
|
|
303
|
+
if (!nome) { dizer('Qual? primocode marca apagar <nome>'); return 1; }
|
|
304
|
+
const r = apagar(nome);
|
|
305
|
+
dizer(r.ok ? `Kit "${nome}" apagado.` : r.error);
|
|
306
|
+
return r.ok ? 0 : 1;
|
|
307
|
+
}
|
|
308
|
+
default: dizer('Uso: primocode marca criar | listar | usar <nome> [aqui] | mostrar [nome] | apagar <nome>'); return 1;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module.exports = { raiz, slug, normHex, contraste, ajustar, validarContraste, ler, listar, salvar, apagar, usar, ativa, fixadaEm, preencher, doSite, criar, mostrar, main };
|